分布式架构EventSourcing& CQRS

微服务将原来的N个模块,或者说服务,按照适当的边界,从单节点划分成一整个分布式系统中的若干节点上。

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

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

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

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

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

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

什么是EventSourcing?

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

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

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


C端的命令的执行流程

客户端如(MVC Controller)发送命令通知系统做修改:

  1. 发送命令到分布式MQ;
  2. 然后命令的订阅者处理命令;
  3. 订阅者内部根据不同的命令调用不同的Command Handler进行处理;
  4. Command Handler内部根据命令所指定的聚合根ID从In-Memory内存中直接获取聚合根对象的引用,然后操作聚合根对象;
  5. 聚合根对象状态发生变化并产生事件;
  6. 框架负责自动持久化事件到Event Storage(简称EventStore);
  7. 框架负责将事件发布到Event MQ;
  8. Event订阅者订阅事件,然后调用对应的Event Handler进行处理,如更新Data Storage(保存了聚合根的最新状态,通常叫读库,ReadDB);

Q端的查询的执行流程

客户端如(MVC Controller)发出查询请求系统返回数据:

  1. 调用轻薄的Query Service,传如Query DTO;
  2. Query Service从读库进行查询并返回结果;

读库可以有很多种,依据我们的业务场景来选择:比如关系型DB、分布式缓存等NoSQL、搜索引擎,etc.

eventuate框架实现了CQRS&ES,正在研究过程中,以后会放上这个框架




你可能感兴趣的:(分布式架构EventSourcing& CQRS)