实时数据丢失怎么办?Iceberg批流一体实现数据离线重刷

0 阅读10分钟

本文为社区作者个人技术实践与思考分享,仅供参考,不构成官方意见。

一、实时数据丢失的困境

在企业实时数据处理场景中,数据通常需要经过数据库、CDC、消息队列、实时计算引擎等多个环节,最终写入数据湖或数据仓库。随着业务实时化程度不断提高,数据链路越来越长,实时任务的稳定性也面临更高要求。一旦 CDC 采集异常、消息消费出现问题、Flink 任务故障,或者下游存储写入失败,就可能出现数据延迟、遗漏甚至计算结果异常。

对于这类问题,真正棘手的并不是实时任务短暂出现故障,而是故障发生后如何快速、准确地完成数据补偿。传统方式通常是重新开发一套离线补数任务,根据异常时间范围查询历史数据,再重新执行计算逻辑并写回目标表。这种方式虽然能够解决数据修复问题,但随着业务规模扩大,实时链路和离线补数链路往往逐渐形成两套独立体系,不仅需要额外维护补数 SQL 和任务,也容易因为计算逻辑、数据源或者写入方式不同,造成实时结果与离线补数结果不一致。

因此,在实时数据处理架构中,除了关注数据实时写入能力之外,还需要考虑数据异常后的可追溯、可恢复和可重算能力。如果实时计算和离线计算能够共享统一的数据表和计算逻辑,那么出现异常后,就可以直接利用已有数据进行历史重算,而不需要重新搭建一套完全独立的补数体系。

这也是湖仓一体架构逐渐受到关注的重要原因之一。以 Iceberg 为代表的开放表格式,通过 Snapshot、Time Travel 和事务提交等能力,为实时数据的历史追溯和离线重算提供了更加统一的数据基础。

二、Iceberg批流一体的核心优势

Iceberg 本质上是一种开放的湖表格式,它通过表元数据组织数据文件、Schema、分区以及表的不同状态。当数据成功提交到 Iceberg 表后,通常会形成一个新的 Snapshot,用来描述这次提交之后的表状态。随着数据持续写入,一张表会逐步形成多个历史 Snapshot。

这里需要特别说明,Snapshot 并不是简单意义上的“快照文件”,也不能直接理解为“某一天的数据”。它代表的是某次提交完成之后整张表的数据状态。在历史 Snapshot 仍然被保留的情况下,Iceberg 可以通过 Time Travel 能力读取历史版本,从而实现数据回溯。例如,当实时任务出现异常时,可以根据任务运行记录、业务时间、分区信息以及历史 Snapshot 等信息,确定需要修复的数据范围,再利用历史数据重新进行计算。

相比传统数据表,Iceberg 的版本管理能力为数据补偿提供了更好的基础。实时任务正常运行时,数据持续写入 Iceberg;当某个时间范围的数据出现异常,可以利用历史数据进行重新计算,再将计算结果写回同一张 Iceberg 表。整个过程中,实时结果和离线重算结果不需要分别维护两套目标存储,而是围绕同一张表进行管理。

同时,Iceberg 支持事务提交和原子性的表操作,可以降低数据重刷过程中出现中间状态的风险。离线任务完成计算后再进行数据提交,成功后形成新的 Snapshot,后续查询基于新的表状态读取数据。

因此,Iceberg 批流一体的核心价值,并不是简单地把“批处理”和“流处理”合并成一个任务,而是通过统一的数据表和数据语义,让实时写入、历史查询以及离线重算能够在同一套数据基础上协同工作。对于需要频繁处理实时数据、同时又存在数据补偿和历史重算需求的企业而言,这种架构可以有效降低两套数据链路长期并行维护的复杂度。

三、离线重刷实现流程

基于 Iceberg 的统一表和历史版本能力,当实时任务出现数据遗漏、延迟或者计算异常时,可以通过 Flink 批处理任务对指定范围的数据重新计算,实现数据补偿。整个过程可以概括为:确定重刷范围、读取历史数据、批处理重算、结果写回以及新版本提交

首先,需要确定具体的重刷范围。实际生产环境中,不能简单地通过某一个 Snapshot 判断“哪一天的数据出现问题”,而是需要结合业务时间字段、分区信息、CDC 位点、任务运行记录以及数据质量检测结果等信息,定位真正需要修复的数据范围。例如实时任务在 10:00~10:30 出现异常,就需要根据实际业务数据判断这一时间范围内哪些数据受到影响。

确定范围后,可以将原有实时计算任务切换到 Flink 批处理模式执行历史重算。Flink 支持批处理和流处理两种运行模式,在批处理模式下,可以针对有界数据源执行历史数据计算。因此,原本用于实时数据处理的部分计算逻辑,在数据源、状态和写入语义满足要求的情况下,也可以复用于历史数据重算。

