CQRS基本概念 | event sourcing | CQRS | axon | EdisonXu的技术分享

标签: | 发表时间:2021-08-01 09:26 | 作者:
出处:http://edisonxu.com

在研究微服务的过程中,跨服务的操作处理,尤其是带有事务性需要统一commit或rollback的,是比较麻烦的。本系列记录了我在研究这一过程中的心得体会。
本篇主要就以下几个问题进行介绍:

  • 微服务中的一个大难题
  • DDD中的几个基本概念
  • 什么是EventSourcing?
  • 什么是CQRS?
  • EventSourcing和CQRS的关系?
  • CQRS/ES怎么解决微服务的难题?

微服务中的一个大难题

微服务架构已经热了有两年了,而且目测会越来越热,除非有更高级的架构出现。相关解释和说明,网上一搜一大堆,我这里就不重复了。一句话概括:
微服务将原来的N个模块,或者说服务,按照适当的边界,从单节点划分成一整个分布式系统中的若干节点上。

原来服务间的交互直接代码级调用,现在则需要通过以下几种方式调用:

  • SOA请求
  • RPC调用
  • ED(EventDriven)事件驱动

前面两种就比较类似,都属于直接调用,好处明显,缺点是请求者必须知道被请求方的地址。现在一般会提供额外的机制,如服务注册、发现等,来提供动态地址,实现负载和动态路由。目前大多数微服务框架都走的这条路子,如当下十分火热的SpringCloud等。
事件驱动的方式,把请求者与被请求者的绑定关系解耦了,但是需要额外提供一个消息队列,请求者直接把消息发送到队列,被请求者监听队列,在获取到与自己有关系的事件时进行处理。主要缺点主要有二:
1) 调用链不再直观;
2) 高度依赖队列本身的性能和可靠性;

但无论是哪种方式,都使得传统架构下的事务无法再起到原先的作用了。
事务的作用主要有二:

  • 统一结果,要么都成功,要么都失败
  • 并发时保证原子性操作

在传统架构下,无论是DB还是框架所提供的事务操作,都是基于同线/进程的。在微服务所处的分布式框架下,业务操作变成跨进程、跨节点,只能自行实现,而由于节点通信状态的不确定性、节点间生命周期的不统一等,把实现分布式事务的难度提高了很多。
这就是微服务中的一个大难题。

DDD中的几个基本概念

在进一步深入前,必须要了解几个基本概念。这些基本概念在EventSourcing和CQRS中都会用到。

Aggregate

聚合。这个词或许听起来有点陌生,用集合或者组合就好理解点。

A DDD aggregate is a cluster of domain objects that can be treated as a single unit.
—— Martin Fowler

以下图为例

车、轮子、轮胎构成了一个聚合。其中车是聚合根( AggregateRoot)
Aggregate有两大特征:

  • 明确的边界
  • AggregateRoot

具体来说, Aggregate存在于两种形式:

  1. 一个单独的对象;
  2. 一组相互有关联的对象,其中一个作为 ROOT,外部只能通过 AggregateRoot对这组对象进行交互;
    这里Customer不能直接访问Car下面的Tire,只能通过聚合根Car来访问。

什么是EventSourcing?

不保存对象的最新状态,而是保存对象产生的所有事件。
通过事件回溯(Event Sourcing, ES)得到对象最新的状态

以前我们是在每次对象参与完一个业务动作后把对象的最新状态持久化保存到数据库中,也就是说我们的数据库中的数据是反映了对象的当前最新的状态。而事件溯源则相反,不是保存对象的最新状态,而是保存这个对象所经历的每个事件,所有的由对象产生的事件会按照时间先后顺序有序的存放在数据库中。当我们需要这个对象的最新状态时,只要先创建一个空的对象,然后把和改对象相关的所有事件按照发生的先后顺序从先到后全部应用一遍即可。这个过程就是事件回溯。

因为一个事件就是表示一个事实,事实是不能被磨灭或修改的,所以ES中的事件本身是不可修改的(Immutable),不会有DELETE或UPDATE操作。
ES很明显先天就会有个问题——由于不停的记录Event,回溯获得对象最新状态所需花的时间会与事件的数量成正比,当数据量大了以后,获取最新状态的时间也相对的比较长。
而在很多的逻辑操作中,进行“写”前一般会需要“读”来做校验,所以ES架构的系统中一般会在 内存中维护一份对象的最新状态,在启动时进行”预热”,读取所有持久化的事件进行回溯。这样在读对象——也就是 Aggregate的最新状态时,就不会因为慢影响性能。
同时,也可以根据一些策略,把一部分的Event合集所产生的状态作为一个snapshot,下次直接从该snapshot开始回溯。
既然需要读,就不可避免的遇到并发问题。
EventSourcing要求对回溯的操作必须是原子性的,具体实现可参照Actor模型。

Actor Model

ActorModel的核心思想是与对象的交互不会直接调用,而是通过发消息。如下图:

