FMS · TAOBAO ORDER PIPELINE

淘宝订单 API 抓数
及补偿指南

覆盖 thirdhub-scrapy-taobao 订单链路的完整原理:双表架构、主抓单水位机制、三代补偿体系、生产排期、补数手册与真实排障实录。目标:新人按本指南可以独立核对数据、执行补数、定位漏单。

适用系统:thirdhub-scrapy-taobao / xxl-job / RabbitMQ / MongoDB(thirdhub) 版本:v1.0(基于 2026-10 排障实录整理)

开始之前 · 零基础先读

这一节不讲代码。看完你会明白:一笔淘宝订单是怎么进到我们库里的、为什么有两张表、以及为什么「抓到了」和「抓全了」是两回事。

先跟着一笔订单走一遍

假设今天 14:32 有位顾客在「kuyura可悠然旗舰店」下了一笔订单:

1
顾客下单,淘宝生成订单订单有「创建时间 created」和「修改时间 modified」两个关键时间——支付、发货、确认收货都会推进 modified。
↓
2
主抓单任务按「修改时间」增量拉单TaobaoIncrementOrderJobHandler 每 10 分钟跑一次,从上次抓到的位置(水位)继续,调 taobao.trades.sold.increment.get 拿到这段时间内有过变化的订单。
↓
3
订单发进 MQ,下游写入增量表每笔订单一条消息(exchange thirdhub_shelve,routing key taobao.tradesold.{sellerId}),下游消费者落库到 tradesold_taobao_increment。
↓
4
再逐单拉详情,写入全量表主抓单同时发一条 self.increment.* 消息,消费者对每笔订单调 taobao.trade.fullinfo.get 拿完整明细,再推 fullinfo.* 路由,落库到 tradesold_taobao。
↓
5
下游系统从这两张表取数财务、报表、仓配等系统按各自需要读增量表或全量表。两张表任何一张缺数,都会表现为「订单丢了」。

两张表,用大白话解释

tradesold_taobao_incrementtradesold_taobao
打个比方「到货登记簿」——先记一笔再说「完整档案柜」——每单一份全档
存什么增量接口返回的订单(列表级字段)fullinfo 接口拉的完整明细(收货人、子单、发票…60+ 字段)
怎么写入主抓单 → MQ → 下游消费者self 消费者逐单调 fullinfo → MQ → 下游消费者
对账口径trade.created(下单时间)trade.created(下单时间)
缺了会怎样这单「从来没到过」,全量表必然也没有「知道有这单,但详情拿不到」,下游按单处理会挂
谁负责补TaobaoIncrementCountCompensateJobHandler(对接口数量)TaobaoIncrementFullInfoOrderJobHandler(对增量表逐单补)
最重要的一个认知 「抓到了吗」看增量表,「抓全了吗」要两张表一起对。数量补偿(对比接口返回的单量)只能发现「整段缺失」,发现不了「单笔缺失但数量碰巧相等」的情况——后文第 8 节的四类漏单会专门讲。
一张图看懂:一笔订单如何进入两张表 上线是增量线(按修改时间抓),下线是全量线(逐单拉详情)。补偿体系围绕两条线各建了一道兜底。 增量线(列表) 全量线(明细) 淘宝订单 created / modified 支付发货都改 modified 主抓单(每10分钟) TaobaoIncrementOrderJobHandler trades.sold.increment.get RabbitMQ thirdhub_shelve taobao.tradesold.* Mongo 增量表 tradesold_taobao _increment 数量兜底 IncrementCount Compensate 按 modified self 消费者 self.increment.tradesold.* 逐单 trade.fullinfo.get RabbitMQ fullinfo. tradesold.taobao.* Mongo 全量表 tradesold_taobao 60+ 明细字段 全量兜底 IncrementFullInfo OrderJobHandler needFullInfo=true 通俗理解:增量线保证「知道这单存在」,全量线保证「有这单的完整档案」。任何一条线断了,对应兜底 job 负责补;两张表对得上,数据才算齐。
图 0-1 一张图看懂淘宝订单数据流(双线 + 双兜底)

130 秒速览与术语表

赶时间就记住这一节:一张职责表 + 一个黄金规则。

日常主抓单每 10 分钟自动跑,不用管 兜底00:30 补全量 / 00:40 补增量 / 04:00 刷状态 核对00:55 对「昨天」两张表数量 红线老 Job(CompensateJob / StatusCompensateJobhandler)已停用

先记住这 14 个词

