Files
zhuangang/topics/backend/data-pipeline/scenarios/third-party-sync.md
T

82 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 时比较更新时间防止旧数据覆盖。字段校验不过进死信,坏数据比例和数据量异常都会停止同步并告警。每天凌晨按汇总对账,差异自动补拉。”