谈一下关于CQRS架构如何实现高性能

CQRS架构简介

源码:http://www.jinhusns.com/Products/Download/?type=xcjnode

关于CQRS(Command Query Responsibility Segration)架构,你们应该不会陌生了。简单的说,就是一个系统,从架构上把它拆分为两部分:命令处理(写请求)+查询处理(读请求)。而后读写 两边能够用不一样的架构实现,以实现CQ两端(即Command Side,简称C端;Query Side,简称Q端)的分别优化。CQRS做为一个读写分离思想的架构,在数据存储方面,没有作过多的约束。因此,我以为CQRS能够有不一样层次的实现, 好比:git

  1. CQ两端数据库共享,CQ两端只是在上层代码上分离;这种作法,带来的好处是可让咱们的代码读写分离,更好维护,且没有CQ两端的数据一致性问题,由于是共享一个数据库的。我我的认为,这种架构很实用,既兼顾了数据的强一致性,又能让代码好维护。
  2. CQ两端数据库和上层代码都分离,而后Q的数据由C端同步过来,通常是经过Domain Event进行同步。同步方式有两种,同步或异步,若是须要CQ两端的强一致性,则须要用同步;若是能接受CQ两端数据的最终一致性,则可使用异步。采 用这种方式的架构,我的以为,C端应该采用Event Sourcing(简称ES)模式才有意义,不然就是本身给本身找麻烦。由于这样作你会发现会出现冗余数据,一样的数据,在C端的db中有,而在Q端的 db中也有。和上面第一种作法相比,我想不到什么好处。而采用ES,则全部C端的最新数据所有用Domain Event表达便可;而要查询显示用的数据,则从Q端的ReadDB(关系型数据库)查询便可。

我以为要实现高性能,能够谈的东西还有不少。下面我想重点说说我想到的一些设计思路:github

避开资源争夺

秒杀活动的例子分析

我以为这是很重要的一点。什么是资源争夺?我想就是多个线程同时修改同一个数据。就像阿里秒杀活动同样,秒杀开抢时,不少人同时抢一个商品,致使商 品的库存会被并发更新减库存,这就是一个资源争夺的例子。通常若是资源竞争不激烈,那无所谓,不会影响性能;可是若是像秒杀这种场景,那db就会抗不住 了。在秒杀这种场景下,大量线程须要同时更新同一条记录,进而致使MySQL内部大量线程堆积,对服务性能、稳定性形成很大伤害。那怎么办呢?我记得阿里 的丁奇写过一个分享,思路就是当MySQL的服务端多个线程同时修改一条记录时,能够对这些修改请求进行排队,而后对于InnoDB引擎层,就是串行的。 这样排队后,无论上层应用发过来多少并行的修改同一行的请求,对于MySQL Server端来讲,内部老是会聪明的对同一行的修改请求都排队处理;这样就能确保不会有并发产生,从而不会致使线程浪费堆积,致使数据库性能降低。这个 方案能够见下图所示:数据库

如上图所示,当不少请求都要修改A记录时,MySQL Server内部会对这些请求进行排队,而后一个个将对A的修改请求提交到InnoDB引擎层。这样看似在排队,实际上会确保MySQL Server不会死掉,能够保证对外提供稳定的TPS。编程

可是,对于商品秒杀这个场景,还有优化的空间,就是Group Commit技术。Group Commit就是对多个请求合并为一次操做进行处理。秒杀时,你们都在购买这个商品,A买2件,B买3件,C买1件;其实咱们能够把A,B,C的这三个请 求合并为一次减库存操做,就是一次性减6件。这样,对于A,B,C的这三个请求,在InnoDB层咱们只须要作一次减库存操做便可。假设咱们Group Commit的每一批的size是50,那就是能够将50个减操做合并为一次减操做,而后提交到InnoDB。这样,将大大提升秒杀场景下,商品减库存的 TPS。可是这个Group Commit的每批大小不是越大越好,而是要根据并发量以及服务器的实际状况作测试来获得一个最优的值。经过Group Commit技术,根据丁奇的PPT,商品减库存的TPS性能从原来的1.5W提升到了8.5W。性能优化

