最终一致意味着短暂的不一致是允许的,但不能永远不一致。靠什么发现?全量还是增量?业务还在写时怎么避免误报?发现之后怎么修?这篇用两个真实的 MySQL 和 10 万个订单,把对账与修复的这些环节逐个量出来。
订单库 100000 行(PAID 89972),支付库 89792 行;归并比较了 90241 个订单号,用时 0.36 s 分歧类型 | 注入的 | 对账报出的 | 其中命中 | 误报(多报) | 漏报 --------------+-----+-------+------+--------+--- 丢失(订单已付,没有支付) | 899 | 899 | 899 | 0 | 0 重复(同一订单两条支付) | 450 | 450 | 450 | 0 | 0 金额不一致 | 450 | 450 | 450 | 0 | 0 孤儿(有支付、订单不存在) | 269 | 269 | 269 | 0 | 0 (孤儿的判定里会混进“订单是 CANCELLED 但支付库里还有支付”的订单——本数据里没有这种,所以误报为 0)
两边按订单号排序流式读出、归并比较:订单是 PAID 就必须有且只有一条金额相同的支付。四类分歧全部发现,误报漏报都是 0。
对账窗口 | 发现的真分歧 | 召回率 | 误报(多报) ----------------------------------------+-------------+------+------- 最近 1 小时 | 97 / 2068 | 5% | 2 最近 6 小时 | 520 / 2068 | 25% | 0 最近 24 小时 | 2068 / 2068 | 100% | 0 最近 6 小时(支付库时钟慢 5 分钟,支付行盖的时间比真实时间早 5 分钟) | 518 / 2068 | 25% | 311 窗口外的分歧永远发现不了;窗口边缘上订单在窗口里、而支付的时间戳(晚了几秒,或者因为时钟慢了几分钟)落在窗口外,会被报成“丢失”;所以增量对账要有重叠窗口,并定期做全量
只比最近窗口的话,窗口外的分歧永远发现不了;窗口边缘和时钟偏差还会误报。需要重叠窗口并定期做全量。
对账策略 | 每次检查的新订单数(平均) | 每次误报“丢失”(平均) | 最多一次误报 --------+---------------+--------------+------- 宽限期 0 秒 | 57 | 0.6 | 1 宽限期 1 秒 | 86 | 0.0 | 0 宽限期 3 秒 | 102 | 0.0 | 0 写入线程这段时间一共产生了 147 个订单,所有订单的支付最终都落地了(没有真实分歧),所以这里的“丢失”全是误报;宽限期换来 0 误报,代价是真正的分歧要等宽限期之后才能发现
订单已提交、支付还在路上的订单会被报成丢失。只对足够老的行下结论,用宽限期消除。
第一次修复(用时 0.15 s)各类动作数:{'丢失(订单已付,没有支付)': 899, '重复(同一订单两条支付)': 450, '金额不一致': 450, '孤儿(有支付、订单不存在)': 269}
修复之后重新对账(只看状态为 PAID 的支付):{'丢失(订单已付,没有支付)': 0, '重复(同一订单两条支付)': 0, '金额不一致': 0, '孤儿(有支付、订单不存在)': 0}
再跑一遍修复(基于重新对账的结果):各类动作数 {'丢失(订单已付,没有支付)': 0, '重复(同一订单两条支付)': 0, '金额不一致': 0, '孤儿(有支付、订单不存在)': 0} —— 修复是幂等的,没有更多可做的补、冲、改、退都写成可重复执行,修复后重新对账验证,再修一次不会再动手。
订单数 | 归并比较的订单号数 | 用时 | 订单/秒 | 发现的分歧总数 -------+-----------+--------+--------+-------- 20000 | 18123 | 0.08 s | 252082 | 415 100000 | 90314 | 0.36 s | 279396 | 2070 300000 | 270939 | 1.04 s | 288592 | 6212 用时随订单数大致线性增长;只比较了金额与笔数,没有比较其它字段
全量对账用时随订单数线性增长,这里每秒约 25 万到 29 万个订单。