对修改同一个聚合根的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的幂等处理
In-Memory模式也是一种削减收集IO的一种设计,经由过程让所有生命周期还没停止的聚合根一向常驻在内存,大年夜而实现当我们要修改某个聚合根时,不必再像传统的方法那样,先大年夜db获取聚合根,再更新,完成后再保存到db了。而是聚合根一向在内存,当Command Handler要修改某个聚合根时,直接大年夜内存拿到该聚合根对象即可,不须要任何序列化反序列化或IO的操作。基于ES模式,我们不须要直接保存聚合根,而是只要简单的保存聚合根产生的事宜即可。当办事器断电要恢复聚合根时,则只要用事宜溯源(Event Sourcing, ES)的方法恢复聚合根到最新状况即可。
这一点,我想不难解得。比如转账的例子中,假如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持久化的幂等处理
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架构所说的要接收最终一致性的原因。
推荐阅读
因为这些技巧的合营之处是它们生成本身的特点,也许我们应当称之为无特点建模的阶段(Era of Featureless Modeling)。你仍然不得不应用已知的标注实例进行练习,然则你不必在列中填入预定>>>详细阅读
本文标题:谈一下关于CQRS架构如何实现高性能
地址:http://www.17bianji.com/lsqh/36039.html
1/2 1