从上面这个例子,咱们能够看到阿里是如何在实际场景中,经过优化MySQL Server来实现高并发的商品减库存的。可是,这个技术通常人还真的不会!由于没多少人有能力去优化MySQL的服务端,排队也不行,更别说Group Commit了。这个功能并非MySQL Server自带的,而是须要本身实现的。可是,这个思路我想咱们均可以借鉴。服务器

CQRS如何实现避免资源竞争

那么对于CQRS架构,如何按照这个思路来设计呢?我想重点说一下我上面提到的第二种CQRS架构。对于C端,咱们的目标是尽量的在1s内处理更 多的Command,也就是数据写请求。在经典DDD的四层架构中,咱们会有一个模式叫工做单元模式,即Unit of Work(简称UoW)模式。经过该模式,咱们能在应用层,一次性以事务的方式将当前请求所涉及的多个对象的修改提交到DB。微软的EF实体框架的 DbContext就是一个UoW模式的实现。这种作法的好处是,一个请求对多个聚合根的修改,能作到强一致性,由于是事务的。可是这种作法,实际上,没 有很好的遵照避开资源竞争的原则。试想,事务A要修改a1,a2,a3三个聚合根;事务B要修改a2,a3,a4;事务C要修改a3,a4,a5三个聚合 根。那这样,咱们很容易理解,这三个事务只能串行执行,由于它们要修改相同的资源。好比事务A和事务B都要修改a2,a3这两个聚合根,那同一时刻,只能 由一个事务能被执行。同理,事务B和事务C也是同样。若是A,B,C这种事务执行的并发很高,那数据库就会出现严重的并发冲突,甚至死锁。那要如何避免这 种资源竞争呢?我以为咱们能够采起三个措施:网络

让一个Command老是只修改一个聚合根

这个作法其实就是缩小事务的范围,确保一个事务一次只涉及一条记录的修改。也就是作到,只有单个聚合根的修改才是事务的,让聚合根成为数据强一致性 的最小单位。这样咱们就能最大化的实现并行修改。可是你会问,可是我一个请求就是会涉及多个聚合根的修改的,这种状况怎么办呢?在CQRS架构中,有一个 东西叫Saga。Saga是一种基于事件驱动的思想来实现业务流程的技术,经过Saga,咱们能够用最终一致性的方式最终实现对多个聚合根的修改。对于一 次涉及多个聚合根修改的业务场景,通常老是能够设计为一个业务流程,也就是能够定义出要先作什么后作什么。好比以银行转帐的场景为例子,若是是按照传统事 务的作法,那多是先开启一个事务,而后让A帐号扣减余额,再让B帐号加上余额,最后提交事务;若是A帐号余额不足,则直接抛出异常,同理B帐号若是加上 余额也遇到异常,那也抛出异常便可,事务会保证原子性以及自动回滚。也就是说,数据一致性已经由DB帮咱们作掉了。架构

可是,若是是Saga的设计,那就不是这样了。咱们会把整个转帐过程定义为一个业务流程。而后,流程中会包括多个参与该流程的聚合根以及一个用于协 调聚合根交互的流程管理器(ProcessManager,无状态),流程管理器负责响应流程中的每一个聚合根产生的领域事件,而后根据事件发送相应的 Command,从而继续驱动其余的聚合根进行操做。并发