词大白话在本系统里指什么
增量表 / 全量表到货登记簿 / 完整档案柜tradesold_taobao_increment / tradesold_taobao
水位系统记住「上次抓到哪」ShopChannelTaskDTO.nextScheduleTime,按「店铺 × API」各存一条,服务重启不丢
modified 抓单按「修改时间」增量拉主抓单用的 taobao.trades.sold.increment.get,老单有变化也能追到
created 对账按「下单时间」数数补偿 Job 与核对脚本统一用 trade.created 切窗口
needFullInfo抓到单后要不要顺手拉详情主抓单参数,true 时全量表由 self 消费者自动写
净推进每轮水位真正前进多少incrementSeconds − backSeconds,必须大于调度间隔才追得上
回看每次多往回抓一段防漏backSeconds,默认 300 秒,重复抓取靠幂等消化
数量补偿拿接口单量对库内单量接口 total_results vs 表 count,缺哪段补哪段
幂等重复跑结果不变两张表都按订单号(_id)覆盖/跳过,补数可反复执行
批量接口一次 POST 带多个子请求/router/batch,仅老 Job 使用,稳定性差已停用
单接口一次一个请求/router/rest,现行链路全部走它
游标丢失(-5)Mongo 翻页翻到一半断了分片集群深分页 + 高负载时的经典错误,重跑安全
老单晚到下单早、最近才被改modified 抓单会把几天前创建的单补进来,造成「历史日期缺口回涨」
北京时间 / UTC参数时区 ≠ 库内时区Job 参数填北京时间;Mongo 里存 UTC,核对时 +8 小时换算
黄金规则 判断数据齐不齐,只有一个口径:按「北京天」对比同一 sellerId 下两张表的 count。增量 = 全量 即齐;不等就按第 6 节补数。其它一切(Job 返回码、日志 true/false)都只是参考。

2双表架构与数据链路

这一节把图 0-1 展开成工程细节:三条淘宝 API、两条 MQ 路由、两个消费者、以及五个 Job 的新老关系。

2.1 用到的三条淘宝 API

API按什么查用途谁在用
taobao.trades.sold.increment.getmodified(修改时间)增量拉单主入口,列表级字段主抓单、Status 补偿重放
taobao.trades.sold.getcreated(下单时间)按创建时间数数量 / 翻列表Count 补偿对账、老 Job
taobao.trade.fullinfo.gettid(单号)拉单笔完整明细self 消费者、FullInfo 补偿、单号直补接口
两个口径差异(设计如此,但要心里有数) 数量补偿用 created 数数(接口 total_results 也是 created 口径),重放下载却按漏单 modified ±5s 拉增量——正常单「创建即有修改事件」所以能覆盖;但「列表接口不返回的单」(第 8.4 节)两条口径都碰不到。

2.2 MQ 路由与消费者

exchange: thirdhub_shelve (topic)

taobao.tradesold.{sellerId}          ← 主抓单/补偿重放,订单消息      → 下游写【增量表】
self.increment.tradesold.taobao.*    ← needFullInfo=true 时附带        → self 消费者逐单调 fullinfo
fullinfo.tradesold.taobao.*          ← fullinfo 结果                  → 下游写【全量表】(+ 订单快照)

三条路由都不在本仓库落库——真正写 Mongo 的消费者部署在其他服务上。所以任何补偿 Job 的日志里「补偿数量 N」指的都是成功推送了 N 条消息,不等于已落库;最终以表内 count 为准。

2.3 五个 Job 的新老关系

五个 Job 的关系:一条主链 + 新老两代补丁 绿色是现行链路(单接口 /router/rest),红色是 2020 老一代(批量接口 /router/batch,已停用)。同色成对:数量补 ↔ 数量补,刷状态 ↔ 刷状态。 现行链路(2021+ · 单接口) TaobaoIncrementOrderJobHandler 主抓单 · 唯一新单入口 水位推进 · modified 抓单 needFullInfo=true 连动全量线 每10分钟 · 必开 TaobaoIncrementCountCompensate 数量补偿 → 补【增量表】 接口 total vs 增量表 count 缺段按 modified±5s 重放 00:40 · 参数 {} TaobaoIncrementFullInfoOrder 全量补偿 → 补【全量表】 增量有 / 全量没有的单 逐单 fullinfo(按 created 切片) 00:30 · 参数 {} TaobaoIncrementStatusCompensate 状态补偿 → 刷未完结单 扫增量表未完结单重拉重推 不直接写库 · 走 MQ 04:00 · backDay:1 老一代(2020 · 批量接口 /router/batch · 已停用) CompensateJob 老数量补偿(按 created 对全量表,批量拉明细直接写库) ↔ 新 CountCompensate 同思路 · 任意一页失败整店放弃 StatusCompensateJobhandler 老状态补偿(扫全量表未完结单,批量拉明细直接更新) ↔ 新 StatusCompensate 同思路 · intervalTime 按分钟解析 停用原因:批量接口 /router/batch 稳定性差(2026-10-19 曾连续 500),且与新链路重复抓单、直接写库绕过 MQ。若 xxl-job 里还挂着,请先停。
图 2-1 五个 Job 的新老关系与分工
老 Job 若还在定时跑,请立即停用 排障实录:2026-10-19 03:35 CompensateJob、05:00 StatusCompensateJobhandler 因批量接口 500 全店失败,每天白耗配额;同日 12:57 走单接口的新补偿一次成功。功能上二者均被新链路覆盖。

