1. 引言
微服务架构现在很热,到处可以看到各大互联网公司的微服务实践的分享总结。但是,我今天的分享和微服务没有关系,希望可以带给大家一些新的东西。
如果一定要说微服务和CQRS架构的关系,那我觉得微服务是一种边界思维,微服务的目的是为了从业务角度拆分(职责分离)当前业务领域的不同业务模块到不同的服务,每个微服务之间的数据完全独立,它们之间的交互可以通过SOA RPC调用(耦合比较高),也可以通过EDA 消息驱动(耦合比较低)。
1.1 基本概念
微服务架构和CQRS架构的关系:每个微服务内部,我们可以用CQRS/ES架构来实现,也可以用传统三次架构来实现。
1.1.1 聚合 VS 聚合根
首先,我们需要先理解DDD中的聚合、聚合根这两个概念。
聚合,它通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的最小原子单元。
聚合根,每个聚合都有一个根对象,根对象管理聚合内的其他子对象(实体、值对象);聚合之间的交互都是通过聚合根来交互,不能绕过聚合根去直接和聚合下的子实体进行交互。
上面的例子中,Car、Wheel、Position、Tire四个对象构成一个聚合,其中Car是聚合根;Customer也是聚合根,Customer不能直接访问Car下的Tire(子实体),而是只能通过聚合根Car来访问。
1.1.2 最终一致性
上面表达了一个关于聚合的一致性设计原则:聚合内的数据修改,是ACID强一致性的;跨聚合的数据修改,是最终一致性的。遵守这个原则,可以让我们最大化的降低并发冲突,从而最大化的提高整个系统的吞吐。
1.1.3 全局内存
In-Memory的意思是指整个系统中的所有的聚合根对象都活在内存。而不是像我们平时那样,用到的时候才从DB获取对象,然后再做修改,再保存回去。
在In-Memory的架构下,当要修改某个聚合根的状态时,它已经在内存,我们可以直接拿到该对象的引用,且框架会尽量保证聚合根对象的状态就是最新的。聚合根是在内存中的最小计算单元,每个聚合内部都封装了业务规则,并保证数据的强一致性。
上图我是挪用了之前比较或的LMAX架构中的一个图,表达的思想就是in-memory架构。其中Business Logic Processor就是中央业务逻辑处理器,内部承载了大量在机器内存中活着的聚合根对象;
1.1.4 事件溯源
-
不保存对象的最新状态,而是保存对象产生的所有事件;
-
通过事件溯源(Event Sourcing,ES)得到对象最新状态;
接下来,我们再来看一下什么是事件溯源。
一个对象从创建开始到消亡会经历很多事件,以前我们是在每次对象参与完一个业务动作后把对象的最新状态持久化保存到数据库中,也就是说我们的数据库中的数据是反映了对象的当前最新的状态。而事件溯源则相反,不是保存对象的最新状态,而是保存这个对象所经历的每个事件,所有的由对象产生的事件会按照时间先后顺序有序的存放在数据库中。可以看出,事件溯源的这种做法是更符合事实观的,因为它完整的描述了对象的整个生命周期过程中所经历的所有事件。
那么,事件到底如何影响一个领域对象的状态的呢?很简单,当我们在触发某个领域对象的某个行为时,该领域对象会先产生一个事件,然后该对象自己响应该事件并更新其自己的状态,同时我们还会持久化在该对象上所发生的每一个事件;这样当我们要重新得到该对象的最新状态时,只要先创建一个空的对象,然后将和该对象相关的所有事件按照事件发生先后顺序从先到后再全部应用一遍即可还原得到该对象的最新状态,这个过程就是所谓的事件溯源。
另一方面,因为是用事件来表示对象的状态,而事件是只会增加不会修改。这就能让数据库里的表示对象的数据非常稳定,不可能存在DELETE或UPDATE等操作。因为一个事件就是表示一个事实,事实是不能被磨灭或修改的。这种特性可以让领域模型非常稳定,在数据库级别不会产生并发更新同一条数据的问题。
1.1.5 事件溯源 VS 增删改查
-
CRUD:DB的记录可变,可以增删改查
-
ES:没有更新、删除,只有Append Event,不可变
通过上面这个图,大家应该可以更直观的理解事件溯源和传统CRUD思想的区别
1.1.5 Actor Model
Actor模型,这个概念大家应该都了解。Actor模型的核心思想是:
-
对象直接不会直接调用来通信,而是通过发消息来通信。
-
每个Actor都有一个Mailbox,它收到的所有的消息都会先放入Mailbox中,然后Actor内部单线程处理Mailbox中的消息。从而保证对同一个Actor的任何消息的处理,都是线性的,无并发冲突。从全局上来看,就是整个系统中,有很多的Actor,每个Actor都在处理自己Mailbox中的消息,Actor之间通过发消息来通信。
Akka框架就是实现Actor模型的并行开发框架,并且Akka框架融入了聚合、In-Memory、Event Sourcing这些概念。
Actor非常适合作为DDD聚合根。Actor的状态修改是由事件驱动的,事件被持久化起来,然后通过Event Sourcing的技术,还原特定Actor的最新状态到内存。
上图表达的是事件驱动的架构的思想。Node表示节点,每个节点负责处理逻辑;Event表示消息,节点之间通过消息进行通信。消息通过分布式消息队列如RocketMQ,Equeue进行通信。
2. 架构思想
2.1 事件驱动的核心思想
-
不同于SOA架构,EDA架构是pub-sub模式;Node1处理完逻辑后产生消息,Node2订阅消息并进行处理,Node1不知道Node2的存在;
-
最终一致性原则,Node1,Node2之间的数据一致性通过MQ最终保证一致;
-
如何保证最终一致性(消息链不会断开):
A. MQ保证消息不丢;
B. 任何一个Node要保证自己完全处理完后才发送ACK给MQ;
C. 每个Node做到对任何消息处理的幂等性;
-
整个架构具有所有分布式MQ所带来的优点:如异步解耦、削峰、降低整个系统的整体部署成本;
2.2 分布式消息队列
上图是一个面向Topic的分布式MQ的逻辑架构图,采用这种架构的MQ有:Kafka,RocketMQ,EQueue。
-
Producer发送消息到某个Topic的某个Queue;
-
消息都存储在Broker上;
-
Consumer从Broker拉取消息进行消费,并支持消费者负载均衡;