导语:搬得更快,也可能错得更快
想象一家商店同时接到“下单”“改地址”和“取消订单”三条通知。如果系统为了提速,让它们分头处理,取消可能反而先于下单生效;同一条付款通知重试两次,还可能生成两笔记录。数据管线扩容的难点就在这里:速度上去了,结果仍得和业务真实发生的顺序一致。
Yuelin Ou 在 Towards Data Science 分享了一次生产环境复盘。这条集成管线是二十多个业务系统之间的中间层,负责搬运和转换事件。系统接口并不统一:有 REST,也有 SOAP,还有系统通过 FTP 投递文件。作者称,管线日常延迟要控制在约 0.5 秒以内,并承受约十倍于常态的峰值流量;普通峰值可达每秒数万事件,促销期间更高。
但标题方向里的“吞吐翻 16 倍”没有得到所给材料支持。材料没有提供起点、终点或对应测量区间,也不能拿“承受约十倍峰值”替代“吞吐提升 16 倍”。因此,这篇复盘真正值得看的不是倍数,而是它怎样划定扩容时不能越过的正确性底线。
第一条底线:旧状态不能盖掉新状态
分布式管线里,同一条更新可能重复送达,也可能乱序到达。网络重传、队列再次投递、消费者处理中途重启,都会造成这种情况。作者的处理方式是让每个实体携带由源系统生成的版本号。写入数据库时,只有版本号更高的数据才能更新现有记录;较旧版本即使晚到,也会被拒绝。
这类似“最后写入者胜出”,但这里的“最后”不是最后抵达,而是版本号最高。区别很重要:到达时间受网络和并行处理影响,不能代表业务状态真正发生的先后。
作者还按实体 ID 分区——分区可以理解为把事件分到多条并行队伍。同一实体始终进入同一分区,便能在局部维持顺序,不必让所有消费者彼此协调。这也带来一个现实问题:一个大客户的更新量约为普通客户的一百倍时,它所在的单个分区会被压满,旁边的消费者却可能闲着。继续增加消费者,也无法绕开这个局部瓶颈。
第二条底线:重复事件不能重复生效
系统为了避免丢消息,常采用“至少一次交付”:宁可把同一事件多送几次,也不轻易漏掉一次。与之配套的是幂等——同一事件重复处理,不会重复扣款或创建两笔订单。
这条管线用去重日志记录“某事件是否已经接受”。关键不只是有日志,而是去重记录和业务数据必须放在同一个数据库事务里:两者要么一起提交,要么一起失败。否则,日志可能说“处理过了”,业务数据却没写进去;也可能业务数据已经生效,日志仍说没有处理。
作者称,早期方案先在业务代码中查询,再执行写入。并发升高后,两步之间的空隙会让重复事件溜过去。后来,判断被下沉到数据库主键约束,由数据库拒绝重复写入。去重日志会持续增长,因此系统每晚清理三十天以前的记录;作者认为,这已经明显超过实际重复投递可能发生的时间窗口。
为什么值得关注
这次经验说明,吞吐优化不是单纯增加机器或并行消费者。并行度越高,乱序和重复处理的机会也越多。只有先把“版本只能前进”和“去重记录必须与业务数据一致”固化在写入路径里,系统才有空间继续扩分区、扩消费者或调整批量大小。
背压同样是理解这类扩容的关键:当下游来不及处理时,上游需要减速、排队或限流,避免积压耗尽内存并触发连锁故障。不过,所给文章片段没有披露该管线具体如何实施背压和失败恢复,不能替作者补全。
局限与未知
- 全部数字都来自作者对生产管线的自报,没有第二个独立信源、监控截图或原始数据。
- 吞吐按消费端每秒处理事件数统计,取自正常营业时段,并非受控基准测试或峰值流量;“稳定”指跨完整业务周期维持在正常波动范围内。数据覆盖多个月底结账和销售高峰周期。
- 作者比较过批大小 50、100、200 和 500,但材料没有提供比较结果。测试来自真实生产负载,作者也明确表示其结论只适用于这项工作负载,不能当作通用参数。