首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >物流单号批量同步老是重复推送:指数退避、幂等键与死信队列的落地记录

物流单号批量同步老是重复推送:指数退避、幂等键与死信队列的落地记录

原创
作者头像
用户9660458
发布于 2026-10-13 09:03:36
发布于 2026-10-13 09:03:36
350
举报

上个月帮朋友接了个物流轨迹同步的活:每天凌晨把前一天产生的两三千个单号批量推给上游查询接口。上线头三天还好,第四天运维半夜打电话说上游投诉我们同一个单号一天推了七八次,那边订单状态被反复覆盖,客户都来问了。

一开始我以为是网络重试没控好,翻了日志才发现没那么简单——超时的、返回 500 的、上游直接断连的,三种错误走了三条不同的重试路径,最后在并发下互相踩。前后花了两个晚上把重试、幂等和死信这套东西理清楚,记录下来。

一、先搞清楚:为什么会重复推送

批量任务是这样跑的:读本地待推列表 → 开 20 个线程调上游接口 → 成功的标记 done,失败的记下原因。问题出在失败重试这一段:

  1. 接口超时(socketTimeout 设的 3 秒),但上游其实处理成功了,我们这边没收到响应,任务状态还停留在"待重试"。
  2. 重试时又没带唯一请求标识,上游收到后当成新请求又处理一遍。
  3. 三个线程同时捞起同一个"待重试"单号(数据库行没锁),一起推。

三个原因叠在一起,一个单号被推四五次很正常。

二、第一步:幂等键先加上,别让上游重复扣账

最关键的一条:每个单号每次推送带一个唯一的 requestId,上游用它去重。requestId 不能随机生成,要和"单号+推送批次"绑定,这样重试时同一个业务请求重试多少次都是同一个 id:

代码语言:txt
复制
// requestId = MD5(trackingNo + batchDate + pushScene)
// 例:MD5("SF1234567890" + "2026-10-12" + "track_sync")

上游收到后先查这个 requestId 有没有处理过,处理过直接返回上次的结果,不再执行业务。这一步做完,即使我们重推十次,上游侧也只生效一次。

三、第二步:指数退避,别一失败就猛打

一开始重试是固定 5 秒一次,连续重试 5 次。上游抖动时,20 个线程同时每 5 秒打一轮,等于雪上加霜。改成指数退避:

代码语言:txt
复制
// 第 n 次重试等待 = base * 2^(n-1),base=1秒
// 第1次:1s  第2次:2s  第3次:4s  第4次:8s  第5次:16s
// 再加随机抖动正负20%,避免多个线程同时醒来
delay = base * pow(2, retryCount) * (0.8 + random*0.4)

这样失败后不是立刻猛打,而是把间隔拉长,给上游恢复时间。总重试次数控制在 5 次,超过就进死信队列人工看。

四、第三步:死信队列和并发捞取

5 次还失败的单号,别留在主任务里反复重试拖垮整批,扔到死信表:

代码语言:txt
复制
dead_letter 表字段:
tracking_no, request_id, last_error, fail_count, next_retry_time, create_time

并发捞取那个坑也要堵:别用"select * where status=待重试"然后多线程抢,改成数据库行锁或者分布式锁:

代码语言:txt
复制
// 用 for update 锁住要处理的行,处理完再提交
select * from push_task where status='pending' order by id limit 50 for update skip locked;

skip locked 让别的线程直接跳过已经被锁的行,不会两个线程捞到同一个单号。

五、踩坑清单(5 条实际踩过的)

  1. 重试时 requestId 必须按业务字段算,不能每次随机,否则幂等形同虚设。
  2. socketTimeout 别设太短(我们 3 秒),上游慢一点就超时误判,建议 5-10 秒 + 连接超时 1 秒。
  3. 指数退避一定要加随机抖动,否则 20 个线程会整齐划一地同时重打。
  4. 批量任务并发捞数据库一定要 for update skip locked,别靠应用层抢到才处理。
  5. 死信队列要有告警,别让重试 5 次的单号悄悄烂在表里没人管。

错误类型

处理策略

最大重试

连接超时/断连

指数退避重试

5 次

上游返回 500

指数退避重试

3 次

上游返回 4xx 业务错

不重试,直接死信

0 次

上游返回 200 但业务失败

查日志人工处理

0 次

这次物流轨迹批量同步是在固乔快递批量查询助手的任务调度上接的,我主要负责重写重试策略、补 requestId 幂等键和死信表那套逻辑,改完后上游那边重复推送的投诉当晚就没了,整批同步从原来的 40 分钟缩到 18 分钟。

复盘

  • 批量推送重复的根因往往不是重试太多,而是重试没带幂等键 + 并发抢同一行。
  • 指数退避要加随机抖动,4xx 业务错误别重试,5xx 和超时才值得退避。
  • 超过重试上限进死信表,配告警,别让失败单静默堆积。

以上是个人实践记录,各平台具体功能以官方实时信息为准。

你们做批量任务时,幂等键是放在请求头还是请求体里?上游那一侧一般怎么落库去重?

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、先搞清楚:为什么会重复推送
  • 二、第一步:幂等键先加上,别让上游重复扣账
  • 三、第二步:指数退避,别一失败就猛打
  • 四、第三步:死信队列和并发捞取
  • 五、踩坑清单(5 条实际踩过的)
  • 复盘
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档