对接类的工作,一半时间在处理系统之间的不确定性,另一半时间在处理人和人之间的不确定性。
前者表现为字段漂移、重复投递、对方半夜升级;后者表现为群里一句”我们没收到,你们发了吗”,然后两边猜三天。
看起来是两件事,其实是同一套功夫:别让”我以为”进入流程,把该核对的东西都变成可核对的事实。
一、对系统:把”会出事”当成前提
你的代码活在两个你都控制不了的系统之间,上游说发的没问题,下游说收的没问题,只有中间那层是你的。所以自保的原则很朴素:
1. 能只读就别写,能旁路就别进主流程。
最舒服的对接是我去拉,最麻烦的是我往你那推——推意味着你不知道对方何时重启、限流、把 id 改成 uuid。优先级很明确:只读 > 可旁路的代理 > 写进对方主流程。每往下一级,耦合多一分,半夜被叫起来的概率也高一分。
2. 把对端当成不可信的输入。 不是不信人,是不信”长期运行的接口”。必填字段偶发为空、时间格式偷偷带时区、编码从 UTF-8 变 GBK、同一条数据投递两遍、先收到”结束”再收到”开始”——这些都会发生。所以空值、类型、长度、枚举、时间格式全部校验,校验不过的落库隔离而不是丢掉:被丢掉的那条,往往就是用户投诉的那条。
3. 每个边界留一行证据。 出问题时第一句话永远是”你们到底收到没有”。没有日志,这句话就要靠猜。标准很土:进出各一行结构化日志——时间、方向、业务键、结果码、耗时、脱敏后的报文摘要,保证五分钟内能回答三个问题:这条进来没有?进来为什么失败?失败的那次我重试了没有、结果如何。可观测性不是什么高级词,就是提前把账记在对的地方。
4. 幂等、重试、回滚,三件套提前想。 重试的前提是幂等:用业务唯一键去重(不是自增 ID),失败落进待重放表按退避重试,超次进人工队列。上线前再问一句:出了事我一分钟内能不能退回去? 灰度、开关、数据回滚方案,成本永远比”线上炸了现想办法”低两个数量级。
5. 配置和契约外置。 字段映射、超时、开关、地址全部进配置;契约文档和实现放同一个仓库、带版本。因为半年后接手的人(很可能就是你自己)只有文档,没有记忆。
二、对人:把”帮我看看”换成坐标
“我们这边没收到数据,你们发了吗?”——信息量约等于零,却能消耗两边三天。不是谁不配合,是它没有给出任何可以核对的锚点。
好问题有三样东西:
- 我要证明哪条结论:不是”帮我看看”,而是”我要确认昨天 14:00–15:00 的告警,是你们没发,还是我们没落库”。目标一明确,路径就唯一。
- 可核对的坐标:时间窗并且写清时区、至少一条具体对象(业务 ID / UUID / 图片 URL)、边界位置(入口是什么、出口是什么、中间经过谁)。
- 已排除项:我看过什么、排除了什么、剩下哪些假设。把排除掉的写出来,对方不用重复劳动,也不会怀疑你在甩锅。
问之前还要花三十秒自问:这件事我能不能自己先取一次证据? 大多数”对方没发”,其实是”我们没记”。看一眼入口日志、查一次库、回放一遍报文——群里拉扯三天的问题,往往三十分钟自己就解决了。
反过来,被问的人也要有章法:先给结论(有/没有/部分有),再给证据(哪个日志、哪一行、什么时间点),最后给下一步(谁在什么时候做什么)。不要用”我这边正常”结束对话——那和”你们查一下”是同一种信息量的反面。
三、其实是同一件事
系统那侧的日志、契约、幂等键,人这侧的坐标、证据、已排除项,本质都在做同一件事:把不可控的东西拆成可核对的颗粒。
可核对,就能收敛;靠感觉,就只能拉扯。
所以那些看起来”运气特别好、问题总能很快解决”的人,通常不是运气好:他给系统留了足够的账,给人留了足够的坐标。方向对了,别人愿意帮他查;坐标齐了,一查就到。