转帐的例子,涉及到的聚合根有:两个银行帐号聚合根,一个交易(Transaction)聚合根,它用于负责存储流程的当前状态,它还会维护流程状 态变动时的规则约束;而后固然还有一个流程管理器。转帐开始时,咱们会先建立一个Transaction聚合根,而后它产生一个 TransactionStarted的事件,而后流程管理器响应事件,而后发送一个Command让A帐号聚合根作减余额的操做;A帐号操做完成后,产 生领域事件;而后流程管理器响应事件,而后发送一个Command通知Transaction聚合根确认A帐号的操做;确认完成后也会产生事件,而后流程 管理器再响应,而后发送一个Command通知B帐号作加上余额的操做;后续的步骤就不详细讲了。大概意思我想已经表达了。总之,经过这样的设计,咱们可 以经过事件驱动的方式,来完成整个业务流程。若是流程中的任何一步出现了异常,那咱们能够在流程中定义补偿机制实现回退操做。或者不回退也不要紧,由于 Transaction聚合根记录了流程的当前状态,这样咱们能够很方便的后续排查有状态没有正常结束的转帐交易。具体的设计和代码,有兴趣的能够去看一 下ENode源代码中的银行转帐的例子,里面有完整的实现。

对修改同一个聚合根的Command进行排队

和上面秒杀的设计同样,咱们能够对要同时修改同一个聚合根的Command进行排队。只不过这里的排队不是在MySQL Server端,而是在咱们本身程序里作这个排队。若是咱们是单台服务器处理全部的Command,那排队很容易作。就是只要在内存中,当要处理某个 Command时,判断当前Command要修改的聚合根是否前面已经有Command在处理,若是有,则排队;若是没有,则直接执行。而后当这个聚合根 的前一个Command执行完后,咱们就能处理该聚合根的下一个Command了;可是若是是集群的状况下呢,也就是你不止有一台服务器在处理 Command,而是有十台,那要怎么办呢?由于同一时刻,彻底有可能有两个不一样的Command在修改同一个聚合根。这个问题也简单,就是咱们能够对要 修改聚合根的Command根据聚合根的ID进行路由,根据聚合根的ID的hashcode,而后和当前处理Command的服务器数目取模,就能肯定当 前Command要被路由到哪一个服务器上处理了。这样咱们能确保在服务器数目不变的状况下,针对同一个聚合根实例修改的全部Command都是被路由到同 一台服务器处理。而后加上咱们前面在单个服务器里面内部作的排队设计,就能最终保证,对同一个聚合根的修改,同一时刻只有一个线程在进行。

经过上面这两个设计,咱们能够确保C端全部的Command,都不会出现并发冲突。可是也要付出代价,那就是要接受最终一致性。好比Saga的思 想,就是在最终一致性的基础上而实现的一种设计。而后,基于以上两点的这种架构的设计,我以为最关键的是要作到:1)分布式消息队列的可靠,不能丢消息, 不然Saga流程就断了;2)消息队列要高性能,支持高吞吐量;这样才能在高并发时,实现整个系统的总体的高性能。我开发的EQueue就是为了这个目标而设计的一个分布式消息队列,有兴趣的朋友能够去了解下哦。

Command和Event的幂等处理

CQRS架构是基于消息驱动的,因此咱们要尽可能避免消息的重复消费。不然,可能会致使某个消息被重复消费而致使最终数据没法一致。对于CQRS架构,我以为主要考虑三个环节的消息幂等处理。

Command的幂等处理