3主抓单与水位机制

主抓单是唯一的新单入口。它没有 startTime/endTime 参数——时间完全由每家店的水位决定,这也是它「自动无缝衔接」和「某些坑」的共同根源。

3.1 一轮执行发生了什么

对每家开启抓单的店铺(并行 shopThreadNum 路):
  startTime = 水位(nextScheduleTime) − backSeconds      // 回看防漏
  endTime   = 水位 + incrementSeconds                    // 本轮抓到这
  if endTime > now: endTime = now                       // 不允许抓未来
  按 intervalSeconds 切片 → 每片: count → 按页拉单 → 发 MQ(增量路由 + self路由)
  本店全部成功 → 水位推进到 endTime;任何一步失败 → 水位不动,下轮重来
水位推进时间轴:净推进 = incrementSeconds − backSeconds 水位 10-20 19:11 −back 300s → 18:26 +increment 2700s 新水位 20:00 回看(重复抓,幂等消化) 本轮窗口 45 分钟 实际拉单区间 三条铁律: ① 净推进 > cron 间隔才追得上实时;相等只能不掉队;小于则永远落后(30分钟调度 × 45分钟窗口 = 每30分钟落后15分钟)。 ② 追赶积压用大 incrementSeconds(如 7200)+ 高频触发(5~10分钟 cron),别指望稳态参数自然消化。 ③ 水位只在「整店成功」后推进——大促小时一片失败,整店重来;所以窗口越大失败代价越大。
图 3-1 水位推进:回看、窗口与净推进

3.2 参数全解

参数默认含义与建议
incrementSeconds86100(约1天)每轮窗口长度 = 水位推进距离。稳态关键:净推进要大于 cron 间隔
backSeconds300回看秒数防接口索引延迟漏单。别低于 300,重复拉靠幂等
intervalSeconds86400窗口内切片大小。600(10分钟)平衡较好;追赶时可 1800 减 count 次数
pageSize40翻页大小,40 稳妥;接口允许时可试 100
shopThreadNum5店铺并行度,撞限流时降到 3
needFullInfotruetrue = 连动全量线;self 消费者故障期间可临时 false,全量改由 FullInfo 补偿兜
sleep0翻页间隔毫秒,稳态建议 500

3.3 日志怎么看

[店铺名]:开始执行:[2026-10-20T18:26:40]-[2026-10-20T19:11:40]   ← 本轮窗口(起=回看点,止=新水位)
[店铺名]:执行:[...], api总数: 951                              ← 该切片接口返回单量
[店铺名]:在:[起]-[止]时间内, api总数: N, 推送总数: M ==> true    ← 一轮汇总,false 即有切片推送失败

看到窗口终点离当前时间越来越近 = 在追平;一直停在同一个时间 = 水位卡住(多半有切片持续失败,查该店铺报错)。

4补偿体系:五个 Job 的分工

三个现行补偿 Job 各管一段,参数结构相似但默认值和坑不同。逐一说明。

4.1 TaobaoIncrementCountCompensateJobHandler(补增量表)

做什么对每店:整段对账(接口 total vs 增量表 count,按 created)→ 有缺口按 intervalMinute 切片细对 → 缺的切片翻列表挑出库里没有的 tid → 按 modified±5s 用增量接口重放 → 重推 MQ
默认窗口不填 = 昨天 00:00 ~ 今天 00:00;intervalMinute=60;sleep=3000
典型参数{"startTime":"2026-10-18 00:00:00","endTime":"2026-10-19 00:00:00","intervalMinute":60,"sleep":3000}
注意startTime/endTime 必须成对给(只给一个会被重置为默认);无店铺过滤,全店跑;整段第一步就调一次接口 count,大跨度窗口可能被接口拒绝,建议按天拆
返回值SUCCESS/FAIL 只反映「最后一家有缺口的店」补偿后复核是否一致;落库有 3 秒延迟,FAIL 不代表没补上——以日志「开始补偿」和最终 count 为准

4.2 TaobaoIncrementFullInfoOrderJobHandler(补全量表)