每一个Actor都有一个Mailbox,它收到的所有的消息都会先放入Mailbox中,然后Actor内部单线程处理Mailbox中的消息。从而保证对同一个Actor的任何消息的处理,都是线性的,无并发冲突。整个系统中,有很多的Actor,每个Actor都在处理自己Mailbox中的消息,Actor之间通过发消息来通信。
Akka框架就是实现Actor模型的并行开发框架。Actor作为DDD聚合根,最新状态是在内存中。Actor的状态修改是由事件驱动的,事件被持久化起来,然后通过Event Sourcing的技术,还原特定Actor的最新状态到内存。
另外,还有 Eventuate,两者的作者是同一人,如果对Akka和Eventuate的区别感兴趣的话,可以参照我翻译的一篇文章 (译)Akka Persistence和Eventuate的对比

什么是CQRS?

CQRS架构全称是 Command Query Responsibility Segregation,即命令查询职责分离,名词本身最早应该是 Greg Young提出来的,但是概念却很早就有了。
本质上,CQRS也是一种读写分离的机制,架构图如下:

CQRS把整个系统划分成两块:

  • Command Side写的一边
    接收外部所有的Insert、Update、Delete命令,转化为 Command,每一个 Command修改一个 Aggregate的状态。 Command Side的命令通常不需要返回数据。注意:这种“写”操作过程中,可能会涉及“读”,因为要做校验,这时可直接在这一边进行读操作,而不需要再到 Query Side去。
  • Query Side读的一边
    接受所有查询请求,直接返回数据。

由于C端与Q端的分离,两端各有一个自己的 Repository,可根据不同的特性选取不同的产品,比如C端用RMDB,而Q端选用读取速度更快的NoSQL产品。

CQRS适用的场景

使用了CQRS架构,由于读写之间会有延迟,就意味着系统的一致性模型为最终一致性(Eventual Consistency),所以CQRS架构一般用于读比写大很多的场景。
注意:
CQRS并不像SOA、EDA(EventDrivenArchitecture)属于顶级架构,它有自己的局限性,并不适合于一切场景。有些天然适合于CRUD的系统,在评估CQRS所带来的好处与坏处后,认为利大于弊再选取CQRS。所以,通常CQRS只作为一个大系统中某部分功能实现时使用。

EventSourcing和CQRS的关系

从前面的介绍,应该可以发现两者其实并没有直接的关系,但是EventSourcing天然适合CQRS架构的C端的实现。
CQRS/ES整合在一起的架构,优缺点如下:

优点

  • 记录了数据变化的完整过程,便于BI分析
  • 可以有效解决线上的数据问题,重演一遍,就可以找到问题所在
  • 可以随时将系统还原到任何一个时间点
  • 正确的实施后,天然适合并发场景

缺点

  • 事件数量巨大,造成存储端成本上升
  • 通过回溯重演获取 Aggregate状态时,如果相关事件过多,需要提前“预热”
  • 事件本身的内容被重构后,必须兼容以前的事件
  • 事件驱动对传统思维的改变,需要适应
  • 实施门槛高,需要成熟框架或中间件支撑

CQRS/ES怎么解决微服务的难题?

我们先把实现微服务事务中的主要难点列出来,然后看用CQRS/ES是怎么一一解决的。

  1. 必须自己实现事务的统一commit和rollback;
    这个是无论哪一种方式,都必须面对的问题。完全逃不掉。在DDD中有一个叫 Saga的概念,专门用于统理这种复杂交互业务的,CQRS/ES架构下,由于本身就是最终一致性,所以都实现了 Saga,可以使用该机制来做微服务下的transaction治理。

  2. 请求幂等
    请求发送后,由于各种原因,未能收到正确响应,而被请求端已经正确执行了操作。如果这时重发请求,则会造成重复操作。
    CQRS/ES架构下通过AggregateRootId、Version、CommandId三种标识来识别相同command,目前的开源框架都实现了幂等支持。

  3. 并发
    单点上,CQRS/ES中按事件的先来后到严格执行,内存中 Aggregate的状态由单一线程原子操作进行改变。
    多节点上,通过EventStore的broker机制,毫秒级将事件复制到其他节点,保证同步性,同时支持版本回退。(Eventuate)

CQRS/ES如何与微服务架构结合

结合的方式很简单,就是把合适的服务变成CQRS/ES架构,然后提供一个统一的分布式消息队列。
每个服务自己内部用的C或Q的Storage完全可以不同,但C端的Storage尽量使用同一个,例如MongoDB、Cansandra这种本身就是HA的,以保证可用性。同时也可以避免大数据分析导数据时需要从不同的库导。
目前,相对成熟的CQRS/ES可用框架有:

