如何可靠重放Event Sourcing事件?关联实体场景高效重放方案问询
Event Sourcing的核心优势之一就是支持事件重放,当实体之间无关联时(例如blob存储、用户profile)重放运行十分顺畅,但如果存在需要校验的重要关联关系,要如何实现快速重放?
例如:Product(id, name, quantity)和Order(id, list of productIds)两个实体,若先触发CreateProduct事件再触发CreateOrder事件,流程会正常执行(产品已入库可用),这类场景很容易通过Kafka实现:产品事件对应1个含n1个分区的topic,订单事件对应1个含n2个分区的topic。
但重放过程运行速度更快,Kafka可能会出现事件重排的情况(比如先收到CreateOrder再收到CreateProduct),这就会导致和原始运行结果不一致的情况:此时CreateOrder会因为对应产品不存在而执行失败。出现该问题的原因是Kafka仅保证同一个topic下单个分区内的事件顺序。最简单的解决办法是把所有事件都放到1个只有单个分区的大topic里,但这种方案完全不具备可扩展性,针对规模较大的数据库,单线程重放至少需要数天才能完成。
请问目前有没有更优的成熟方案,可以实现关联实体的快速重放?还是说当数据库需要做关联关系校验时,我们就不适合采用Event Sourcing和事件重放机制,事件重放仅适用于无关联数据场景?
存在关联关系校验的场景完全可以正常使用Event Sourcing和事件重放机制,工业界已有多个落地验证的可扩展方案,无需牺牲性能采用单分区方案,常见实现思路如下:
- 分区路由对齐方案
核心逻辑是将存在依赖关系的事件路由到同一个Kafka分区,利用Kafka单分区严格保序的特性避免乱序。具体实现时可以给所有事件增加dependency_ids字段,录入该事件依赖的所有实体ID,事件生产时按依赖ID的哈希值取模选择分区,例如引用了Product ID为123的CreateOrder事件,和ID为123的CreateProduct事件会被路由到同一个分区,保证两者的相对顺序和生产时完全一致。这种方案可以完全保留Kafka多分区并行处理的能力,扩展性和无关联场景一致。 - 容错重放+延迟重试方案
无需修改原有事件的分区规则,重放逻辑中增加乱序容错机制:当处理事件时如果发现依赖的实体不存在,不直接抛出失败,而是将该事件写入延迟重试队列,等主流程的重放任务完成后,再批量处理重试队列中的事件。通常99%以上的事件都会在主流程中正常处理,只有极少量乱序事件需要重试,重放效率接近纯并行重放的性能。重试多次仍失败的事件可以归入死信队列做定向排查。 - 分阶段预加载依赖方案
针对全量重建状态的重放场景,可以先单独重放所有被依赖的实体事件,提前构建好依赖实体的状态缓存,再重放依赖方的事件。例如示例场景中可以先多分区并行重放所有Product的事件,构建出全量Product ID的索引缓存,再并行重放所有Order事件,此时Order校验Product是否存在时直接查缓存即可,不会出现依赖缺失的问题。 - 全局序列号+窗口排序方案
事件生产时为每个事件分配全局严格单调递增的序列号(可通过分布式ID生成器、中心化序列服务实现),重放时拉取固定大小窗口的事件,在内存中按序列号归并排序后再批量执行处理逻辑。窗口大小可根据重放吞吐量动态调整,既保证全局事件顺序和生产时完全一致,又能实现高吞吐的并行处理,适合对顺序一致性要求极高的场景。
内容的提问来源于stack exchange,提问作者iirekm