做什么对窗口(按 created 切片):数「增量表有」与「全量表有」→ 不等则逐单检查,缺的逐单调 fullinfo 推 MQ → 下游写全量表。纯 Mongo 对账 + 单号级接口,不受淘宝跨度限制
默认窗口不填 = 昨天 00:00 ~ 今天 00:00;intervalMinute=60;sleep=3000;pageSize=1000;sellerId 可选(单店排查用)
典型参数日常兜底 {};补大缺口 {"startTime":"…","endTime":"…","intervalMinute":60,"sleep":1000,"sellerId":"808734231"}
注意永远返回 SUCCESS(代码无失败分支),别看 xxl-job 状态,看日志 incrementCount:fullInfoCount = x:y;skip 深分页在大促小时会翻几十页,游标丢失高危(见 8.1),大缺口按店铺拆 + intervalMinute 降到 15
能力边界只能补「增量表里已有」的单;增量表缺的单它无能为力(先跑 4.1)

4.3 TaobaoIncrementStatusCompensateJobHandler(刷订单状态)

做什么扫窗口内「未完结状态」的单(WAIT_BUYER_PAY / WAIT_SELLER_SEND_GOODS / PAY_PENDING / SELLER_CONSIGNED_PART / TRADE_NO_CREATE_PAY / WAIT_BUYER_CONFIRM_GOODS)→ 逐单调 fullinfo 拿 modified → 重拉 modified±3s 增量重推 MQ,让下游把卡住的状态刷过来。不直接写库
默认窗口start = 今天00:00 − backDay,end = start + intervalDay(默认 backDay=30 / intervalDay=5,即「30天前~25天前」,直接定时跑等于白跑)
定时参数{"backDay":1,"intervalDay":2,"statusList":[…]} → 覆盖昨天全天+今天凌晨
注意很重(实测约 0.66s/单,一天未完结单几千~几万);skip 分页 + 游标丢失高危;sellerIdList 类型是 List<Integer>,13 位 sellerId 会溢出,别用
什么时候需要刷状态? 补回来的单下载时状态已是最新,通常不用跑;只有「单在、但状态长期停在 WAIT_*」才需要。大促日一天几万单未完结,建议跳过或把 statusList 缩到两三个关键状态。

5生产配置:排期与参数

以「每天凌晨 01:00 前前一天订单必须齐」为 SLA 设计的排期。xxl-job 用 Quartz 六位 cron(秒 分 时 日 月 周)。

5.1 推荐排期总表

时间Jobcron参数
每 10 分钟TaobaoIncrementOrderJobHandler0 0/10 * * * ?{"incrementSeconds":1200,"backSeconds":300,"intervalSeconds":600,"pageSize":40,"shopThreadNum":5,"needFullInfo":true,"sleep":500}
00:30TaobaoIncrementFullInfoOrderJobHandler0 30 0 * * ?{}(默认昨天)
00:40TaobaoIncrementCountCompensateJobHandler0 40 0 * * ?{}(默认昨天)
00:55核对(脚本/只读 Job,见第 7 节)0 55 0 * * ?对比昨天两张表 count,不等告警
04:00TaobaoIncrementStatusCompensateJobHandler0 0 4 * * ?{"backDay":1,"intervalDay":2}(statusList 可省略)
稳态参数的安全余量 每 10 分钟调度 × 净推进 15 分钟(1200−300)= 每轮多追 5 分钟,留有容错。若 cron 是 30 分钟,incrementSeconds 应提到 2700~3000。改频率后记得核对这条不等式:incrementSeconds − backSeconds > cron 间隔。

5.2 xxl-job 侧设置

5.3 这套排期能保证什么、不能保证什么

能保证(常规日)主抓单 10 分钟延迟内追单;00:30 兜住全量表偶发缺单;00:40 兜住增量表漏段;00:55 有核对有告警;01:00 前大概率齐。
不能保证① 列表接口不返回的单(要靠菜鸟反查,见 8.4);② 大促小时(4 万单/小时会让兜底 Job 翻几十页游标丢失);③ 服务宕机期间的单(恢复后需手工补);④ 老单晚到造成的历史日期回涨(兜底只跑「昨天」抓不到,见 9)。

6补数手册

按「缺什么」走对应分支。所有 Job 幂等,重复跑安全;时间参数一律北京时间;同一天只跑一个补偿 Job,不要并发。

补数决策:先定位缺在哪张表,再选工具 核对(第7节) 按北京天对比两张表 增量表缺(接口 > 增量)→ 场景 A 增量有 / 全量缺 → 场景 B 两表都没有 + 菜鸟单有 → 场景 C 历史日期缺口回涨 → 场景 D A:Count 补偿,按天拆窗口 {"startTime":"…","endTime":"…","intervalMinute":60,"sleep":3000} B:FullInfo 补偿,大缺口按店拆 / intervalMinute 降 15 {"startTime":"…","endTime":"…","intervalMinute":60,"sleep":1000,"sellerId":"…"} C:单号直补 HTTP 接口(列表接口碰不到的单只有这条路) POST /api/order/fullInfo {"tid":"…","sellerId":"…"} D:FullInfo 补偿按「created 覆盖回涨日期」重跑一遍 窗口 = 缺口日期 00:00 ~ 次日 00:00(老单会被一并对上) 顺序原则:先 A 后 B(增量表是全量补偿的数据来源);C/D 视核对结果单独处理。补完永远回到核对步骤验收。
图 6-1 补数决策流程