接下来,根据实际补数场景读取历史数据。如果只是某个时间范围的数据缺失,并且原始数据仍然完整,可以直接读取对应时间范围的数据进行重算;如果需要基于某一个历史数据状态进行恢复,则可以利用 Iceberg Time Travel 读取指定 Snapshot,再结合业务时间或者分区条件确定需要处理的数据。需要注意的是,Time Travel 提供的是历史表状态,而不是直接提供“某一天的数据”,因此实际重刷过程中仍然需要结合业务数据范围进行筛选。

完成历史数据读取后,Flink Batch 按照原有计算逻辑重新执行任务,并将结果写回 Iceberg。在目标表采用合理分区设计的情况下,如果所使用的计算引擎和写入方式支持相应的动态分区覆盖语义,可以只替换受影响的数据分区,而不需要重新处理整张表。数据写入成功并完成提交后,Iceberg 会形成新的 Snapshot,后续查询即可基于新的表状态获取修复后的数据。

通过这种方式,原本需要单独开发补数脚本的流程,可以逐步沉淀为标准化的数据恢复机制:实时任务负责日常增量处理,批处理任务负责异常情况下的历史重算,而 Iceberg 负责统一承载数据及其版本状态。

四、实践避坑指南

虽然 Iceberg + Flink 的批流一体模式能够有效解决数据重算问题,但在实际落地过程中仍然需要关注几个关键问题。首先是 Snapshot 的生命周期管理。Time Travel 并不意味着所有历史数据版本都会永久保留,企业需要根据实际的数据回溯需求、补数窗口、存储成本以及合规要求制定合理的 Snapshot 保留策略,否则历史版本被清理后,就无法继续通过 Time Travel 进行回溯。

其次是小文件问题。实时任务通常会持续产生数据文件,如果提交频率过高或者单次写入数据量较小,就可能产生大量小文件。小文件不仅会增加元数据管理开销,也会增加查询时需要打开和扫描的数据文件数量;在 HDFS 场景下,还可能进一步增加 NameNode 的元数据压力。因此,生产环境需要结合实际写入规模,通过定期的数据文件重写、合并以及 Snapshot 和孤儿文件治理等机制控制文件数量,而不是简单认为 Iceberg 会自动完成所有小文件治理。

此外,分区设计同样非常重要。合理的分区能够缩小实时查询和历史重刷的数据范围,但分区粒度也不能过细,否则容易产生大量小文件和过多分区。对于需要频繁补数的业务,更应该从业务时间字段、数据更新特点以及查询方式出发设计分区策略。

最后,还需要注意不同计算引擎和版本之间的能力差异。比如<font style="color:rgb(0, 0, 0);">INSERT OVERWRITE</font>、动态分区覆盖以及 Iceberg 的具体读写能力,都需要结合实际使用的 Flink、Spark、Iceberg 版本和 Catalog 配置进行验证。因此,批流一体并不意味着“实时任务可以无条件直接切换成批任务”,而是需要在数据源、计算逻辑、状态语义和写入方式等方面进行适配。

五、袋鼠云实时开发平台StreamWorks的批流一体实践

针对企业实时数据开发过程中实时任务开发、数据入湖以及异常处理等需求,袋鼠云实时开发平台 StreamWorks 提供了一站式的实时数据开发能力,并支持全链路 CDC、表结构演进和数据入湖等能力。

在实际应用中,可以将 StreamWorks 作为实时数据开发入口,将 Flink 等实时计算引擎与湖表存储能力结合起来,形成从数据采集、实时计算到数据入湖的一体化链路。当实时任务正常运行时,数据持续进入 Iceberg 等湖表;当任务出现数据遗漏或计算异常时,则可以结合历史数据和任务运行情况,通过批处理方式重新计算指定范围的数据,并将修复结果写回统一的数据表。

这种方式的核心并不是单纯增加一个“补数功能”,而是将数据开发、实时计算和历史数据处理放在统一的数据链路中进行管理。实时任务负责数据的持续处理,湖表负责统一的数据承载和版本管理,批处理则负责异常场景下的数据重算,由此形成从“实时处理”到“异常恢复”的完整闭环。

从企业数据架构的长期建设来看,实时数据处理真正需要解决的也并不只是“快”,而是数据在持续产生之后,能否做到可追溯、可恢复、可重算和可治理。通过 Flink 与 Iceberg 等技术结合,可以让实时数据和历史数据在统一的数据基础上进行协同处理,减少实时链路和离线补数链路重复建设带来的成本。

对于企业而言,当实时任务出现异常时,数据补偿不再只是临时性的人工操作,而可以逐步沉淀为标准化的数据工程能力。通过实时处理与批处理的协同,以及统一湖表的数据版本管理能力,进一步构建更加稳定、可恢复的数据处理体系,为企业后续的数据分析、数据资产管理以及 AI 应用提供更加可靠的数据基础。