本文是《深入理解 Apache DolphinScheduler:从调度原理到 DataOps 实战》系列专栏的第五篇,它会依据 Apache DolphinScheduler 讲述,去解析调度系统里的失败重试、手动重跑以及补数回填机制,弄清楚调度语义里 Exactly Once 的意思,并且总结常见的误用场景和实践建议,以此助力构建稳定可靠的数据调度体系。
于数据平台平常的运行期间,任务失败差不多难以避免。网络出现抖动,存在资源不足的状况,下游依赖出现异常,代码有 Bug 等,均有可能致使调度任务执行失败。面对失败之际,许多团队一般依靠自动重试,或者手动重跑,又或者补数回填来恢复数据。
但一个经常被忽视的问题是:
在调度系统里存在着失败重试,还有手动重跑,另外还有补数回填,它们的语义实际上是全然不一样的。
一旦有所理解偏差,极易致使重复数据出现、数据位置错乱乃至数据遭受污染。当下本文会依据Apache DolphinScheduler的设计机制,深入剖析调度系统里最为常见然而却最易于被误解的三种能力,即失败时重试、手动重新运行以及补数后回填,并且会进一步探究调度系统中“Exactly Once”的实际意义。
一、失败重试 vs 手动重跑:两种完全不同的恢复机制
在调度系统中,失败任务通常有两种恢复方式:
自动重试(Retry)手动重跑(Rerun)
不少人觉得二者仅仅是触发的方式存在不同,然而事实上,在运作语义这个方面,它们有着本质性的差异。
1 自动重试:同一次实例的再次执行
Apache DolphinScheduler里,每一回调度都会产生一个Workflow Instance,也就是流程实例,此实例里涵盖多个Task Instance,也就是任务实例。
在某个任务失败之际,要是对Retry Times进行了配置,那么系统会于同一个任务实例的情况下,去触发自动重试呢。
其特点是:
执行流程示意:
自动重试的设计目标是:
解决瞬时失败(Transient Failure)
例如:
在这种情况下,自动重试通常可以快速恢复任务。
2 手动重跑:创建新的实例
与自动重试不同,手动重跑会生成新的实例。
于Apache DolphinScheduler里,用户能够进行挑选,可供选择的有:
此时,该系统就会去生成出一个全新的Workflow Instance。
示意:
这表明,存在这样一种情形,即两个实例,有着去处理处于同一时间阶段的数据的可能性,并且,下游任务层面,存在着重复写入数据的可能性。
要是任务并非幂等(Idempotent)的,那么就有将会致使重复数据问题出现的可能性。
二、补数与回填:调度系统中的时间重建
于数据仓库场景里头,补数,也就是Backfill,属于一项极为常见的操作。比如说:
于Apache DolphinScheduler里,补数一般借由Backfill Run达成。
1 补数的本质:生成多个历史实例
假设一个任务是 每日调度。
补数区间:
2025-03-01 → 2025-03-05
系统会创建多个实例:
Instance (2025-03-01)
Instance (2025-03-02)
Instance (2025-03-03)
Instance (2025-03-04)
Instance (2025-03-05)
每个实例都有:
调度时间会被设置为历史时间。
2 补数的关键:调度时间 vs 执行时间
在调度系统中,有两个非常重要的概念:
调度时间(Schedule Time)
数据逻辑时间
执行时间(Execution Time)
任务实际运行时间
例如:
Schedule Time : 2025-03-01
Execution Time: 2025-03-10
如果 SQL 使用的是:
WHERE dt = ${schedule_time}
补数是安全的。
但如果使用:
WHERE dt = today()
补数就会产生 错误数据。
这也是很多数据问题的根源。
三、调度系统里的 Exactly Once,它的真实含义究竟是什么呢?
于流处理系统里头,就像 Apache Flink 这样的,Exactly Once 一般来讲意味着。
每条数据只被处理一次。
但在调度系统中,Exactly Once 的含义完全不同。
调度系统没法确保任务不会出现重复执行的状况,也不能够保证数据不会有被重复写入的问题。原因在于自动重试存在着可能重复执行的情况,手动重跑也存在可能性导致重复执行,并且补数会重复执行以往的逻辑。
因此,调度系统中的 Exactly Once 更接近于:
同一个调度时间只生成一个逻辑实例。
但任务本身仍然可能执行多次。
所以,实实在在的 Exactly Once 是需要任务的逻辑去确保幂等的。
常见实现方式包括:
1 覆盖写入
INSERT OVERWRITE TABLE
2 基于分区写入
partition dt='${schedule_time}'
3 去重写入
MERGE INTO
四、常见误用场景
很多数据事故其实都来自于对调度语义的误解。
1 使用当前时间作为数据日期
错误示例:
dt = today()
正确方式:
dt = ${schedule_time}
2 非幂等写入
例如:
INSERT INTO table
如果任务重跑:
数据会重复
3 手动重跑整个流程
很多用户习惯:
失败 → 从头重跑
但实际上更安全的方式是:
只重跑失败节点
五、最佳实践建议
与 Apache DolphinScheduler 的运用经历相结合,能够归纳出若干关键实践来 ,这些关键实践是重要的 ,是可以列举出来呈现各种情况的。
1 任务必须设计为幂等
所有任务都应该允许:
重复执行
不会影响数据正确性。
2 数据逻辑必须基于调度时间
避免使用:
now()
today()
统一使用:
${schedule_time}
3 合理使用重试策略
建议配置:
Retry Times: 1~3
Retry Interval: 1~5 min
避免无限重试。
4 补数要控制并发
补数区间过大时:
一次性生成大量实例
可能导致:
建议:
分批补数
结语
在数据平台里头,调度系统常常被视作仅仅是“任务触发器”,然而事实上,它肩负着时间管理的职责,承担着依赖控制的任务,还履行着故障恢复的核心责任。
理解失败重试的真实语义,理解手动重跑的真实语义,理解补数回填的真实语义,我们才能够真正构建稳定的数据生产系统,我们才能够真正构建可靠的数据生产系统。
形如Apache DolphinScheduler这一类型的现代调度系统,它已然给出过极为完备的机制。然而,最终对数据质量起到决定作用的,依旧是:
正确理解调度语义 + 设计幂等的数据任务。
唯有如此这般,数据平台才能够于遭遇失败之际,依旧维持着可恢复的状态,保持着可追溯的特性,留存着可重建的条件。
相关标签: # 调度系统 # 重试机制 # 补数回填 # ExactlyOnce # 幂等性