6.1 场景 A:补增量表

识别:接口数量 > 增量表(Count 补偿日志「时间段异常, api数量:x, db数量:y」,或核对脚本报增量缺)。

Job:  TaobaoIncrementCountCompensateJobHandler
按天跑(缺口大再拆半天):
{"startTime":"2026-10-18 00:00:00","endTime":"2026-10-19 00:00:00","intervalMinute":60,"sleep":3000}

6.2 场景 B:补全量表

识别:增量 = 全量 不成立且「增量 > 全量」。

Job:  TaobaoIncrementFullInfoOrderJobHandler
小缺口(<1000 单)整段跑:
{"startTime":"2026-10-16 00:00:00","endTime":"2026-10-17 00:00:00","intervalMinute":60,"sleep":1000}

大缺口(>5000 单 或 含大促小时)按店铺拆 + 15 分钟切片:
{"startTime":"2026-10-20 00:00:00","endTime":"2026-10-21 00:00:00","intervalMinute":15,"sleep":1000,"sellerId":"808734231"}

6.3 场景 C:单号直补(列表接口碰不到的单)

识别:订单号在两张表都查不到,但菜鸟单据表里有(taobao_wlb_wms_cainiao_bill.trade.orderSourceCodes 包含该单号);或核对时差集算出了具体 tid。

单笔:
POST /api/order/fullInfo
{"tid":"3316521615004026973","sellerId":"2206567129287"}

批量(每批 50~100,body 为数组):
POST /api/order/fullInfo/list
[{"tid":"…","sellerId":"…"},{"tid":"…","sellerId":"…"}]
注意 该接口走 fullinfo 路由,只写全量表,不写增量表(增量表没有单号级入口)。执行前确认店铺 token 有效;返回的 Trade 里看 type 字段,若是不在 TaobaoApiClient.orderType 过滤串里的类型,正是它不出现于列表接口的原因(见 9.1)。

6.4 场景 D:老单晚到 / 历史缺口回涨

识别:已经清零的历史日期,几天后核对又出现小缺口(如 10-16 从 12 单回涨到 153 单)——modified 抓单把「创建早、最近才修改」的单补进了增量表。

Job:  TaobaoIncrementFullInfoOrderJobHandler
直接按 created 覆盖回涨日期重跑(老单会被一并对上):
{"startTime":"2026-10-16 00:00:00","endTime":"2026-10-17 00:00:00","intervalMinute":60,"sleep":1000}

日常预防:每周手动跑一次「最近 7 天」窗口(intervalMinute=60),比每日只兜「昨天」更能收敛这类回涨;根治方案见 9.2。

6.5 补完必验收

回到第 7 节的核对方法,目标口径:每个北京天 × 每个 sellerId,增量表 count = 全量表 count。差集为空即收工。

7核对与验收

唯一可信的验收方式:直接查 Mongo 对 count。这里给出可直接复用的口径与脚本。

7.1 时区规则(最容易踩的坑)

Job 参数 ≠ 库内时间 Job 的 startTime/endTime 按北京时间解析(JVM 默认时区);Mongo 里存的是 UTC。所以查库时:北京 10-18 全天 = 库内 [10-17 16:00, 10-18 16:00)。
真实案例:曾用库内读到的 07:20 直接当参数去补,实际窗口偏了 8 小时,扫到的全是别的订单,目标单纹丝不动。换算口诀:库内 UTC + 8 = 北京时间 = Job 参数。

7.2 逐日核对脚本(北京天 × 店铺)

# 以卖家 808734231、北京 10-20 为例(窗口 = 库内 [10-19 16:00, 10-20 16:00))
inc = db.tradesold_taobao_increment.count_documents({
  "sellerId": 808734231,
  "trade.created": {"$gte": ISODate("2026-10-19T16:00:00Z"),
                    "$lt":  ISODate("2026-10-20T16:00:00Z")}})
full = db.tradesold_taobao.count_documents({
  "sellerId": 808734231,
  "trade.created": {"$gte": ISODate("2026-10-19T16:00:00Z"),
                    "$lt":  ISODate("2026-10-20T16:00:00Z")}})
# inc == full 即齐;inc > full 走场景 B 补全量

7.3 差集定位(找出具体缺哪些单号)

ids = [x["_id"] for x in db.tradesold_taobao_increment.find(
  {"sellerId": 808734231,
   "trade.created": {"$gte": ISODate("2026-10-19T16:00:00Z"),
                     "$lt":  ISODate("2026-10-20T16:00:00Z")}}, {"_id": 1})]