这一点,我想不难理解。好比转帐的例子中,假如A帐号扣减余额的命令被重复执行了,那会致使A帐号扣了两次钱。那最后就数据没法一致了。因此,咱们 要保证Command不能被重复执行。那怎么保证呢?想一想咱们平时一些判断重复的操做怎么作的?通常有两个作法:1)db对某一列建惟一索引,这样能够严 格保证某一列数据的值不会重复;2)经过程序保证,好比插入前先经过select查询判断是否存在,若是不存在,则insert,不然就认为重复;显然通 过第二种设计,在并发的状况下,是不能保证绝对的惟一性的。而后CQRS架构,我认为咱们能够经过持久化Command的方式,而后把CommandId 做为主键,确保Command不会重复。那咱们是否要每次执行Command前线判断该Command是否存在呢?不用。由于出现Command重复的概 率很低,通常只有是在咱们服务器机器数量变更时才会出现。好比增长了一台服务器后,会影响到Command的路由,从而最终会致使某个Command会被 重复处理,关于这里的细节,我这里不想多展开了,呵呵。有问题到回复里讨论吧。这个问题,咱们也能够最大程度上避免,好比咱们能够在某一天系统最空的时候 预先增长好服务器,这样能够把出现重复消费消息的状况降至最低。天然也就最大化的避免了Command的重复执行。因此,基于这个缘由,咱们没有必要在每 次执行一个Command时先判断该Command是否已执行。而是只要在Command执行完以后,直接持久化该Command便可,而后由于db中以 CommandId为主键,因此若是出现重复,会主键重复的异常。咱们只要捕获该异常,而后就知道了该Command已经存在,这就说明该Command 以前已经被处理过了,那咱们只要忽略该Command便可(固然实际上不能直接忽略,这里我因为篇幅问题,我就不详细展开了,具体咱们能够再讨论)。然 后,若是持久化没有问题,说明该Command以前没有被执行过,那就OK了。这里,还有个问题也不能忽视,就是某个Command第一次执行完成了,也 持久化成功了,可是它因为某种缘由没有从消息队列中删除。因此,当它下次再被执行时,Command Handler里可能会报异常,因此,健壮的作法时,咱们要捕获这个异常。当出现异常时,咱们要检查该Command是否以前已执行过,若是有,就要认为 当前Command执行正确,而后要把以前Command产生的事件拿出来作后续的处理。这个问题有点深刻了,我暂时不细化了。有兴趣的能够找我私聊。

Event持久化的幂等处理

而后,由于咱们的架构是基于ES的,因此,针对新增或修改聚合根的Command,老是会产生相应的领域事件(Domain Event)。咱们接下来的要作的事情就是要先持久化事件,再分发这些事件给全部的外部事件订阅者。你们知道,聚合根有生命周期,在它的生命周期里,会经 历各类事件,而事件的发生总有肯定的时间顺序。因此,为了明确哪一个事件先发生,哪一个事件后发生,咱们能够对每一个事件设置一个版本号,即version。聚 合根第一个产生的事件的version为1,第二个为2,以此类推。而后聚合根自己也有一个版本号,用于记录当前本身的版本是什么,它每次产生下一个事件 时,也能根据本身的版本号推导出下一个要产生的事件的版本号是什么。好比聚合根当前的版本号为5,那下一个事件的版本号则为6。经过为每一个事件设计一个版 本号,咱们就能很方便的实现聚合根产生事件时的并发控制了,由于一个聚合根不可能产生两个版本号同样的事件,若是出现这种状况,那说明必定是出现并发冲突 了。也就是必定是出现了同一个聚合根同时被两个Command修改的状况了。因此,要实现事件持久化的幂等处理,也很好作了,就是db中的事件表,对聚合 根ID+聚合根当前的version建惟一索引。这样就能在db层面,确保Event持久化的幂等处理。另外,对于事件的持久化,咱们也能够像秒杀那样, 实现Group Commit。就是Command产生的事件不用立马持久化,而是能够先积累到必定的量,好比50个,而后再一次性Group Commit全部的事件。而后事件持久化完成后,再修改每一个聚合根的状态便可。若是Group Commit事件时遇到并发冲突(因为某个聚合根的事件的版本号有重复),则退回为单个一个个持久化事件便可。为何能够放心的这样作?由于咱们已经基本 作到确保一个聚合根同一时刻只会被一个Command修改。这样就能基本保证,这些Group Commit的事件也不会出现版本号冲突的状况。因此,你们是否以为,不少设计实际上是一环套一环的。Group Commit什么时候出发?我以为能够只要知足两个条件了就能够触发:1)某个定时的周期到了就能够触发,这个定时周期能够根据本身的业务场景进行配置,好比 每隔50ms触发一次;2)要Commit的事件到达某个最大值,即每批能够持久化的事件个数的最大值,好比每50个事件为一批,这个BatchSize 也须要根据实际业务场景和你的存储db的性能综合测试评估来获得一个最适合的值;什么时候可使用Group Commit?我以为只有是在并发很是高,当单个持久化事件遇到性能瓶颈时,才须要使用。不然反而会下降事件持久化的实时性,Group Commit提升的是高并发下单位时间内持久化的事件数。目的是为了下降应用和DB之间交互的次数,从而减小IO的次数。不知不觉就说到了最开始说的那3 点性能优化中的,尽可能减小IO了,呵呵。

