2. 离线回填核心概念:什么是离线回填?回填的触发条件,回填与实时计算的差异

2.1 到底什么是离线回填?

先说说我自己的理解。离线回填,说白了就是「补数据」。

你想想看,实时计算跑得好好的,突然发现昨天有个业务逻辑写错了。或者上游数据源出了故障,丢了两个小时的数据。这时候怎么办?实时流已经过去了,不可能倒流回去重新算。

离线回填就是干这个的——用批处理的方式,把过去某段时间的数据重新算一遍,然后把结果写回到存储里,覆盖或补充之前错误的数据。

我在项目中遇到过最典型的场景:某天凌晨,业务方说「昨天的实时大屏数据不对,GMV 少算了 300 万」。排查下来,是实时任务里有个维度关联的 UDF 函数写错了。修复之后,我们立刻启动了一个离线回填任务,把昨天 00:00 到 23:59 的数据重新跑了一遍。半小时后,大屏数据恢复正常。

核心定义:离线回填 = 用离线批处理引擎(如 Spark、Flink Batch)对历史时间段内的数据进行重新计算,并将结果写入目标存储,以修正或补充实时计算产出的数据。

2.2 回填的触发条件

不是所有情况都需要回填。我总结了一下,常见的触发条件就这几种:

  1. 代码逻辑错误——实时任务里的业务逻辑写错了,比如过滤条件不对、聚合函数用错、维度关联丢失。这是最常见的触发原因。
  2. 上游数据延迟或丢失——Kafka 主题挂了几个小时,或者数据源接口超时,导致实时流里缺了一段数据。
  3. 业务规则变更——业务方说「从今天起,优惠券的统计口径要改」。但历史数据也得按新口径重新算一遍,否则同比环比没法做。
  4. 数据质量校验失败——离线数仓和实时数仓对账发现差异超过阈值,需要回填修复。
  5. 存储迁移或表结构变更——换了新的 ClickHouse 集群,或者改了表的分区键,需要把历史数据重新写入。

我的经验:我曾经踩过一个坑——回填任务跑完后,发现实时任务还在继续写旧分区的数据,导致新旧数据混在一起。后来我养成了一个习惯:回填之前,先把实时任务的目标分区停掉写入,等回填完成后再恢复。这叫「写保护」,能避免数据覆盖冲突。

2.3 离线回填 vs 实时计算:到底差在哪?

很多人觉得,回填不就是把实时任务改成批处理跑一遍吗?其实没那么简单。我列个表,你一看就明白。

对比维度 实时计算 离线回填
计算引擎 Flink Streaming、Spark Streaming Spark Batch、Flink Batch、MapReduce
数据范围 当前时间窗口(秒级/分钟级) 历史时间段(小时/天/月)
延迟要求 秒级到分钟级 分钟级到小时级都可以接受
状态管理 依赖 Flink State / RocksDB 无状态,每次全量或增量读取
数据一致性 At-least-once / Exactly-once Exactly-once(通过幂等写入)
资源消耗 长期占用资源 临时申请,跑完释放
典型场景 实时大屏、实时风控、实时推荐 数据修复、口径变更、历史对账

嗯,这里要注意一个关键点:实时计算是有状态的。比如你要算过去 7 天的 UV,Flink 会在 State 里维护一个窗口。但离线回填没有这个状态,它直接从原始数据源(比如 Kafka 或 HDFS)重新读一遍,然后从头算起。

为什么会这样?因为离线回填不需要「增量维护」,它只需要「全量重算」。你想想看,如果回填也要维护状态,那跟实时任务就没区别了,反而更复杂。

2.4 离线回填的典型流程

我画了一张流程图,帮你理清回填的整体脉络。

离线回填典型流程 触发条件 确定回填时间范围 选择原始数据源 启动批处理任务 写入目标存储 校验数据一致性 注意 回填前先停掉实时任务 对目标分区的写入。 回填完成后恢复写入。 避免数据覆盖冲突。

流程看起来简单,但每个环节都有坑。比如「确定回填时间范围」这一步,我曾经遇到过业务方说「从上周一开始回填」,结果发现上周一的数据源已经因为过期被删除了。所以回填之前,一定要先确认数据源还在不在。

2.5 离线回填的两种模式

根据我的经验,离线回填通常有两种模式:

  • 全量回填:把整个历史数据重新算一遍。适合数据量不大、或者第一次上线时做基线校正。缺点是慢,资源消耗大。
  • 增量回填:只回填出问题的那段时间。比如只回填昨天一天的数据。速度快,资源省,但需要保证回填的数据和已有数据能无缝拼接。

避坑指南:我曾经在增量回填时犯过一个低级错误——回填任务写数据时用了 INSERT OVERWRITE,结果把整个分区的数据都覆盖了,包括之前正确的数据。后来我改成了 INSERT INTO + 去重逻辑,才彻底解决。记住:回填写入一定要用幂等操作,要么覆盖精确分区,要么用 upsert 语义。

2.6 回填与实时计算的协作关系

很多人以为回填和实时计算是互斥的,其实不是。它们更像是「搭档」。

实时计算负责「快」,离线回填负责「准」。实时任务先出数据,保证业务能实时看到。如果发现数据有问题,离线回填再出手修复。最终,实时数据和回填数据会合并成一份「最终一致」的数据。

我常用的做法是:实时任务写实时表,回填任务写回填表,然后通过一个合并视图对外提供服务。这样实时表可以随时重建,回填表也能独立维护,互不干扰。