have = set(x["_id"] for x in db.tradesold_taobao.find(
  {"_id": {"$in": ids}}, {"_id": 1}))
missing = [t for t in ids if t not in have]   # 具体缺失 tid,走场景 C 直补

大促小时(单小时 >1 万)建议加 maxTimeMS 并分批 $in,避免全表扫描超时。

7.4 在售店铺 sellerId 对照(2026-10 实测)

sellerId店铺sellerId店铺
808734231kuyura可悠然旗舰店2206567129287第一三共海外旗舰店
2582702075anessa安热沙官方旗舰店458450809MINON蜜浓海外旗舰店
328672609fino芬浓旗舰店61991006威戈官方旗舰店
1771000166senka珊珂官方旗舰店351961830tmr海外旗舰店
342149215uno吾诺旗舰店314684773水之印海外旗舰店

店铺增减以 tradesold_snapshot_shop_config / admin 配置为准;上表是 2026-10 大促期间有单量的主集合。

8排障手册

按症状索引。每条都来自 2026-10 的真实事件,附判据与处置。

8.1 症状速查表

症状(日志关键词)根因处置
MongoCursorNotFoundException: Cursor … not found(错误码 -5) 分片集群(mongos)+ skip 深分页 + 高负载:游标被 shard 重启 / chunk 迁移 / 超时回收。大促小时单小时 3~4 万单,FullInfo/Status 补偿按 1000/页要翻 30~40 页,高危 重跑安全(幂等);预防=按店铺拆 + intervalMinute 降到 15~30;卡死的大促小时先算差集走单号直补
ApiException: IOException: 500 Internal Server Error(BatchFeature / BatchTaobaoClient 栈) 批量接口 /router/batch 上游 5xx(2026-10-19 03:35~05:00 全店复现;单接口 /router/rest 同期正常) 停用走批量的老 Job(CompensateJob / StatusCompensateJobhandler);改走单接口的新链路。网关恢复验证:GET /router/batch 返回标准淘宝错误 XML 即活
某店水位长时间不动(窗口终点不变) 该店某切片持续失败 → 整店水位不推进(设计如此);或 xxl-job 阻塞策略把上轮顶掉了 看该店最近一轮报错;确认阻塞策略「单机串行」、超时 0;积压多时用追赶参数(incrementSeconds 7200 + 10 分钟内多次触发)
数量补偿报「时间段异常」但补偿后 db 始终 0 下游消费者没在写这种类型/该店;「补偿数量」只统计推送成功,不等于落库 查 MQ 消费者与队列堆积;确认店铺抓单开关(admin 配置);不要反复重跑浪费配额
FullInfo 补偿永远 SUCCESS 但缺口还在 该 Job 无失败分支,返回值不代表结果 以 7.2 count 为准;差集定位后按场景 B/C 处理
补数窗口跑了但目标单没进来 最常见:时区差 8 小时(参数该用北京时间);或该单属于「列表接口不返回」(8.4) 按 7.1 换算重跑;仍无则查菜鸟单反查(8.4)走单号直补

8.2 Mongo 游标丢失(-5)详解

两个 Job 先后中招(10-19 Status 补偿第 5 页、10-21 FullInfo 补偿 10-15 20:00 大促小时的第 8 页)。共同点:分页查询发出后 1 秒内失败,说明游标刚建就被服务端判不存在——环境问题而非代码逻辑错误。让 DBA 查对应时段 mongos / shard 日志(restart、stepDown、moveChunk)可坐实具体诱因。

大促小时抢救流程(实测有效) ① 用 7.3 算出该小时具体缺的 tid(通常只有个位数);② 逐单 /api/order/fullInfo 直补;③ 数量相等后重跑补偿,该小时会被直接跳过,不再翻几十页。

8.3 批量接口 500 事件复盘(2026-10-19)

1
03:35 CompensateJob 失败对账正常(kuyura 接口 5980 / 库 0),一进补偿抓单全店每页 5 连 500,任务 code=500。
↓
2
05:00 StatusCompensateJobhandler 失败同样批量栈 500,4 秒结束;同刻单接口路径正常。
↓
3
12:57 单接口链路成功TaobaoIncrementCountCompensateJobHandler(/router/rest)一次跑通,印证故障只在批量链路。
↓
4
结论老 Job 双双停用;补数一律走单接口新链路。老 Job 的重试参数(3 次 × 3 毫秒)对瞬时故障无效,停用前也扛不住。

8.4 四类漏单:只有一类能被自动兜住

