自建推理服务前面挂了一层网关,网关对 5xx 自动重试两次。用 SDK 批量生成商品文案,客户反馈同一订单收到三条一模一样的短信。查日志发现模型调用 42 秒超时,网关重试后上游三次都算完并写了库,服务里只有 try/except 没有幂等键,环境是 FastAPI 0.115 加 uvicorn 4 个 worker 加 Redis 7.2。

原因不止一个,得分开看。网关按 5xx 自动重试,服务端把重试当成全新请求;幂等键只存在进程内的字典里,多个 worker 之间不共享;上游超时设得比实际推理耗时短,客户端先放弃连接;写库和回写缓存分成两步,中间没有标记位。

动手时先确认网关实际的重试次数和超时值

curl -s http://gw:8080/config | jq '.retry, .timeout'

再往下把幂等键写进请求头并落到 Redis 做去重

redis-cli SET idem:order_20261110 1 NX EX 600

服务端入口先查键再决定是否进入推理

curl -H 'X-Idem-Key: order_20261110' http://127.0.0.1:8000/generate -d '{"prompt":"p"}'

末了重试时复用同一个键并同步下调上游超时

python -c "import requests;print(requests.post('http://127.0.0.1:8000/generate',headers={'X-Idem-Key':'order_20261110'},timeout=45).status_code)"

别只看报错消失,验证要多做一步。同一订单连续触发五次,库里只有一条记录,Redis 里键的 TTL 正常递减,网关日志中重试请求全部命中已处理标记。

这里还有个坑。幂等键必须由业务侧生成并在重试之间保持不变,每次新生成一个 UUID 等于没做;TTL 至少要覆盖网关的最大重试窗口。监控比日志更早给出信号,事后回看经常能提前半小时发现异常。

作者 9823189