对 CQRS 的一次批判性思考

  • Jan Stenberg
  • 邵思华

2015 年 7 月 23 日

话题:架构语言 & 开发

在今年的 NDC 大会上,Udi Dahan进行了一场有关 CQRS 的演讲。他在演讲中提到,如果从更广义的架构角度来看,除了命令与查询职责分离(CQRS)之外,还有其它类似的架构风格可以选择,例如事件驱动架构(EDA)与发布 - 订阅架构。而在某些场景中,使用传统的数据库技术也能够解决同样的问题,并且要简便的多。Udi Dahan 的想法是应当多考虑一些应用 CQRS 的不同方式。而在确实需要 CQRS 架构的场合,他认为也存在着一种可替代的方式能够实现 CQRS 的大部分目标,同时与传统的 CQRS 相比其可变部分更少。

Dahan 与Greg Young都可称得上是位于 CQRS 架构最前沿的专家,在他与客户进行交流的过程中,他总是察觉到一点,即可伸缩性是应用 CQRS 架构的主要理由之一。在进行大量的读操作时,如果将读取与写入操作进行分离,将能够提高查询的可伸缩能力。但 Dahan 也提示道,现在已经可以通过某些技术实现读写分离,并且只需极小的代码改动,甚至是零改动,他认为开发者们应当注意到这种技术的存在。另一方面,Dahan 认为,由于应用 CQRS 这一主题已经在全球范围内进行了多次演讲,在社区中无意中形成了一种“必须”使用 CQRS 的压力,这也是人们应用 CQRS 架构的原因之一。

其实对于读取密集型应用环境来说,可以通过另一种途径实现可伸缩性,即主 / 从复制。数据的修改全部发生在主节点上,数据将同步到一个或多个进行读取的从节点上。这种方式已经存在很久了,并且在许多数据库机制中都已经得到了完整的支持。但 Dahan 也将它定义为一种 CQRS 架构,因为它确实做到了读写分离。他也提到,如果在生成查询结果时需要进行复杂的连接操作,那么这种数据复制机制无法解决由此引起的延迟问题,但至少这种机制能够通过加入更多的从节点克服数据吞吐量方面的问题。

Dahan 还推荐了一种用于处理可伸缩性问题的解决方案,即数据分片、或数据库分区。这种方式将某张表中的数据切分到数据库的多个实例中,并通过某个字段在实例之间进行分布,例如客户的名称。这种方式的一大优点在于能够设置多个主节点,因此可以对命令与查询两方面同时进行扩展。而许多数据库技术本身就支持分区能力,只需一些非常简单的代码就可以实现。Dahan 同时提到,在使用多个主节点的情况下无法支持跨记录的事务,因此就像对解决方案领域所进行的分区一样,对业务领域也要进行某种程度的划分。

Dahan 认为在某一种场景下是非常适合应用 CQRS 架构的,即具有高竞争性的业务领域。在这种领域中的负载非常大,而且具有高度的局域性。一旦出现并发异常就可能导致大多数事务操作开始失败、无法释放数据库连接、阻塞用户请求等一系列问题,产生巨大的系统瓶颈。这种测试用例在进行性能测试时经常会被忽略,一般在进行可伸缩性测试时,通常会针对多条记录创建大量的用户请求,而不是让所有的请求都集中于少数几条记录上。即使完美地实现了 CQRS 架构,这种瓶颈依然会出现在数据查询的场景,由于后台的进程不能快速地完成更新操作,导致查询模型总是无法及时获得最新的数据。

对于这种高竞争性的问题,Dahan 的解决方案是转为使用一种基于事件的模型,其数据模型是非阻塞式的、并且是只增的(append only),这就意味着该模型不会受到竞争性的影响。但这种方案可能会带来一个问题,因为它影响了某些业务规则。打个比方,某条业务规则规定只有在订单中的产品都有足够的库存时才允许接受订单。而这种方案则要求在业务处理上提供更高的灵活性,甚至可能会对需求产生变更。拿这个订单的例子来说,业务流程应当允许在不检查库存的情况下直接接受这个订单。而如果在处理订单时发现其中有某些产品库存不足,需要由业务人员决定如何处理这种情况。

查看英文原文:A Critical Look at CQRS

架构语言 & 开发