四类漏单与兜底覆盖情况 ① 整段缺(服务宕机) 水位断档 / 一段时间没抓 数量对账可发现 → Count 补偿 ✅ → FullInfo 补偿 ✅ ② 零星缺(推送丢失) 数量恰好相等但个别单没落库 增量vs全量 count 可发现 → FullInfo 补偿 ✅ (单号直补 ✅) ③ 老单晚到 created 早 / modified 晚 历史日期缺口回涨 → 每日兜底抓不到 ⚠️ → 每周滚动窗口 ✅ ④ 列表接口不返回 列表 total 不含它,两表皆空 数量对账天然失明 → 自动兜底全失效 ❌ → 只能菜鸟反查 + 直补 第④类的实证(2026-10-21):单号 3316521615004026973 两表皆无,菜鸟出库单 LBX03228725662334037 的 orderSourceCodes 指向它; 用菜鸟单(orderType=201,modifiedTime 索引)反查 10-15 以来出库单的 2558 个订单号,1586 个两表皆无——按同店单号邻居估算,约 800 单创建于两天以前,属真漏。 排查入口: db.taobao_wlb_wms_cainiao_bill.find({"trade.orderSourceCodes": "目标单号"}) // 反查该单是否有仓单 批量:按 trade.modifiedTime 拉窗口 → 收集 orderSourceCodes → 对两表求差集 → /api/order/fullInfo/list 直补
图 8-1 四类漏单:兜底覆盖矩阵与菜鸟反查

8.5 2026-10 事件时间线(供复盘对照)

时间(北京)事件影响与处置
10-10 16:00服务宕机全量表断写 4 天;11 家店约 4.2 万单缺口,恢复后分批补齐
10-17 深夜增量抓单停摆10-18/10-19 单量暴跌(2993/164);定位为主任务水位停滞
10-19 03:35~05:00批量接口 500两个老 Job 全店失败;12:57 单接口链路验证成功,老 Job 停用
10-19 12:57Count 补偿跑通 10-17/10-18增量表恢复;期间发现参数时区坑(UTC+8)
10-20~10-21大窗口追赶 + FullInfo 分店补水位追平;10-15 20:00(40749 单/小时)触发游标丢失,改用「差集直补 2 单 + 重跑跳过」
10-21菜鸟反查发现第④类漏单1586 单两表皆无;确认自动兜底覆盖不了,走单号直补 + 根治建议(第 9 节)
10-22全链路清零10-10~10-21 全部日期 增量=全量;仅当日 2 单为「刚落库待全量」的正常延迟

9已知局限与根治建议

现行体系的三处结构性短板与对应的改造方向,按投入产出排序。

9.1 第④类漏单无自动兜底 现象:列表接口不返回的单(疑似 type 不在 orderType 过滤串、或 step 定金尾款子单等),两表皆空且数量对账失明。

根治:新增「菜鸟单 → 订单」兜底 Job——按 trade.modifiedTime 拉当日出库单,收集 orderSourceCodes 对两表求差集,缺的按单号调 fullinfo;同时把直补拉回的 trade.type 补进 TaobaoApiClient.orderType,让主抓单以后能自己抓到。
9.2 老单晚到靠人肉滚动窗口 现象:FullInfo 兜底按 trade.created 对账,每日只跑「昨天」,抓不到历史日期回涨。

根治:把对账字段从 trade.created 改成 createTime(增量表写入时间)——不管订单哪天下,只要新写进增量表就能兜住。小改动,收益大。过渡方案:每周手动跑一次最近 7 天。
9.3 深分页在大促小时脆弱 现象:skip 分页 + 分片集群,单小时几万单时翻 30~40 页,游标丢失概率高;失败后整轮重来。
根治:补偿 Job 改 keyset 分页(记录上页最后一条 trade.created,下页从其后取);单条处理加 try/catch 不中断整批;捕获 MongoCursorNotFoundException 从断点续跑;页大小降到 200~500。
监控建议(配合 00:55 核对) ① 每日 00:55 对「昨天」跑 7.2 脚本,增量≠全量 或 接口数≠增量 即告警;② 监控主抓单水位滞后量(now − 水位),超过 1 小时告警;③ 监控 04:00 状态补偿的单日耗时趋势,超过 2 小时说明未完结单在堆积。

10FAQ 16 问

按提问频率排列,答案尽量一句话给结论。

1. 补偿 Job 的日志「补偿数量 36/39」是什么意思?

成功推送了 36 条消息 / 发现缺口 39。「推送 ≠ 落库」,下游写库有延迟,最终看 count。个别差异也可能来自消息重复/合并,差 1~3 属正常。

2. 为什么 FullInfo 补偿永远显示成功?

代码里没有失败分支,恒返回 SUCCESS。它只是「跑完了」,不代表补齐了——验收必须回到 count 对比。

3. 主抓单要不要传时间参数?

