第四部分:Akka集群与事件溯源,CQRS,发布/订阅,分布式数据
群集分片是一种重要的强大工具,用于维护actor系统之间的状态。但是让我们进一步考虑加入数据持久化和存储的场景,这时就需引入事件溯源和CQRS(命令查询与职责分离)。随着近来分布式系统使用率的提高,这些特性越来越受欢迎,在Akka中,同样通过持久化和微服务以另一种方式提供了这些特性。
事件溯源和CQRS
继续集群分片的例子,在上图中,Akka持久化用于确保如果我们丢失了节点且必须重建actor的时候,可以使用某种持久的存储来恢复状态。Akka持久化使用事件溯源(通过事件日志)来实现。
当一个命令进入,根据实体ID,通过集群分片,被路由到实现了Akka持久化的Actor。让我们还是来看看银行账户的例子,这种场景需要强一致性:在Akka持久化模式中,有一个负责持久化的(单元),我们只需告诉它:"这里有个存款"即可。
命令是请求做某事,发生在将来。而事件是已经发生的事情。存储的则是事实,已经完成的事情-这个账户接受存款,另外某个账户提取金额(转账的场景)。一旦事件已经持久化在事件日志中,实体再更新状态,增加或减少账户余额。
在图22中,使用了另一个称为Akka持久化查询的功能,它被视作CQRS的查询职责--另一种视角是写入侧和读取侧。写入侧是事件日志,读取侧是可查询的数据存储的地方。因为事件日志本身并不方便查询,所以事件会从事件日志被传播到读取侧。这个过程是非事务的,意味着没有单独的数据库事务来处理,因此需要某些操作来保证事件不会丢失。这个重要的功能内建在Akka持久化查询中了,读取侧会从事件日志中拉取事件,而不是由事件日志端推送事件到读取侧。
发布和订阅
Akka内置了发布和订阅功能来确保命令和事件被正确的处理。在这种情形下,涉及到更多的actor,包括中介actor,发布actor和订阅actor。
如图23,集群中每个节点都有一个中介Actor,有N个订阅actor注册了某个感兴趣的事件。然后由发布Actor来发布事件(例如发送消息). 发布者仅仅发送消息到本地的中介actor。中介actor知道集群在哪,也知道集群中其他节点的中介Actor在哪。
首先收到消息的中介Actor通知集群中其他同行:"这里发布了一个事件,需要人来处理它"。和集群分片中描述的轮询方式一样,每个中介Actor将消息发送给订阅者。这些处理由一系列集群内的消息通知,以及为订阅和发布设计的actor组成。开发者无需担心这些细节,因为本地的中介actor知道如何处理。
分布式数据
Akka持久化关注维护状态以实现强一致性,而Akka分布式数据实现了从集群任意节点的actor上发送最终一致性消息,且无需太多的协作。这是通过无冲突复制数据类型(CRDR)的概念来实现的。对counter,set,map和register这样的数据结构很有用,它们需要低延迟的高读取和写入可用性(分区容差)。
Akka分布式数据对于数据类型有一些限制。例如在最终一致性系统中读取操作可能返回过时的数据。Akka使用分布在集群的其他类型的actor来处理此类问题。在图24中,集群中每个节点都创建了复制actor(Replicator)。
图示中,有一个actor负责在集群中复制K值(K1, K2, K3),节点2还有一个负责更新的Actor。如果负责更新的Actor想要知道K3的值,它将发送一个消息给复制actor。负责更新的Actor不知道环境中的其他任何信息,只是发送消息给本地的复制actor。复制actor从K3得到值然后再回消息给负责更新的Actor。
图25中,情况有所变化,负责更新的Actor请求修改K3的值,但K3的新值必须复制到集群中的所有K3实例。负责更新的Actor发消息给负责复制的actor,而负责复制的actor知道集群内其他节点的同行,所以它转发更新消息给其他同行,由它们各自负责修改本地的K3值。
从开发人员的角度看,只需简单发送一个消息给本地的复制actor;剩下的事情会通过其他一些actor的动作自动完成,不需要额外手动编写代码。
总结
- 对于Reactive系统中的分布式数据层操作,Akka支持事件溯源和CQRS技术;
- Akka持久化模块和分布式数据模块提供了实现强一致性和最终一致性的机制;
- Akkaj集群允许一种"即发即忘"的发布/订阅范式,这是从支持分布式系统的底层细节抽象出来的。