破除CQRS的一些神话

CQRS实际是一个很简单的概念,读写分离,这是架构首次重视读写分离,以往我们都没有这个意识,比如使用Spring+Hibernate,写命令和读操作被服务统管在一起,其实从REST的POST/GET分离的概念我们可能也体会到了Web系统的一个本质。

如果我们从读写分离关注这个新角度重新审视我们系统,发现优化余地很大,比如Tomcat等Web容器总是会自动产生JSESSIONID,依附在URL或Cookie中,这两者都很违背互联网原则,因为在互联网环境中,CDN是必须的,但是本来是一个内容不怎么更新的文章因为有了JSESSIONID,被CDN等会认为是动态页面而不缓存,CDN没有起作用,而我们关注的是要让CDN能分清楚经常更新和不太经常更新的。要做到这点,必须从读写分离这个角度将系统的所有读页面去除Cookie,只有用户登录后(可能会写操作)才会有Cookie。

当然,从读写分离角度还可以很优雅解决负载问题,因为大部分系统写操作负载不大,那么分两台服务器分别负责读和写,对读的服务器我们就可以尽情优化,而不担心用户Session问题,去除了服务器HttpSession粘附,使用负载平衡效率更高,因为负载平衡如果发现一个URL后面有JSESSIONID,会发到原来服务器,这样负载平衡的粒度只能划分到Session,而读写分离后,没有Session,负载平衡的粒度可以划分到每个Request

来自文章破除CQRS神秘系统地谈了这个问题,作者看到有些CQRS不必要地走弯路,搞复杂了,特别写这篇文章澄清一下。

1.误区一:CQRS = Event Sourcing,反之亦然。
Event sourcing一般比较合适CQRS, 因为它在模型的最终一致性方面提出了一个直接的构建和更新读存储的方式。同时能够更好聚合那些侧重行为的系统或基于任务驱动UI系统。

但是 event sourcing是完全和CQRS是相互垂直的,只是互相合适对方,CQRS并不一定需要event sourcing, 而实现event sourcing并不意味着我们在实现CQRS.

作者认为event sourcing对于他来说门槛很高。


误区2:CQRS需要一个最终一致的读存储
这其实不必,你可以采取即时一致读取(关系数据库),也就是说,当你的命令成功执行后,你的读存储能够立即更新。(最终一致和即时一致是的区别见CAP原理和BASE思想)

对于很多遗留系统,切换到最终一致性会导致系统失火,特别是你急于切入异步模型。相反,你可以逐步过渡,比如哪些页面用户不希望立即看到结果,将之实现异步(如密码已经发到你信箱等功能)。

@clonalman 关于CQRS的由来阐述比我更准确些,业务建模: CQRS是鸡肋?,上面我是纯从性能优化角度来看待CQRS的。

下面继续几个CQRS的误区译文:
误区三: CQRS需要总线bus/队列queues/异步消息

你只要在基础架构上分离命令和查询即可(banq注:命令即为写命令,查询为读,也就是读写分离,POST/GET分离),如果你真正需要最终一致性(eventually consistent ),你才可能需要总线。(banq注:总线是在读存储和写存储之间同步数据,如果数据量不大,甚至可以拷贝整个数据库即可,如mysql的data文件全部拷贝过去,我经常这么干,这比总线编程要简单有效)。

最终一致性是由业务来决定的,这会直接影响用户的感受和体验,如果你试图用最终一致性模型来模拟即时一致性(immediate consistency ),那么你可能犯错了。

误区四:命令是fire-and-forget类型的
(banq注:fire-and-forget类型是一种不需要返回结果的命令,见多个消费者都有返回值时,报错)

在你的业务系统中到底真正有多少命令是发出后就不会忘记,不需要返回结果呢?你至少需要同步确认命令已经接受处理了,如果你发出一个异步命令,如何通知用户?Email,这个是否合适呢?

对于命令请求到充分执行这个请求有比较重要的两个事情:

接受请求
执行请求
接受请求应当是同步的,执行则是不必,但是对于如果实现异步模型执行,我们必须知道请求路由关联,这样才能应用那些流程过程式的应用workflows/processes/sagas。


误区五:读模型应当以最终一致性方式实现

只有当业务需要时才可以,你是如何发现有这个需要,不断探寻提问?需要反思我们为什么需要最终一致性,反寻踪迹到业务需求中,业务需求是一个没有计算机的世界,人工世界是一个易于出错 异步和主并行的世界,我们只是需要匹配我们系统到现实是如何工作的,不必要要和人工世界匹配(banq注:财务电算化建模中一个大的误区就是模型模拟手工帐,手工帐有一个单据,那么我们就建立一个单据模型,这是神马逻辑?)

banq注:关于现实世界是如何工作的,这个还是需要人来挖掘,盲人摸象各有各观点,即时一致性的人看到的都是即时一致性,所以,关系数据库模型才会大规模使用,也有人认为弱一致性在现实世界中到处存在,说现实世界是即时一致性的人都是用技术架构误导你们。

误区六:CQRS能够逃脱一致性问题消除并发冲突

其实不对的,我们已经看到CQRS/ES(Event Sourcing)系统野蛮扩张,主要是为了逃避命令并发问题,分散请求到单个区域,不错,所以命令集中在一个区域执行会有并发问题,但是这会变相导致进行一个序列号事务隔离级别的顺序模型。

在读取端,你可以让事件处理器幂等(反复执行同一个结果),但是如果乱序事件发生怎么办?

分散事件处理是简单,但是这只是在逃避并发。

误区七:CQRS很容易

如果它真的容易,我们就不会看到很多人因为失败尝试放弃CQRS,CQRS并没有简化你对业务的理解,只有你理解了业务才更容易,否则只会建立一个错误方向的CQRS。

CQRS替代遗留系统会让事情变得困难,重写几乎不可能,但是你可以在合适的角度冒不太大风险实施(banq注:正如我建议使用Spring的用户只要在某个部分使用Jdonframework即可。)