--- id: backend.data-pipeline.scenario.third-party-sync title: 第三方平台订单同步:限流、断点续跑、迟到数据、对账和告警 type: scenario domain: backend stack: [data-pipeline, postgresql, redis, mq] tags: [第三方对接, 增量同步, 限流, 对账, 迟到数据, 告警] level: intermediate prerequisites: [backend.data-pipeline.reliability] est_hours: 2.5 related: [backend.data-pipeline.interview, backend.redis.lock-ratelimit] week: [14] --- # 第三方平台订单同步:限流、断点续跑、迟到数据、对账和告警 ## 业务背景与问题 公司为商家提供 SaaS 经营分析和 AI 报表助手,需要把商家在多个电商平台的订单同步过来。现状是一个简单的定时脚本,问题频发: 1. 平台接口限流,高峰期大量 429,脚本直接失败; 2. 脚本中途挂掉后从头再跑,产生重复订单; 3. 少量订单状态和平台对不上(退款了这边还是已支付); 4. 某次平台接口返回字段变更,脏数据写入了几个小时才被商家发现。 ## 约束 - 每个平台的限流规则不同,而且按“商家授权的应用”计算配额。 - 上千个商家,每个商家每 10 分钟同步一次。 - 平台没有提供变更推送,或者推送不可靠。 ## 方案设计 1. **任务拆分**:调度器每 10 分钟为每个“商家 × 平台”生成一个同步任务,投递到队列;多个工作进程消费;同一个商家同一时间只运行一个任务(分布式锁或队列分区)。 2. **限流**:按“平台 + 应用”在 Redis 中做令牌桶,所有工作进程共享;429 按 `Retry-After` 退避。 3. **断点续跑**:每个任务有自己的水位线(`updated_at + id`),本页数据落库后才推进;重叠窗口回拉 5 分钟处理迟到数据;upsert 带版本比较。 4. **数据质量**: - 字段级校验(类型、枚举、金额范围),坏数据进死信表并带原始报文; - 批次级检查:坏数据比例、与上周同时段的数据量对比; - 不通过就停止该平台的同步并告警,避免继续写入脏数据。 5. **对账**:每天凌晨按商家拉取前一天的订单汇总(数量、金额、各状态数量)与本地比对,差异自动补拉并记录。 6. **可观测**:每个任务记录拉取数、写入数、死信数、重试次数、耗时;面板按平台展示;连续失败、死信突增、对账差异超阈值时告警。 ## 关键代码 基于 Redis 的共享令牌桶(Lua 保证原子性): ```lua -- KEYS[1]=桶的 key ARGV: rate(每秒), capacity, now(毫秒) local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts') local rate, cap, now = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3]) local tokens = tonumber(b[1]) or cap local ts = tonumber(b[2]) or now tokens = math.min(cap, tokens + (now - ts) / 1000 * rate) local ok = 0 if tokens >= 1 then tokens = tokens - 1; ok = 1 end redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now) redis.call('PEXPIRE', KEYS[1], math.ceil(cap / rate * 1000) + 1000) return ok ``` 单进程版本的完整实现和测试:[examples/incremental-sync](../examples/incremental-sync/)。 ## 取舍 | 选择 | 优点 | 缺点 | | --- | --- | --- | | **队列 + 工作进程 + 共享限流(本方案)** | 可水平扩展、互不影响 | 需要队列和 Redis | | 单个定时脚本串行同步 | 简单 | 商家多了跑不完,一个失败影响全部 | | 引入 Airflow 等编排平台 | 可视化、重跑方便 | 学习和运维成本,对小团队偏重 | ## 易错点 - 限流计数放在进程内存中,多个工作进程加起来超过配额。 - 同一商家的两个任务并发运行,水位线互相覆盖。 - 平台字段变更时没有校验,脏数据持续写入。 - 只有“任务失败”告警,没有“数据量异常”告警:任务成功但拉回 0 条同样是问题。 ## 面试怎么讲 > “我们对接了多个电商平台的订单。调度器为每个商家生成同步任务进队列,工作进程消费;限流用 Redis 令牌桶按平台和应用共享;每个任务有水位线,落库后才推进,回拉 5 分钟重叠窗口处理迟到数据,upsert 时比较更新时间防止旧数据覆盖。字段校验不过进死信,坏数据比例和数据量异常都会停止同步并告警。每天凌晨按汇总对账,差异自动补拉。”