使用Kafka Streams构建事件源系统的经验

在基辅召开的JEEConf会议上,Amitay Horwitz描述了他和他的团队如何实施事件溯源的发票系统,他们在生产2年半后遇到的挑战以及他们如何使用Kafka Streams实施新设计。

Wix的软件工程师Horwitz 于2015年开始与他的团队一起开展新的发票服务,目的是帮助他们的客户管理发票并在线接收付款。在设计新服务时,他们希望创建一个小而简单的库,该库非侵入式,维护数据完整性,并且能够轻松添加自定义视图。为了实现所有目标,他们决定使用事件源架构来实现服务。

他们也遇到了问题,客户有时无法看到新创建的发票,因为从写入到读取是最终一致性。

但他们最大的问题是重建视图。确保在没有事件进入的情况下触发重建,事实证明它比预期的更复杂,特别是在具有来自各种服务器的事件的分布式环境中。这些问题使Horwitz寻求替代架构,同时保持事件溯源的好处。

Horwitz将Kafka描述为一个复制的,容错的,分布式附加日志。它通常用于pub-sub或队列,但他指出它可以做更多。Kafka中的基本结构是一个分区的主题,一个逻辑队列。生产者将根据消息中的key将消息推送到每个分区,然后消费者可以使用这些消息。对于事件源系统至关重要的两个重要特性是在单个分区内维护消息之间的排序,并且消息即使在消耗之后也可以存储

Kafka Streams将流处理带给了Kafka。它有两个主要的抽象:

1. Horwitz认为流中的数据流是一种无限有序和可重放的不可变数据序列,这使得它对于事件源系统来说很有趣。保证了顺序。

2. 能在静态数据中看到Table,表Table存储聚合数据的某个时间点状态视图,该视图在接收到新消息时更新。

在使用Kafka的发票服务的新设计中,有一个快照状态存储,用于保存每个聚合的当前状态,从命令流接收命令后,命令处理程序Handler会根据聚合ID从快照状态存储中读取相应聚合的当前状态;然后,该处理程序Handler确定命令是成功被执行还是失败了,并通过结果流返回结果;如果命令执行成功,则会创建事件并将其发送到事件存储库;接下来,读取这边系统查询有新事件的流,并将状态存储中的聚合更新为其新状态(banq注:注意更新状态在读取这边,不是写入那边),他指出,命令处理程序Hanlder逻辑可以用非常简洁和声明的方式编写,在他的示例中只有60行Scala代码。

在新的架构中,Kafka位于中心,微服务与Kafka通信,彼此之间也通过Kafka进行通信。他们还可以在创建分析报告时将信息推送到Kafka或提取信息,总之,Horwitz指出,新设计给了他们几个优势:

1. 一个简单的声明式系统
2. 最终的一致性现在已被接受并优雅地处理
3. 添加或更改视图很容易
4. 使用Kafka提高了可扩展性和容错能力

新设计仍处于评估阶段,尽管他们在生产中大量使用卡夫卡。他指出,有人声称Kafka不适合CQRS或事件来源系统,但他认为,如果能进行权衡,Kafka可以被利用。如果使用不同的客户端属性保存页面视图事件,就可以根据该事件信息轻松创建聚合,这是一种事件溯源形式,他认为Kafka非常适合。

通过使用聚合的标识作为Kafka的分区键key,同一聚合的所有命令将最终位于命令主题的同一分区中,并将在单个线程中(单写)按顺序处理。这样,在前一个生成所有下游事件之前不会处理任何命令,并且Horwitz指出这将创建强大的一致性保证。

https://www.infoq.com/news/2018/07/event-sourcing-kafka-streams

https://www.youtube.com/watch?v=b17l7LvrTco