3.5 全链路可靠性方案:从发布到签收的检查点地图 本节摘要:可靠性不是某个开关,而是横跨发布、路由、存储、投递、签收五段的接力链,每段有各自的丢失风险与对应的堵漏手段。本节把第 3 章前四节的方案汇成一张端到端检查点地图,给出按业务分级的保障配置组合,并附一段可以直接抄进项目的完整代码。 五个现场追查完毕,是时候结案了。本节没有新机制,只有一次总装——把散落在各节的知识点拼成一张能贴在工位上的地图。 端到端检查点地图 图 10 全链路可靠性检查点地图 图 6 全链路可靠性检查点地图 五段检查点的配置细节此前各节都已展开,本节补上总装的最后一件事:一段把发布段与存储段检查点一次配齐的生产级代码骨架。
本节摘要:可靠性不是某个开关,而是横跨发布、路由、存储、投递、签收五段的接力链,每段有各自的丢失风险与对应的堵漏手段。本节把第 3 章前四节的方案汇成一张端到端检查点地图,给出按业务分级的保障配置组合,并附一段可以直接抄进项目的完整代码。
五个现场追查完毕,是时候结案了。本节没有新机制,只有一次总装——把散落在各节的知识点拼成一张能贴在工位上的地图。

五段检查点的配置细节此前各节都已展开,本节补上总装的最后一件事:一段把发布段与存储段检查点一次配齐的生产级代码骨架。
背景:为"核心交易链路"写一个发布方:确认送达、强制路由、失败落补偿记录,三者缺一不可。
操作:
import pika, json, logging log = logging.getLogger("reliable-publisher") def publish_reliably(channel, exchange, routing_key, payload: dict): payload_bytes = json.dumps(payload).encode() props = pika.BasicProperties( content_type="application/json", delivery_mode=2, # 存储段:消息持久化 message_id=f"{payload['order_id']}-{payload['event']}") try: # 发布段:confirm 模式下,返回 True 代表 Broker 已收妥并落盘 ok = channel.confirm_delivery() channel.basic_publish( exchange=exchange, routing_key=routing_key, body=payload_bytes, properties=props, mandatory=True) return True except pika.exceptions.UnroutableError: # 路由段:被 mandatory 退回,说明无绑定命中 log.error("消息不可路由,转人工补偿: %s", payload) compensation.save(payload) # 本地补偿表,定时重投 return False except pika.exceptions.NackError: # 存储段:Broker 明确 nack,通常伴随磁盘异常 log.error("Broker 拒收,检查磁盘与健康度: %s", payload) compensation.save(payload) return False
结果:三种结局各有去处——确认成功进队列,不可路由进补偿表,Broker 拒收进补偿表加告警。解读:注意本地补偿表的定位:它兜的是"确认机制告诉你的失败",而"确认机制没告诉你的失败"(比如进程在确认回来之前崩溃)要靠业务对账兜底。两道防线叠加,才构成端到端闭环。这也解释了为什么所有成熟的消息平台方案里都有一张对账表——技术手段可以把丢失概率压到极低,压不到零。
变式:把分级保障落到代码——日志埋点链路把 confirm、mandatory、delivery_mode 全部去掉,同样的函数加个 reliability 参数控制三处开关,一套发布方服务全公司所有等级的消息,运维与代码双双收敛。
补偿表兜的是"发布方可感知的失败",业务对账兜的是剩下的全部。对账不神秘,它的最小实现是一个定时任务加两条 SQL 的时间窗比对,骨架如下:
def reconcile(yesterday_start, yesterday_end): # 业务侧:昨天真实发生的支付成功单 paid_ids = db.query( "SELECT order_id FROM payments " "WHERE status='paid' AND paid_at BETWEEN ? AND ?", yesterday_start, yesterday_end) # 消息侧:事件表中这些单据的支付事件是否齐备 emitted = event_store.query( "SELECT DISTINCT order_id FROM order_events " "WHERE event='created' AND created_at BETWEEN ? AND ?", yesterday_start, yesterday_end) missing = paid_ids - emitted for order_id in missing: log.warning("对账发现缺口,补投: %s", order_id) publish_reliably(channel, "order.events", "order.created", build_event_from_db(order_id))
几个工程细节决定对账的可用性。窗口要留缓冲:取昨天的对账窗口时前后各放宽五分钟,避免"消息在窗口边界到达"的边缘误报(6.1 节案例二的教训)。补投要带标记:补投的事件加一个 reconciled: true 头,下游据此与原始事件区分,监控与审计各取所需。对账结果本身要可观测:缺口数是零还是五十,应该出现在 4.4 节的监控面板上——缺口曲线的突起往往比告警更早暴露上游故障。
深夜追查到此结案:那批"失踪"的订单事件死于自动确认加消费者 OOM 的组合,修复方案是手动签收加 prefetch 限流;复盘同时暴露出另外两处隐患——消息未持久化、路由无兜底——均已按检查点地图补齐。这张地图从此成为团队的评审清单:任何新的消息链路上线前,五段检查点逐一过卡。
💡 关键直觉:可靠性的验收标准不是"配置了什么",而是"丢失发生时你会以多快速度、通过什么途径知道"。配置是手段,可观测与可对账才是承诺。
追踪单的后半程走完。第 4 章换个视角:这台 Broker 本身怎么装、怎么配、怎么组成集群——让它配得上你为消息做的所有保障。