重复数据从哪来
重复几乎不可避免,来源主要有三类:上游系统重发(接口重试、日志采集器重读文件)、消息投递语义为至少一次(故障重放)、以及任务重跑(补数或失败重试)。与其要求上游不重复,不如在下游把去重做成常规能力。
批侧与流侧的差异
批处理有完整数据集,去重直接按业务主键排序取最新一条即可;流处理看不到全集,只能在状态里记住「这个键处理过没有」,因此必须给状态设置过期时间,否则状态会随 key 基数无限增长,最终拖垮作业。
基本信息
| 批处理去重 | 按主键分组排序取第一条(最新状态) |
|---|---|
| 流式去重 | 按主键维护状态并设置 TTL,或用去重算子 |
| 兜底手段 | 落库端按主键 upsert,无论上游重复多少次结果一致 |
| 关键前提 | 去重键必须是完整的业务主键 |
| 代价 | 流式去重会保留状态,TTL 越长内存占用越大 |
去重位置怎么选
| 去重位置 | 优点 | 局限 |
|---|---|---|
| 上游采集端 | 减少下游数据量 | 难以覆盖所有重复来源 |
| 加工层 SQL | 逻辑清晰、可重跑验证 | 只对已到达的数据生效,无法防未来重复 |
| 落库端主键 upsert | 最靠近存储,兜底效果最好 | 需要唯一键与索引支撑 |
| 流式状态去重 | 能挡住重复事件进入下游 | 状态成本高,必须设置过期时间 |
典型使用场景
- CDC 链路:连接器重启后位点回退,同一变更事件会重复投递
- 日志采集:采集器文件轮转或重启后重读末尾部分内容
- 接口拉取:网络超时后重试,实际已成功写入的数据被再拉一次
- 任务重跑:失败重试后同一分区被处理两遍
- 多来源合并:两个上游系统上报同一业务实体的记录
SQL 去重取最新
SQL 去重取最新
-- 按业务主键取最新一条(批处理最常用)
SELECT *
FROM (
SELECT o.*, ROW_NUMBER() OVER (
PARTITION BY order_id ORDER BY updated_at DESC) AS rn
FROM ods_order o
) t
WHERE rn = 1;Flink SQL 去重与状态过期
Flink SQL 去重与状态过期
-- 流式去重:按主键保留第一条,其余丢弃(配合状态 TTL 使用)
SELECT order_id, amount, ts
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY ts ASC) AS rn
FROM orders_src
) WHERE rn = 1;
-- 关键:不给状态设置过期时间,去重状态会随 key 基数无限增长
SET 'table.exec.state.ttl' = '24h'; -- 超过该时间未更新的状态会被清理去重键必须覆盖完整业务主键:用组合主键去重的表只取其中一列,会把不同业务实体的记录误判为重复并删除掉。
流式去重状态不设 TTL 是最常见的生产事故来源:任务前几周运行正常,key 基数上来后状态膨胀,checkpoint 与恢复时间同步失控。