enode框架入门:Staged event-driven architecture思想的运用2013-11-26 博客园 netfocus上一篇文章,简单介绍了enode框架的command service api设计思路。本文介绍一下enode框架对Staged Event-driven architecture思想的运用。通过前一篇文章我们知道command service是会被高并发的访问,我 们除了可以用异步的方式执行command以及集群的方式来提高系统响应性能外。最根本上要解决的问题是尽量 快的处理单个command。这样才能在单位时间内处理更多的command。先贴一下enode框架的内部实现架 构图,这样对大家理解后面的分析有帮助。

我觉得要尽量快的处理command,主要思路有两点:能并行处理的尽量并行command service接收到command后,会把command发送到某个可用的command队列。然后该command队列的出 口端,如果只有单个线程在处理command,而且这个线程如果有IO操作,那肯定快不到哪里去;因为只要处理 单个command的速度跟不上command进入队列的速度,那command队列里的command就会不断增多,导致command 执行的延迟增加。所以,思路就是,设计多个线程(就是上图中的Command Processor中的worker)来同时从 command队列拿command,然后处理。这样就能实现多个线程在同时处理不同的command。但是光这样还不够,实际上我们还可以做的更好,那就是command queue也可以设计为多个。也就是说 command service接收到command后,会通过一个command router,将当前command路由到某个可用的command队 列,然后将该command发送到该队列。这样做的好处是,我们的command service背后有多个command队列(上 图画了两个command queue)支撑着,每个command队列的出口端又有多个线程在同时处理。这样的话,我们就 能最大化的压榨我们的服务器CPU和内存等资源了。当然,框架要支持允许用户配置多少个command队列,以及 每个队列多少个线程处理。这样框架使用者就能根据当前服务器的CPU个数来决定该如何配置了。同理,domain model产生的事件(domain event)的处理也应该要并行处理;那具体是什么处理呢?就是 上图中的Event Processor所做的事情。Event Processor会包含多个worker,每个worker就是一个线程。每个 worker会从event queue中拿出事件,然后将事件进一步分发(dispatch)给所有的事件订阅者。那么上面这些并行执行的逻辑是如何访问共享资源的呢?对于command processor中的每个work线 程,从上面的架构图可以清晰的看到,共享资源是event store和memory cache。event store,我们会并发的 写入事件;memory cache,我们会并发的更新聚合根。所以,这两种存储都必须很好的支持高并发的写入,且 要高效;经过我的一些调研,个人觉得mongodb比较适合作为eventstore。原因是:1)支持集群和sharding; 2)支持唯一索引;3)支持关系型查询;4)高性能,默认是先保存到内存,每100ms将内存数据写入日志,每 1分钟将内存数据正式写入磁盘;基于这4点,我们能利用mongodb实现一个比较理想的eventstore。而内存缓 存(memory cache),我觉得memcached或者redis都还不错,都是比较成熟的分布式缓存。利用分布式缓存, 我们不必担心数据放不下的问题,因为我们可以对数据按特征进行分区存放。这个思路就像数据库的分库分表 类似。需要注意的是,eventstore必须支持严格控制并发冲突,mongodb的唯一索引可以确保这一点;而 memory cache,不用支持并发冲突检测,只要能保障快速的根据key读写即可。因为我们总是先持久化完事件 后再将最新状态的聚合根更新到memory cache,而持久化事件到eventstore已经做了并发冲突检测,所以更新 到memory cache就一定也是按照事件持久化的顺序被更新到memory cache的。另外,实际上event store和 memory cache是被整个web服务器集群所共享的。不过幸好mongodb,redis等产品足够强大,都支持横向扩展 ,所以我们完全有信心在web服务器不断增加的情况下,也对mongodb,redis做相应的横向扩展,从而不会让这 两个地方产生瓶颈。