名称 地址 语言 文档 特点
AxonFramework http://www.axonframework.org Java 比较全,更新及时 目前作者正在开发与SpringCloud的相关集成
Akka Persistence http://akka.io/ Scala(也有.Net版) 文档全 相对成熟,性能较强
Eventuate http://eventuate.io Scala 文档较少 与Akka同源,在Akka基础上对分布式相关功能进行了增强,提供AWS上的SaaS
ENode https://github.com/tangxuehua/enode C# 博客 来自微软的国人原创
Confluent https://www.confluent.io Scala 文档较少 不仅仅只是CQRS/ES,是整个一套基于kafka的高性能微服务产品,提供商业版
本文由 EdisonXu - 徐焱飞创作,采用 CC BY 4.0 CN协议进行许可。 可自由转载、引用,但需署名作者且注明文章出处。
本文链接为 http://edisonxu.com/2017/03/23/hello-cqrs.html

相关 [cqrs 概念 event] 推荐:

CQRS基本概念 | event sourcing | CQRS | axon | EdisonXu的技术分享

- -
在研究微服务的过程中,跨服务的操作处理,尤其是带有事务性需要统一commit或rollback的,是比较麻烦的. 本系列记录了我在研究这一过程中的心得体会. 本篇主要就以下几个问题进行介绍:. 什么是EventSourcing. EventSourcing和CQRS的关系. CQRS/ES怎么解决微服务的难题.

Axon Saga的使用 | event sourcing | CQRS | axon | DDD | EdisonXu的技术分享

- -
在上一篇里面,我们正式的使用了CQRS模式完成了AXON的第一个真正的例子,但是细心的朋友会发现一个问题,创建订单时并没有检查商品库存. 库存是否足够直接回导致订单状态的成功与否,在并发时可能还会出现超卖. 当库存不足时还需要回滚订单,所以这里出现了复杂的跨Aggregate事务问题. Saga就是为解决这里复杂流程而生的.

微服务数据一致性的演进:SAGA,CQRS,Event Sourcing的由来和局限-InfoQ

- -
讲微服务数据一致性的文章,网上比较多. 此前 EAWorld 与发过几篇,包括《 微服务架构下的数据一致性保证(一)》、《 微服务架构下的数据一致性保证(二)》、《 微服务架构下的数据一致性保证(三):补偿模式》,以及《 使用消息系统进行微服务间通讯时,如何保证数据一致性》. 本篇文章在我看来,是从一个纵向的维度把相关的一致性概念的演进过程,讲的比较清晰,简单的逻辑是这样的:.

什么是 Event Loop?

- - 阮一峰的网络日志
Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制. JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题. Aaron Cois的 《Understanding The Node.js Event Loop》,解释什么是Event Loop,以及它与JavaScript语言的单线程模型有何关系.

JS的event对象--知识点总结

- - CSDN博客推荐文章
Event描述:event代表事件的状态,例如触发event对象的元素、鼠标的位置及状态、按下的键等等. 需要注意的是:event对象只在事件发生的过程中才有效. event的某些属性只对特定的事件有意义. 比如,fromElement 和 toElement 属性只对 onmouseover 和 onmouseout 事件有意义.

80%人都理解错误的 Event Loop

- - IT瘾-dev
本文的目的在于,一次性推翻80%人构建好的关于Event Loop的知识体系和一次性的完整的理解Nodejs(13以上)和浏览器中的Event Loop. 首先进行一下基础概念的划分. mouseover(之类的事件). Web API大部分异步返回方法(XHR,fetch). 逗知识:其实setTimeout和setInterval也属于Web API.

对CQRS/EventSourcing架构的思考

- -
开始之前想先说一下微服务架构和CQRS架构的区别和联系. 微服务架构现在很热,到处可以看到各大互联网公司的微服务实践的分享总结. 但是,我今天的分享和微服务没有关系,希望可以带给大家一些新的东西. 如果一定要说微服务和CQRS架构的关系,那我觉得微服务是一种边界思维,微服务的目的是为了从业务角度拆分(职责分离)当前业务领域的不同业务模块到不同的服务,每个微服务之间的数据完全独立,它们之间的交互可以通过SOA.

CQRS——命令与查询职责分离【翻译】

- - 企业架构 - ITeye博客
翻译自大牛Martin Fowler的CQRS. 原文地址:http://martinfowler.com/bliki/CQRS.html. CQRS代表着命令查询责任分离. 我第一次听到这个模式是从Greg Young那里. 这个模式的核心就是你在更新数据时所使用的模型可以与读取和查询时所用的数据模型不一样.

JavaScript 运行机制详解:再谈Event Loop

- - 阮一峰的网络日志
一年前,我写了一篇 《什么是 Event Loop. 》,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲 《Help, I'm stuck in an event-loop》. 这才尴尬地发现,自己的理解是错的. 我决定重写这个题目,详细、完整、正确地描述JavaScript引擎的内部运行机制.

使用Docker+springboot+springcloud+axon 构建CQRS/ES的微服务架构_quguang65265的博客-CSDN博客

- -
在过去几年中,软件架构的变化步伐迅速发展. DevOps,Microservices和Containerisation等新方法已成为热门话题,采用率迅速提高. 在这篇文章中,我想向您介绍一个我一直在研究的微服务项目,它结合了过去几年中两个突出的架构进步:命令和查询责任分离(CQRS)和容器化. 在第一部分中,我将向您展示使用容器分发和运行多服务器微服务应用程序是多么容易.