明天就是大年三十了,今天在家有空,想集中整理一下CQRS架构的特点以及相比传统架构的优缺点分析。先提前祝大家猴年新春快乐、万事如意、身体健康!
最近几年,在DDD的领域,我们经常会看到CQRS架构的概念。我个人也写了一个ENode框架,专门用来实现这个架构。CQRS架构本身的思想其实非常简单,就是读写分离。是一个很好理解的思想。就像我们用MySQL数据库的主备,数据写到主,然后查询从备来查,主备数据的同步由MySQL数据库自己负责,这是一种数据库层面的读写分离。关于CQRS架构的介绍其实已经非常多了,大家可以自行百度或google。我今天主要想总结一下这个架构相对于传统架构(三层架构、DDD经典四层架构)在数据一致性、扩展性、可用性、伸缩性、性能这几个方面的异同,希望可以总结出一些优点和缺点,为大家在做架构选型时提供参考。
前言CQRS架构由于本身只是一个读写分离的思想,实现方式多种多样。比如数据存储不分离,仅仅只是代码层面读写分离,也是CQRS的体现;然后数据存储的读写分离,C端负责数据存储,Q端负责数据查询,Q端的数据通过C端产生的Event来同步,这种也是CQRS架构的一种实现。今天我讨论的CQRS架构就是指这种实现。另外很重要的一点,C端我们还会引入Event Sourcing+In Memory这两种架构思想,我认为这两种思想和CQRS架构可以完美的结合,发挥CQRS这个架构的最大价值。
数据一致性传统架构,数据一般是强一致性的,我们通常会使用数据库事务保证一次操作的所有数据修改都在一个数据库事务里,从而保证了数据的强一致性。在分布式的场景,我们也同样希望数据的强一致性,就是使用分布式事务。但是众所周知,分布式事务的难度、成本是非常高的,而且采用分布式事务的系统的吞吐量都会比较低,系统的可用性也会比较低。所以,很多时候,我们也会放弃数据的强一致性,而采用最终一致性;从CAP定理的角度来说,就是放弃一致性,选择可用性。
CQRS架构,则完全秉持最终一致性的理念。这种架构基于一个很重要的假设,就是用户看到的数据总是旧的。对于一个多用户操作的系统,这种现象很普遍。比如秒杀的场景,当你下单前,也许界面上你看到的商品数量是有的,但是当你下单的时候,系统提示商品卖完了。其实我们只要仔细想想,也确实如此。因为我们在界面上看到的数据是从数据库取出来的,一旦显示到界面上,就不会变了。但是很可能其他人已经修改了数据库中的数据。这种现象在大部分系统中,尤其是高并发的WEB系统,尤其常见。
所以,基于这样的假设,我们知道,即便我们的系统做到了数据的强一致性,用户还是很可能会看到旧的数据。所以,这就给我们设计架构提供了一个新的思路。我们能否这样做:我们只需要确保系统的一切添加、删除、修改操作所基于的数据是最新的,而查询的数据不必是最新的。这样就很自然的引出了CQRS架构了。C端数据保持最新、做到数据强一致;Q端数据不必最新,通过C端的事件异步更新即可。所以,基于这个思路,我们开始思考,如何具体的去实现CQ两端。看到这里,也许你还有一个疑问,就是为何C端的数据是必须要最新的?这个其实很容易理解,因为你要修改数据,那你可能会有一些修改的业务规则判断,如果你基于的数据不是最新的,那意味着判断就失去意义或者说不准确,所以基于老的数据所做的修改是没有意义的。
扩展性传统架构,各个组件之间是强依赖,都是对象之间直接方法调用;而CQRS架构,则是事件驱动的思想;从微观的聚合根层面,传统架构是应用层通过过程式的代码协调多个聚合根一次性以事务的方式完成整个业务操作。而CQRS架构,则是以Saga的思想,通过事件驱动的方式,最终实现多个聚合根的交互。另外,CQRS架构的CQ两端也是通过事件的方式异步进行数据同步,也是事件驱动的一种体现。上升到架构层面,那前者就是SOA的思想,后者是EDA的思想。SOA是一个服务调用另一个服务完成服务之间的交互,服务之间紧耦合;EDA是一个组件订阅另一个组件的事件消息,根据事件信息更新组件自己的状态,所以EDA架构,每个组件都不会依赖其他的组件;组件之间仅仅通过topic产生关联,耦合性非常低。
上面说了两种架构的耦合性,显而易见,耦合性低的架构,扩展性必然好。因为SOA的思路,当我要加一个新功能时,需要修改原来的代码;比如原来A服务调用了B,C两个服务,后来我们想多调用一个服务D,则需要改A服务的逻辑;而EDA架构,我们不需要动现有的代码,原来有B,C两订阅者订阅A产生的消息,现在只需要增加一个新的消息订阅者D即可。
从CQRS的角度来说,也有一个非常明显的例子,就是Q端的扩展性。假设我们原来Q端只是使用数据库实现的,但是后来系统的访问量增大,数据库的更新太慢或者满足不了高并发的查询了,所以我们希望增加缓存来应对高并发的查询。那对CQRS架构来说很容易,我们只需要增加一个新的事件订阅者,用来更新缓存即可。应该说,我们可以随时方便的增加Q端的数据存储类型。数据库、缓存、搜索引擎、NoSQL、日志,等等。我们可以根据自己的业务场景,选择合适的Q端数据存储,实现快速查询的目的。这一切都归功于我们C端记录了所有模型变化的事件,当我们要增加一种新的View存储时,可以根据这些事件得到View存储的最新状态。这种扩展性在传统架构下是很难做到的。
可用性可用性,无论是传统架构还是CQRS架构,都可以做到高可用,只要我们做到让我们的系统中每个节点都无单点即可。但是,相比之下,我觉得CQRS架构在可用性方面,我们可以有更多的回避余地和选择空间。
传统架构,因为读写没有分离,所以可用性要把读写合在一起综合考虑,难度会比较更大。因为传统架构,如果一个系统的高峰期的并发写入很大,比如为2W,并发读取也很大,比如为10W。那该系统必须优化到能同时支持这种高并发的写入和查询,否则系统就会在高峰时挂掉。这个就是基于同步调用思路的系统的缺点,没有一个东西去削峰填谷,保存瞬间多出来的请求,而必须让系统不管遇到多少请求,都必须能及时处理完,否则就会造成雪崩效应,造成系统瘫痪。但是一个系统,不会一直处在高峰,高峰可能只有半小时或1小时;但为了确保高峰时系统不挂掉,我们必须使用足够的硬件去支撑这个高峰。而大部分时候,都不需要这么高的硬件资源,所以会造成资源的浪费。所以,我们说基于同步调用、SOA思想的系统的实现成本是非常昂贵的。