Saga 一旦跑起来,谁知道它们卡在哪一步?worker 崩溃了怎么发现、怎么接着做?重试了几次、花了多久?什么时候该报警?这篇用 saga_log / saga_step 两张状态表,配上恢复守护进程和几条监控查询,在真实的 MySQL 上把这些问题量出来。
要回答“lost-1 现在卡在哪一步”,得分别去问:订单库 PENDING 、库存库 RESERVED 、支付库 0 条支付; 订单 PENDING + 库存 RESERVED + 没有支付 → 推断“预留之后、扣款之前挂了”,但这是人在拼三份数据后的推断:没有人知道 worker 是死了还是只是在等,也不知道它重试了几次、上一次错误是什么
只能跨三个服务的库拼出一个 saga 的位置,不知道它还活着没有、重试了几次。
1000 个 saga 在 1.0 s 里跑完(worker 的返回值):{'CANCELLED': 179, 'CONFIRMED': 801, 'WORKER_CRASHED': 20}
从状态表里看:{'in_flight': 20, 'oldest': 0.888, 'stuck': 0, 'comp_failed': 0, 'confirmed': 801, 'cancelled': 179, 'total': 1000};saga_step 一共 4296 行,其中临时失败的尝试 341 行
各状态的 saga 个数:CANCELLED 179, CONFIRMED 801, RUNNING 20
卡住的(非终态)saga 里最老的 5 个(id、状态、当前步骤、最后的错误、多少秒没更新):[('saga-12', 'RUNNING', 'confirm', None, 0), ('saga-112', 'RUNNING', 'confirm', None, 0), ('saga-193', 'RUNNING', 'charge', None, 0), ('saga-214', 'RUNNING', 'reserve', None, 0), ('saga-225', 'RUNNING', 'charge', 'create: order_create 临时不可用', 0)]saga_log 记当前步骤、状态、最后错误,saga_step 每次尝试一行;卡住的 saga 一眼可见。
第一轮扫描接管并继续了 20 个 saga,第二轮 0 个
恢复之后状态表:{'in_flight': 0, 'oldest': 0.0, 'stuck': 0, 'comp_failed': 0, 'confirmed': 821, 'cancelled': 179, 'total': 1000}
各状态的 saga 个数:CANCELLED 179, CONFIRMED 821
对账:库存仍被占着、但订单不是 CONFIRMED 的 0 个;已扣款、但订单是 CANCELLED 的 0 个;状态表里 COMPENSATION_FAILED 的 0 个扫非终态、久未更新的 saga,抢 owner 接管,从记录的步骤继续;每一步幂等才安全。
预留库存这一步的尝试次数分布(尝试 n 次的 saga 个数):{1: 925, 2: 66, 3: 9}
saga | 个数 | 创建到结束 中位数 | p99
---------+-----+-----------+-------
没有临时失败 | 709 | 4 ms | 59 ms
至少一次临时失败 | 271 | 30 ms | 101 ms
重试(带指数退避)让有临时失败的 saga 变慢,但都走完了;saga_step 里能直接查到每个 saga 在哪一步试了几次、每次什么错尝试次数分布和耗时都能从 saga_step 查;有临时失败的 saga 更慢但都走完。
时刻 | 在途 saga | 最老的非终态 saga 多久没更新 | 已确认 | 告警 ------+---------+-------------------+-----+--- 0.0 s | 0 | 0.0 s | 0 | 0.5 s | 0 | 0.0 s | 62 | 1.0 s | 2 | 0.0 s | 123 | 1.5 s | 24 | 0.2 s | 123 | 2.0 s | 48 | 0.4 s | 123 | 告警 2.5 s | 72 | 0.9 s | 123 | 告警 3.0 s | 79 | 1.3 s | 123 | 告警 3.5 s | 96 | 1.8 s | 123 | 告警 4.0 s | 113 | 2.3 s | 130 | 告警 4.5 s | 80 | 2.2 s | 478 | 告警 5.0 s | 72 | 2.7 s | 528 | 告警 5.5 s | 64 | 3.1 s | 536 | 告警 6.1 s | 56 | 3.1 s | 544 | 告警 6.6 s | 48 | 3.5 s | 552 | 告警 7.1 s | 40 | 4.0 s | 560 | 告警 7.6 s | 32 | 4.0 s | 568 | 告警 8.1 s | 24 | 4.4 s | 576 | 告警 8.6 s | 16 | 4.8 s | 584 | 告警 9.1 s | 6 | 0.0 s | 594 | 9.6 s | 0 | 0.0 s | 600 | 支付服务在 1.0 s 到 4.0 s 中断;第一次告警出现在 2.0 s,告警在 9.1 s 解除;恢复守护进程一共接管并继续了 104 个 saga;最终状态:CONFIRMED 600(共 600 个) 其中 saga_step 里有临时失败记录的 saga 120 个;中断期间扣款的退避重试把在途数顶高、让最老的 saga 越来越久没更新,告警在那时出现;重试用完的 saga 停在 RUNNING,由守护进程在支付恢复之后接着跑完
在途数和最老非终态年龄两个指标能在中断 1 秒内触发告警;恢复速度由守护进程的接管能力决定。