Akka 系列(八):Akka persistence 设计理念之 CQRS

这一篇文章主要是讲解Akka persistence的核心设计理念,也是CQRS(Command Query Responsibility Segregation)架构设计的典型应用,就让咱们来看看为何Akka persistence会采用CQRS架构设计。数据库

CQRS

不少时候咱们在处理高并发的业务需求的时候,每每能把应用层的代码优化的很好,好比缓存,限流,均衡负载等,可是很难避免的一个问题就是数据的持久化,以至数据库的性能极可能就是系统性能的瓶颈,我前面的那篇文章也讲到,若是咱们用数据库去保证记录的CRUD,在并发高的状况下,让数据库执行这么多的事务操做,会让不少数据库操做超时,链接池不够用的状况,致使大量请求失败,系统的错误率上升和负载性能降低。缓存

既然这样,那咱们可不可借鉴一下读写分离的思想呢?假使写操做和同操做分离,甚至是对不一样数据表,数据库操做,那么咱们就能够大大下降数据库的瓶颈,使整个系统的性能大大提高。那么CQRS究竟是作了什么呢?服务器

咱们先来看看普通的方式:架构


acid
acid

咱们能够看出,咱们对数据的请求都是经过相应的接口直接对数据库进行操做,这在并发大的时候确定会对数据库形成很大的压力,虽然架构简单,但在面对并发高的状况下力不从心。并发

那么CQRS的方式有什么不一样呢?咱们也来看看它的执行方式:异步


acid
acid

乍得一看,彷佛跟普通的方式没什么不一样啊,不就多了一个事件和存储DB么,其实否则,小小的改动即是核心理念的转换,首先咱们能够看到在CQRS架构中会多出一个Event,那它到底表明着什么含义呢?其实看过上篇文章的同窗很容易理解,Event是咱们系统根据请求处理得出的一个领域模型,好比一个修改余额操做事件,固然这个Event中只会保存关键性的数据。分布式

不少同窗又有疑问了,这不跟普通的读写分离很像么,难道还隐藏着什么秘密?那咱们就来比较一下几种方式的不一样之处:高并发

1.单数据库模式
  • 写操做会产生互斥锁,致使性能下降;
  • 即便使用乐观锁,可是在大量写操做的状况下也会大量失败;
2.读写分离
  • 读写分离经过物理服务器增长,负荷增长;
  • 读写分离更适用于读操做大于写操做的场景;
  • 读写分离在面对大量写操做的状况下仍是很吃力;
3.CQRS
  • 普通数据的持久化和Event持久化可使用同一台数据库;
  • 利用架构设计可使读和写操做尽量的分离;
  • 能支撑大量写的操做状况;
  • 能够支持数据异步持久,确保数据最终一致性;

从三种方式各自的特色能够看出,单数据库模式的在大量读写的状况下有很大的性能瓶颈,但简单的读写分离在面对大量写操做的时候也仍是力不从心,好比最多见的库存修改查询场景:性能


common-action
common-action

咱们能够发如今这种模式下写数据库的压力还会很大,并且还有数据同步,数据延迟等问题。优化

那么咱们用CQRS架构设计会是怎么样呢:


cqrs-action
cqrs-action

首先咱们能够业务模型进行分离,对不一样的查询进行分离,另外避免不了的同一区间数据段进行异步持久化,在保证数据一致性的状况下提高系统的吞吐量。这种设计咱们不多会遇到事务竞争,另外还可使用内存数据库(固然若是是内存操做那就最快)来提高数据的写入。(以上的数据库均可为分布式数据库,不担忧单机宕机)

那么CRQS机制是怎么保证数据的一致性的呢?

从上图中咱们能够看出,一个写操做咱们会在系统进行初步处理后生成一个领域事件,好比a用户购买了xx商品1件,b用户购买了xx商品2件等,按照普通的方式咱们确定是直接将订单操做,库存修改操做一并放在一个事务内去操做数据库,性能可想而知,而用CQRS的方式后,首先系统在持久化相应的领域事件后和修改内存中的库存(这个处理很是迅速)后即可立刻向用户作出反应,真正的具体信息持久能够异步进行,固然如果当在具体信息持久化的过程当中出错了怎么办,系统能恢复正确的数据么,固然能够,由于咱们的领域事件事件已经持久化成功了,在系统恢复的时候,咱们能够根据领域事件来恢复真正的数据,固然为了防止恢复数据是形成数据丢失,数据重复等问题咱们须要制定相应的原则,好比给领域事件分配相应id等。

使用CQRS会带来性能上的提高,固然它也有它的弊端:

  • 使系统变得更复杂,作一些额外的设计;
  • CQRS保证的是最终一致性,有可能只适用于特定的业务场景;

Akka Persistence 中CQRS的应用

经过上面的讲解,相信你们对CQRS已经有了必定的了解,下面咱们就来看看它在Akka Persistence中的具体应用,这里我就结合上一篇文章抽奖的例子,好比其中的LotteryCmd即是一个写操做命令,系统通过相应的处理后获得相应的领域事件,好比其中LuckyEvent,而后咱们将LuckyEvent进行持久化,并修改内存中抽奖的余额,返回相应的结果,这里咱们就能够同时将结果反馈给用户,并对结果进行异步持久化,流程以下:


cqrs-example
cqrs-example

能够看出,Akka Persistence的原理彻底是基于CQRS的架构设计的,另外Persistence Actor还会保存一个内存状态,至关于一个in memory数据库,能够用来提供关键数据的存储和查询,好比前面说到的库存,余额等数据,这部分的设计取决于具体的业务场景。

阅读Akka Persistence相关源码,其的核心就在于PersistentActor接口中的几个持久方法,好比其中的

def persist[A](event: A)(handler: AUnit): Unit

def persistAll[A](events: immutable.Seq[A])(handler: AUnit): Unit复制代码

等方法,它们都有两个参数,一个是持久化的事件,一个是持久化后的后续处理逻辑,咱们能够在后续handler中修改Actor内部状态,向外部发送消息等操做,这里的模式就是基于CQRS架构的,修改状态有事件驱动,另外Akka还能够在系统出错时,利用相应的事件恢复Actor的状态。

总结

总的来讲,CQRS架构是一种不一样于以往的CRUD的架构,因此你在享受它带来的高性能的同时可能会遇到一些奇怪的问题,固然这些都是能够解决的,重要的是思惟上的改变,好比事件驱动,领域模型等概念,不过相信当你理解并掌握它以后,你便会爱上它的。

相关文章
相关标签/搜索