Event消费时的幂等处理

CQRS架构图中,事件持久化完成后,接下来就是会把这些事件发布出去(发送到分布式消息队列),给消费者消费了,也就是给全部的Event Handler处理。这些Event Handler多是更新Q端的ReadDB,也多是发送邮件,也多是调用外部系统的接口。做为框架,应该有职责尽可能保证一个事件尽可能不要被某个 Event Handler重复消费,不然,就须要Event Handler本身保证了。这里的幂等处理,我能想到的办法就是用一张表,存储某个事件是否被某个Event Handler处理的信息。每次调用Event Handler以前,判断该Event Handler是否已处理过,若是没处理过,就处理,处理完后,插入一条记录到这个表。这个方法相信你们也都很容易想到。若是框架不作这个事情,那 Event Handler内部就要本身作好幂等处理。这个思路就是select if not exist, then handle, and at last insert的过程。能够看到这个过程不像前面那两个过程那样很严谨,由于在并发的状况下,理论上仍是会出现重复执行Event Handler的状况。或者即使不是并发时也可能会形成,那就是假如event handler执行成功了,可是last insert失败了,那框架仍是会重试执行event handler。这里,你会很容易想到,为了作这个幂等支持,Event Handler的一次完整执行,须要增长很多时间,从而会最后致使Query Side的数据更新的延迟。不过CQRS架构的思想就是Q端的数据由C端经过事件同步过来,因此Q端的更新自己就是有必定的延迟的。这也是CQRS架构所 说的要接收最终一致性的缘由。

关于幂等处理的性能问题的思考

关于CommandStore的性能瓶颈分析

你们知道,整个CQRS架构中,Command,Event的产生以及处理是很是频繁的,数据量也是很是大的。那如何保证这几步幂等处理的高性能 呢?对于Command的幂等处理,若是对性能要求不是很高,那咱们能够简单使用关系型DB便可,好比Sql Server, MySQL均可以。要实现幂等处理,只须要把主键设计为CommandId便可。其余不须要额外的惟一索引。因此这里的性能瓶颈至关因而对单表作大量 insert操做的最大TPS。通常MySQL数据库,SSD硬盘,要达到2W TPS应该没什么问题。对于这个表,咱们基本只有写入操做,不须要读取操做。只有是在Command插入遇到主键冲突,而后才可能须要偶尔根据主键读取一 下已经存在的Command的信息。而后,若是单表数据量太大,那怎么办,就是分表分库了。这就是最开始谈到的,要避开海量数据这个原则了,我想就是经过 sharding避开大数据来实现绕过IO瓶颈的设计了。不过一旦涉及到分库,分表,就又涉及到根据什么分库分表了,对于存储Command的表,我以为 比较简单,咱们能够先根据Command的类型(至关于根据业务作垂直拆分)作第一级路由,而后相同Command类型的Command,根据 CommandId的hashcode路由(水平拆分)便可。这样就能解决Command经过关系型DB存储的性能瓶颈问题。其实咱们还能够经过流行的基 于key/value的NoSQL来存储,好比能够选择本地运行的leveldb,或者支持分布式的ssdb,或者其余的,具体选择哪一个,能够结合本身的 业务场景来选择。总之,Command的存储能够有不少选择。

