
前不久看到博客园一位园友写了一篇文章其中的观点是要想高性能需要尽量避开网络开销IO避开海量数据避开资源争夺。对于这3点我觉得很有道理。所以也想谈一下CQRS架构下是如何实现高性能的。关于CQRSCommand Query Responsibility Segregation架构大家应该不会陌生了。简单的说就是一个系统从架构上把它拆分为两部分命令处理写请求查询处理读请求。然后读写两边可以用不同的架构实现以实现CQ两端即Command Side简称C端Query Side简称Q端的分别优化。CQRS作为一个读写分离思想的架构在数据存储方面没有做过多的约束。所以我觉得CQRS可以有不同层次的实现比如CQ两端数据库共享CQ两端只是在上层代码上分离这种做法带来的好处是可以让我们的代码读写分离更好维护且没有CQ两端的数据一致性问题因为是共享一个数据库的。我个人认为这种架构很实用既兼顾了数据的强一致性又能让代码好维护。CQ两端数据库和上层代码都分离然后Q的数据由C端同步过来一般是通过Domain Event进行同步。同步方式有两种同步或异步如果需要CQ两端的强一致性则需要用同步如果能接受CQ两端数据的最终一致性则可以使用异步。采用这种方式的架构个人觉得C端应该采用Event Sourcing简称ES模式才有意义否则就是自己给自己找麻烦。因为这样做你会发现会出现冗余数据同样的数据在C端的db中有而在Q端的db中也有。和上面第一种做法相比我想不到什么好处。而采用ES则所有C端的最新数据全部用Domain Event表达即可而要查询显示用的数据则从Q端的ReadDB关系型数据库查询即可。我觉得要实现高性能可以谈的东西还有很多。下面我想重点说说我想到的一些设计思路避开资源争夺秒杀活动的例子分析我觉得这是很重要的一点。什么是资源争夺我想就是多个线程同时修改同一个数据。就像阿里秒杀活动一样秒杀开抢时很多人同时抢一个商品导致商品的库存会被并发更新减库存这就是一个资源争夺的例子。一般如果资源竞争不激烈那无所谓不会影响性能但是如果像秒杀这种场景那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不能被重复执行。那怎么保证呢想想我们平时一些判断重复的操作怎么做的一般有两个做法1db对某一列建唯一索引这样可以严格保证某一列数据的值不会重复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架构中CommandEvent的产生以及处理是非常频繁的数据量也是非常大的。那如何保证这几步幂等处理的高性能呢对于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的存储唯一需要的是AggregateRootIdVersion的唯一索引其他就无任何要求了。那这样就和CommandStore一样好办了。如果也是采用关系型DB那只要用AggregateRootIdVersion这两个作为联合主键即可。然后如果要分库分表我们可以先根据AggregateRootType做第一级垂直拆分即把不同的聚合根类型产生的事件分开存储。然后和Command一样相同聚合根产生的事件可以根据AggregateRootId的hashcode来拆分同一个AggregateRootId的所有事件都放一起。这样既能保证AggregateRootIdVersion的唯一性又能保证数据的水平拆分。从而让整个EventStore可以无限制水平伸缩。当然我们也完全可以采用基于key/value的NoSQL来存储。另外我们查询事件也都是会确定聚合根的类型以及聚合根的ID所以这和路由机制一直不会导致我们无法知道当前要查询的聚合根的事件在哪个分区上。设计存储时的重点考虑点在设计command, event的存储时我认为主要考虑的应该是提高整体的吞吐量而不是追求单机存储的性能。因为假如我们的系统平均每秒产生1W个事件那一天就是8.64亿个事件。已经是很大的数据量。所以我们必须要对command, event这种进行分片。比如我们设计864个表那每个表每天产生100W条记录这是在可以接受的范围内。然后我们一旦分了864个表了肯定会把它们分布在不同的物理数据库上。这样就是多个物理数据库同时提供存储服务可以整体提高存储的吞吐量。我个人比较倾向于使用MySQL来存储即可因为一方面MySQL是开源的各种分库分表的成熟做法比较多。另一方面关系型数据库相比Mongodb这种自己更熟悉能更好的控制。比如数据扩容方案可以自己做不像MongoDB这种虽然它都帮我们搞定了大数据存储但一旦出了问题也许自己无法掌控。另一方面关于RT即单条数据存储时的响应时间这个我觉得不管是关系型数据库还是NoSQL最终的瓶颈都是在磁盘IO。NoSQL之所以这么快无非就是异步刷盘而关系型DB不是很快因为它要保证数据的落地要保证数据的更高级别的可靠性。所以我觉得要在保证数据不会丢失的情况下尽量提高RT可以考虑使用SSD硬盘。另一方面我觉得由于我们已经做了分库分表了所以单个DB的压力不会太大所以一般局域网内的RT也不会延迟很大应该可以接受。聚合根的内存模式In-MemoryIn-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的状态的吧。多个消息同时发送给actor时全部会先放入该actor的mailbox里排队然后actor单线程从mailbox顺序消费消息消费一个后产生事件持久化事件akka-persistence也是采用了ES的方式持久化持久化完成后更新actor的状态更新状态完成后再处理mailbox中的下一个消息从上面的过程我们可以看出akka框架本质上也实现了避免资源竞争的原则因为每个actor是单线程处理它的mailbox中的每个消息的从而就避免了并发冲突。然后我们可以看到akka框架也是先持久化事件之后再更新actor的状态的。这说明akka采用的也叫保守的方式即必须先确保数据落地再更新内存再处理下一个消息。真正理想的in-memory架构应该是可以忽略持久化当actor处理完一个消息后立即修改自己的状态然后立即处理下一个消息。然后actor产生的事件的持久化完全是异步的也就是不用等待持久化事件完成后再更新actor的状态然后处理下一个消息。我认为是不是异步持久化不重要因为既然大家都要面临一个问题就是要在宕机后恢复actor的状态那持久化事件是不可避免的。所以我也是认为事件不必异步持久化完全可以像akka框架那样产生的事件先同步持久化完成后再更新actor的状态即可。这样做在宕机恢复actor的状态到最新时就只要简单的从db获取所有事件然后通过ES得到actor最新状态即可。然后如果担心事件同步持久化有性能瓶颈那这个总是不可避免这块不做好那整个系统的性能就上不去所以我们可以采用SSDsharding, 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都是基于消息驱动的架构。虽然实现方式有所区别但目的都是相同的。小结本文从CQRSEvent Sourcing的架构出发结合实现高性能的几个要注意的点避开网络开销IO避开海量数据避开资源争夺分析了这种架构下我所想到的一些可能的设计。整个架构中一个Command在被处理时一般是需要做两次IO1持久化Command2持久化事件当然这里没有算上消息的发送和接收的IO。整个架构完全基于消息驱动所以拥有一个稳定可扩展高性能的分布式消息队列中间件是比不可少的EQueue正是在向这个目标努力的一个成果。目前EQueue的TCP通信层可以做到发送100W消息在一台i7 CPU的普通机器上只需3s有兴趣的同学可以看一下。最后ENode框架就是按照本文中所说的这些设计来实现的有兴趣的朋友欢迎去下载并和我交流哦不早了该睡了。