微服务架构下如何应对并发与乱序事件以维护数据完整性?
微服务架构下后台管理工具的并发与乱序事件处理问题
背景
- 微服务发送事件,用于更新Elasticsearch中的读模型
- 后台工具需展示订单、客户、地址等多类数据
- 部分数据(如订单)的读模型由多个微服务的事件共同构建
- 事件可能出现乱序或延迟到达的情况
订单索引的文档结构如下:
{ "id": "2bc3ec03-eaf4-46cb-9a41-3854098c8763", // 来自 order.* 事件 "line_items": [], // 来自 order.* 事件 "shipping_address": { // 来自 shipping.* 事件 "name": "", "surname": "" }, "timestamp": 12381231238 // 修改数据的最新事件时间戳 }
问题
假设先处理了timestamp为2的订单事件,更新了Elasticsearch中的timestamp;10分钟后,timestamp为1的shipping_address.changed事件到达,若基于全局timestamp处理乱序事件,将不会更新读模型中的收货地址。
并发处理方案
通过为指定ID一次处理一个事件应对并发:事件到达时加入任务队列,利用Laravel的WithoutOverlapping中间件确保同一时间只有一个Worker修改对应文档。
现有乱序事件处理方案
1. 基于Redis的临时事件存储
- 事件到达时暂存于Redis
- 队列任务获取指定Key下的所有事件,按事件timestamp排序后处理
- Redis中事件的TTL设为15分钟,符合当前业务场景
- 可正确处理乱序事件,但增加了复杂度与内存占用
- 若事件延迟超过15分钟,因timestamp早于Elastic文档中的最新timestamp,将无法被处理
2. 为不同数据源设置独立Timestamp
- 文档中来自不同数据源的字段拥有独立timestamp
修改后的Elasticsearch文档结构:
{ "id": "2bc3ec03-eaf4-46cb-9a41-3854098c8763", "line_items": [], "shipping_address": { // 数据来源为 shipping.* 事件 "timestamp": 123123123, // 该字段的独立时间戳 "name": "", "surname": "" }, "timestamp": 1123123123 // order.* 事件对应的根时间戳 }
- 仅当新事件的timestamp晚于对应字段存储的timestamp时才执行更新:处理
order.*事件时对比根timestamp,处理shipping_address时对比shipping_address.timestamp与事件timestamp - 即使事件延迟数小时到达,仍可被处理
3. 将所有事件存储至数据库
- 接收事件后存入数据库
- 加入队列任务更新索引
- 任务获取所有影响该索引的事件,按顺序从最早到最新依次处理
- 可保障最高数据完整性,但担忧数据库数据量膨胀,可对已完成订单的旧事件进行归档或删除
寻求解答
- 是否有其他处理乱序事件的策略?
- 上述方案中哪种更合理?
- 各方案存在哪些缺陷?
- 是否有更简便的方式实现后台工具的数据展示(已弃用通过API查询微服务并合并数据的方案,因速度过慢)?
内容的提问来源于stack exchange,提问作者Kamil Latosinski
相关产品推荐
相关产品推荐