关于EventStore的性能瓶颈分析

经过上面的分析,咱们知道Event的存储惟一须要的是AggregateRootId+Version的惟一索引,其余就无任何要求了。那这样就 和CommandStore同样好办了。若是也是采用关系型DB,那只要用AggregateRootId+Version这两个做为联合主键便可。而后 若是要分库分表,咱们能够先根据AggregateRootType作第一级垂直拆分,即把不一样的聚合根类型产生的事件分开存储。而后和Command一 样,相同聚合根产生的事件,能够根据AggregateRootId的hashcode来拆分,同一个AggregateRootId的全部事件都放一 起。这样既能保证AggregateRootId+Version的惟一性,又能保证数据的水平拆分。从而让整个EventStore能够无限制水平伸 缩。固然,咱们也彻底能够采用基于key/value的NoSQL来存储。另外,咱们查询事件,也都是会肯定聚合根的类型以及聚合根的ID,因此,这和路 由机制一直,不会致使咱们没法知道当前要查询的聚合根的事件在哪一个分区上。

聚合根的内存模式(In-Memory)

In-Memory模式也是一种减小网络IO的一种设计,经过让全部生命周期还没结束的聚合根一直常驻在内存,从而实现当咱们要修改某个聚合根时, 没必要再像传统的方式那样,先从db获取聚合根,再更新,完成后再保存到db了。而是聚合根一直在内存,当Command Handler要修改某个聚合根时,直接从内存拿到该聚合根对象便可,不须要任何序列化反序列化或IO的操做。基于ES模式,咱们不须要直接保存聚合根, 而是只要简单的保存聚合根产生的事件便可。当服务器断电要恢复聚合根时,则只要用事件溯源(Event Sourcing, ES)的方式恢复聚合根到最新状态便可。

了解过actor的人应该也知道actor也是整个集群中就一个实例,而后每一个actor本身都有一个mailbox,这个mailbox用于存放 当前actor要处理的全部的消息。只要服务器不断电,那actor就一直存活在内存。因此,In-Memory模式也是actor的一个设计思想之一。 像以前很轰动的国外的一个LMAX架构,号称每秒单机单核能够处理600W订单,也是彻底基于in-memory模式。不过LMAX架构我以为只要做为学 习便可,要大范围使用,仍是有不少问题要解决,老外他们使用这种架构来处理订单,也是基于特定场景的,而且对编程(代码质量)和运维的要求都很是高。具体 有兴趣的能够去搜一下相关资料。

关于in-memory架构,想法是好的,经过将全部数据都放在内存,全部持久化都异步进行。也就是说,内存的数据才是最新的,db的数据是异步持 久化的,也就是某个时刻,内存中有些数据可能尚未被持久化到db。固然,若是你说你的程序不须要持久化数据,那另当别论了。那若是是异步持久化,主要的 问题就是宕机恢复的问题了。咱们看一下akka框架是怎么持久化akka的状态的吧。

  1. 多个消息同时发送给actor时,所有会先放入该actor的mailbox里排队;
  2. 而后actor单线程从mailbox顺序消费消息;
  3. 消费一个后产生事件;
  4. 持久化事件,akka-persistence也是采用了ES的方式持久化;
  5. 持久化完成后,更新actor的状态;
  6. 更新状态完成后,再处理mailbox中的下一个消息;

从上面的过程,咱们能够看出,akka框架本质上也实现了避免资源竞争的原则,由于每一个actor是单线程处理它的mailbox中的每一个消息的, 从而就避免了并发冲突。而后咱们能够看到akka框架也是先持久化事件以后,再更新actor的状态的。这说明,akka采用的也叫保守的方式,即必须先 确保数据落地,再更新内存,再处理下一个消息。真正理想的in-memory架构,应该是能够忽略持久化,当actor处理完一个消息后,当即修改本身的 状态,而后当即处理下一个消息。而后actor产生的事件的持久化,彻底是异步的;也就是不用等待持久化事件完成后再更新actor的状态,而后处理下一 个消息。

