微调任务完成的 webhook 一晚上收到三次,业务侧连着发了三封通知邮件,事件体里的 id 完全一样,只有 created_at 每次都不同。

一开始怀疑是「返回 500 或连接超时都会触发重试,重试间隔逐次拉长」,查了一圈,方向不对。

排查方向其实有限,先把可能性排一排。投递是至少一次语义,收到 2xx 之前会按间隔重试。少数情况下是处理耗时超过超时阈值,服务端判定失败后重发一次。

少数情况下是事件 id 才是幂等键,用时间戳或内容哈希去重都会漏。

上来先看事件体里哪些字段是稳定的

python -c "import json;d=json.load(open('event.json'));print(d['id'],d['type'],d['created_at'])"

给事件 id 建唯一索引挡住重复写入

python -c "import sqlite3;c=sqlite3.connect('w.db');c.execute('create unique index if not exists ux on events(id)');print('ok')"

落库再处理,接口立刻返回 200

python -c "from fastapi import BackgroundTasks;print(BackgroundTasks.__name__)"

剩下的就是按幂等键判断是否需要触发下游

python -c "import hashlib;print(hashlib.sha1(b'evt_1').hexdigest()[:8])"

别只看报错消失,验证要多做一步。同一事件重复到达时第二次写入被唯一索引拒绝,通知邮件一晚上只发出一封,投递日志里仍能看到三次 200 响应。

这里还有个坑。幂等键要落在存储层,靠内存里的字典挡不住多实例部署,重启之后重复事件又会全部走一遍。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189