82 lines
4.3 KiB
Markdown
82 lines
4.3 KiB
Markdown
---
|
||
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 时比较更新时间防止旧数据覆盖。字段校验不过进死信,坏数据比例和数据量异常都会停止同步并告警。每天凌晨按汇总对账,差异自动补拉。”
|