在过去一年里,我一直是数据流团队的一员,负责Wix事件驱动的消息传递基础设施(基于 Kafka)。有超过 1400 个微服务使用这个基础设施。在此期间,我实现或目睹了事件驱动消息传递设计的几个关键模式,这些模式有助于创建一个健壮的分布式系统,该系统可以轻松地处理不断增长的流量和存储需求。
1.消费与投影
针对那些使用非常广泛、已经成为瓶颈的服务
当有遗留服务存储着大型域对象的数据,这些数据使用又非常广泛,使得该遗留服务成为瓶颈时,此模式可以提供帮助。
在 Wix,我们的 MetaSite 服务就面临着这样的情况,它为 Wix 用户创建的每个站点保存了大量的元数据,比如站点版本、站点所有者以及站点上安装了哪些应用程序——已安装应用上下文(The Installed Apps Context.)。
这些信息对于 Wix 的许多其他微服务(团队)很有价值,比如 Wix Stores、Wix booking、Wix Restaurants 等等。这个服务被超过 100 万 RPM 的请求轰炸,它们需要获取站点元数据的不同部分。
从服务的各种 API 可以明显看出,它处理了客户端服务的太多不同的关注点。
MetaSite 服务处理大约 1M RPM 的各类请求
我们想要回答的问题是,如何以最终一致的方式将读请求从该服务转移出来?
使用 Kafka 创建“物化视图”
负责这项服务的团队决定另外创建一个服务,只处理 MetaSite 的一个关注点——来自客户端服务的“已安装应用上下文”请求。
-
首先,他们将所有数据库的站点元数据对象以流的方式传输到 Kafka 主题中,包括新站点创建和站点更新。一致性可以通过在 Kafka Consumer 中进行 DB 插入来实现,或者通过使用CDC产品(如Debezium)来实现。
-
其次,他们创建了一个有自己数据库的“只写”服务(反向查找写入器),该服务使用站点元数据对象,但只获取已安装应用上下文并写入数据库。即将站点元数据的某个“视图”(已安装的应用程序)投影到数据库中。
已安装应用上下文消费与投影
-
第三,他们创建了一个“只读”服务,只接受与已安装应用上下文相关的请求,通过查询存储着“已安装应用程序”视图的数据库来满足请求。
读写分离
效果
-
通过将数据以流的方式传输到 Kafka,MetaSite 服务完全同数据消费者解耦,这大大降低了服务和 DB 的负载。
-
通过消费来自 Kafka 的数据,并为特定的上下文创建一个“物化视图”,反向查找写入器服务能够创建一个最终一致的数据投影,大幅优化了客户端服务的查询需求。
-
将读服务与写服务分开,可以方便地扩展只读 DB 副本和服务实例的数量,这些实例可以处理来自全球多个数据中心的不断增长的查询负载。
2.端到端事件驱动
针对简单业务流程的状态更新
请求-应答模型在浏览器-服务器交互中特别常见。借助 Kafka 和WebSocket,我们就有了一个完整的事件流驱动,包括浏览器-服务器交互。
这使得交互过程容错性更好,因为消息在 Kafka 中被持久化,并且可以在服务重启时重新处理。该架构还具有更高的可伸缩性和解耦性,因为状态管理完全从服务中移除,并且不需要对查询进行数据聚合和维护。
考虑一下这种情况,将所有 Wix 用户的联系方式导入 Wix 平台。
这个过程涉及到两个服务:Contacts Jobs 服务处理导入请求并创建导入批处理作业,Contacts Importer 执行实际的格式化并存储联系人(有时借助第三方服务