我认为,是否是异步持久化不重要,由于既然你们都要面临一个问题,就是要在宕机后,恢复actor的状态,那持久化事件是不可避免的。因此,我也是 认为,事件没必要异步持久化,彻底能够像akka框架那样,产生的事件先同步持久化,完成后再更新actor的状态便可。这样作,在宕机恢复actor的状 态到最新时,就只要简单的从db获取全部事件,而后经过ES获得actor最新状态便可。而后若是担忧事件同步持久化有性能瓶颈,那这个老是不可避免,这 块不作好,那整个系统的性能就上不去,因此咱们能够采用SSD,sharding, Group Commit, NoSQL等方法,优化持久化的性能便可。固然,若是采用异步持久化事件的方式,确实能大大提升actor的处理性能。可是要作到这点,还须要有一些前提 的。好比要确保整个集群中一个actor只有一个实例,不能有两个同样的actor在工做。由于若是出现这种状况,那这两个同样的actor就会同时产生 事件,致使最后事件持久化的时候一定会出现并发冲突(事件版本号相同)的问题。但要保证急群众一个actor只有一个实例,是很困难的,由于咱们可能会动 态往集群中增长服务器,此时一定会有一些actor要迁移到新服务器。这个迁移过程也很复杂,一个actor从原来的服务器迁移到新的服务器,意味着要先 中止原服务器的actor的工做。而后还要把actor再新服务器上启动;而后原服务器上的actor的mailbox中的消息还要发给新的actor, 而后后续可能还在发给原actor的消息也要转发到新的actor。而后新的actor重启也很复杂,由于要确保启动以后的actor的状态必定是最新 的,而咱们知道这种纯in-memory模式下,事件的持久化时异步的,因此可能还有一些事件还在消息队列,还没被持久化。因此重启actor时还要检查 消息队列中是否还有未消费的事件。若是还有,就须要等待。不然,咱们恢复的actor的状态就不是最新的,这样就没法保证内存数据是最新的这个目的,这样 in-memory也就失去了意义。这些都是麻烦的技术问题。总之,要实现真正的in-memory架构,没那么容易。固然,若是你说你能够用数据网格之 类的产品,无分布式,那也许可行,不过这是另一种架构了。

上面说了,akka框架的核心工做原理,以及其余一些方面,好比akka会确保一个actor实例在集群中只有一个。这点其实也是和本文说的同样, 也是为了不资源竞争,包括它的mailbox也是同样。以前我设计ENode时,没了解过akka框架,后来我学习后,发现和ENode的思想是如此接 近,呵呵。好比:1)都是集群中只有一个聚合根实例;2)都对单个聚合根的操做的Command作排队处理;3)都采用ES的方式进行状态持久化;4)都 是基于消息驱动的架构。虽然实现方式有所区别,但目的都是相同的。

小结

源码:http://www.jinhusns.com/Products/Download/?type=xcj

本文,从CQRS+Event Sourcing的架构出发,结合实现高性能的几个要注意的点(避开网络开销(IO),避开海量数据,避开资源争夺),分析了这种架构下,我所想到的一些 可能的设计。整个架构中,一个Command在被处理时,通常是须要作两次IO,1)持久化Command;2)持久化事件;固然,这里没有算上消息的发 送和接收的IO。整个架构彻底基于消息驱动,因此拥有一个稳定可扩展高性能的分布式消息队列中间件是比不可少的,EQueue正是在向这个目标努力的一个 成果。目前EQueue的TCP通讯层,能够作到发送100W消息,在一台i7 CPU的普通机器上,只需3s;有兴趣的同窗能够看一下。

相关文章
相关标签/搜索