-

浅谈CQRS

大背景

  • 基于事件驱动的微服务架构

在微服务的大背景下,服务拆分、服务通信、服务事务管理成为需要解决和研究的课题。服务的查询是其中一个,服务拆分后,联合查询成为一大问题。

为什么

服务拆分后的通常查询措施

在单体应用中,当联合查询不足以满足业务数据需求的时候,也会用到这种模式。在微服务中没有了“跨库联合查询”,将API组合模式搬过来就显得“理所当然”了

组合的开销成本

数据组合需要调用多个服务,网络、数据集操作等多因素造成的开销大大增加查询的耗时。这时候spring reactive stack就排上了一定用用场,将编程模式从同步转到异步。

组合的职责问题

服务拆分后,各服务职责单一几乎已经成为“政治正确”,但是往往查询并不是单个服务的查询,而是queryXxxDetail()这样多服务的数据整合。

个人认为现阶段使用的API Gateway,严格来讲并不是所谓的API组合模式,从结果来看确实是组合API,但它却将组合的操作下放到了service,使service不再“纯粹”。

组合的数据一致性

这里指查询数据一致性

通过组合API模式查询的服务间缺少事务支持,进行多数据库查询会出现部分数据不一致的情况。虽然办法总比困难多,但这不是写业务的程序员应该考虑的事情,也不应该徒增代码的复杂度。

是什么

什么是CQRS(命令查询职责隔离)

理解概念就先了解定义
CQRS(Command Query Responsibility Segregation)有四个字母,分开说

C和Q

C和Q的具体


C和Q的抽象

这里command翻译成指令或许更好理解一点,综上就是:

  • command:改变状态,但不返回结果
  • query:返回结果,但不改变状态

然后放在应用程序里,对应过来就是CUDR。(当然会有改变状态并返回结果的,如保持实体并返回实体ID)

R&S

字面意思,就是C(Command)和Q(Query)的职责(Responsibility)隔离(Segregation),没什么需要强加的解释。

CQRS

左侧是服务的非CQRS版本,右侧是CQRS版本。CQRS将服务重构为命令端和查询端模块,这些模块具有独立的数据库

CQRS将原本的CRUD彻底分为CUDR(类似将Elasticsearch或Solr文本搜索数据库文本搜索查询,在应用程序层面再竖切一刀),即命令端模块查询端模块,同时将查询端数据库从物理层面分开,作为视图数据库,查询端模块订阅命令端模块事件并更新(同步)视图数据库,至于视图数据库用什么数据库取决于业务需求。(这就有点从数据仓库(DW)数据集市(DM)的意思)

  • 此处视图数据库(view database)并非数据库视图(database view)
    视图数据库(view database):对数据库职责的抽象
    数据库视图(database view):对数据库数据的抽象
  • R分离出来并不意味着查询模块部分没有CUD(数据持久化)

怎么样

  • 架构隔离和可扩展性

视图数据库并没有指定具体数据库,完全取决于业务需求,所以就可以“为所欲为”了。就算修改视图结构也不会影响原始数据

  • 数据查询更高效和简单

CUDR分开后,一方面解决在负载不均的情况(通常大于),R分离出来后,视图数据库可以自由缩放;另一方面数据整合已经在查询端模块事件处理程序完成,查询和数据的获取就变得简单多了。

  • 事务数据最终一致性

视图数据库支持事务模式的前提下,事件处理程序持久化的数据最终是一致的。(保证事件有序)

最后

“没有一项技术可以被称为‘银弹’”。从大前提(基于事件)的部署,到如何设计CQRS视图的细节都需要考虑细致,还有团队和成本,到最终的落地。架构设计应适应业务发展,杀鸡用牛刀,杀前还得磨刀,杀完把鸡扔了,却只取了个鸡蛋,那就舍本求末乱套了。

参考

  • Bertrand Meyer.Object-Oriented Software Construction 2nd Edition[M].Prentice Hall,1997:751
  • Bertrand Meyer. Eiffel: a language for software engineering,2014:24
  • Chris Richardson.Microservice Patterns[M].Manning Publications,2019:229
  • (美)克里斯·理查森(Chris Richardson),喻勇.微服务架构设计模式[M].机械工业出版社,2019.4:212-243