不能传。它没有时间参数,窗口完全由水位算出。要补历史只能用 Count/FullInfo 补偿 Job。

4. 主抓单水位卡住怎么办?

先看该店最近一轮日志哪个切片在报错(水位只在整店成功后推进)。排掉错误后若积压多,用追赶参数 incrementSeconds=7200 + 10 分钟内多次触发。

5. 一次把 incrementSeconds 开到 60000 行不行?

行但有代价:单轮跑几十分钟,xxl-job 超时要设 0、阻塞策略单机串行;中途失败该店整段重来。更稳的做法是 7200 × 多次触发,失败只损失 2 小时进度。

6. 补偿时 startTime/endTime 只填一个行吗?

不行。判断是「或」,只填一个两个都会被重置为默认窗口(昨天),等于没填。

7. 为什么我补了,目标单还是没进表?

三查:① 时区——参数要北京时间,库内 UTC+8;② 该单是否属于第④类(列表接口不返回,只能单号直补);③ 补偿 Job 窗口是否真覆盖该单的 created。

8. 老的 CompensateJob / StatusCompensateJobhandler 还能开吗?

不能。走批量接口(已两次全店 500)、与新链路重复、绕过 MQ 直接写库。功能被 Count/FullInfo/Status 三个新 Job 完全覆盖。

9. 游标丢失(-5)会丢数据吗?

不会。只是当轮翻页中断,已处理的部分有效;重跑幂等。反复卡在同一小时就走「差集直补 + 重跑跳过」。

10. intervalMinute 越小越好吗?

不是。切片越多 count 调用越多。常规 60;大促小时 15~30;FullInfo 补偿单店小缺口 60 足够。

11. StatusCompensateJobHandler 的 intervalDay 是什么?

窗口总长度(不是往前天数)。start = 今天−backDay,end = start+intervalDay。定时用 {"backDay":1,"intervalDay":2} 覆盖昨天+今天凌晨。默认 30/5 等于只跑 25~30 天前,定时必踩坑。

12. sellerIdList 能用来只跑一家店吗?

Status 补偿的 sellerIdList 是 List<Integer>,13 位店铺号会溢出,别用。单店过滤用 FullInfo 补偿的 sellerId(字符串),或者临时在 admin 关其他店抓单开关(影响正常抓单,慎用)。

13. 怎么判断「今天主抓单活着」?

查增量表最近 1~2 小时 createTime 的写入量;或看主抓单窗口终点是否贴近当前时间。持续为 0 = 停摆。

14. 全量表偶发缺几单正常吗?

刚落库 1~2 分钟内的单(self 消费者还没处理到)属正常延迟;超过半小时还缺就是真缺,走场景 B/C。

15. 大促日要提前做什么?

主抓单 cron 提到 5 分钟;00:30 兜底改按店拆 + intervalMinute=15;00:55 核对后立即手工补,不等到第二天;04:00 状态补偿可跳过。

16. 菜鸟单据和订单表怎么互查?

订单号查仓单:db.taobao_wlb_wms_cainiao_bill.find({"trade.orderSourceCodes": "单号"});仓单反查漏单:按 trade.modifiedTime 拉窗口收集 orderSourceCodes 对两表求差集(8.4)。

11速查卡

打印贴工位。全链路只此三步:核对 → 补 → 验收。

① 核对(北京天 × 店铺)

  • 口径:trade.created,北京 10-D 全天 = 库内 [10-(D-1) 16:00, 10-D 16:00)
  • 齐:增量 count = 全量 count
  • 增量缺 → 场景 A;全量缺 → 场景 B;两表无+菜鸟有 → 场景 C

② 补(幂等,可重跑)

  • A 增量:Count 补偿,按天 {"startTime":"…","endTime":"…","intervalMinute":60,"sleep":3000}
  • B 全量:FullInfo 补偿,大缺口按店拆 + intervalMinute:15,可加 sellerId
  • C 单号:POST /api/order/fullInfo {"tid":"…","sellerId":"…"}(只写全量表)

③ 红线与口诀

  • 时区:参数北京时间;库内 UTC + 8
  • 净推进:incrementSeconds − backSeconds > cron 间隔
  • 停用:CompensateJob / StatusCompensateJobhandler(批量接口)
  • 别信:FullInfo 的 SUCCESS、补偿的「推送数」——只信 count
一句话总结 主抓单按水位增量抓单写两张表;三个补偿 Job 分别按「接口数量 / 增量对全量 / 未完结状态」兜底;数据齐不齐只有一个判据——按北京天对 count;补数按「先增量后全量、单号直补兜底」三步走;列表接口碰不到的单靠菜鸟反查。

本文基于 thirdhub-scrapy-taobao 源码与 2026-10 生产事件整理 · 参数与 cron 以 xxl-job 实际配置为准 · 双击图表可放大