先打个广告,欢迎了解与试用腾讯实时计算团队打造的 Oceanus:基于Apache Flink的一站式实时计算平台
好久不更新,这篇我们来聊聊Flink检查点的失败处理逻辑重构以及后续将会引入的更灵活的失败处理与恢复相关的改进机制。与此相关的issue列举如下:
FLINK-4810: Checkpoint Coordinator should fail ExecutionGraph after "n" unsuccessful checkpoints
FLINK-10074: Allowable number of checkpoint failure
FLINK-10724: Refactor failure handling in check point coordinator
FLINK-11159
前三个issue上个月已经向社区提交了设计文档(见下文),经过(dA公司的Andrey Zagrebin以及Kostas Kloudas)多次review,该设计文档已基本定稿,重构的工作正在进行,而FLINK-11159是一个独立的issue,涉及到优化恢复的过程。接下来我们先简单介绍一下设计文档的大致内容,然后再谈谈FLINK-11159。
重构CheckpointCoordinator
CheckpointCoordinator
的重构是改进检查点失败处理机制的基础。重构的动机有如下两点:
目前
CheckpointCoordinator
失败处理的逻辑很散乱。它们散布在CheckpointCoordinator
以及PendingCheckpoint
中,这使得一些新需求的实现与现有代码的维护代价都非常高昂。当前Job容忍检查点失败的实现处于TM端,而不是
CheckpointCoordinator
所在的JM端,这使得Coordinator的职责被削弱,从而对检查点的一些行为“失去控制”,也使得在实现FLINK-4810的语义上出现了歧义。
简单而言,我们可以将检查点的整个过程划分为两个阶段:触发与执行。
在触发阶段,Flink当前有 CheckpointTriggerResult
和 CheckpointDeclineReason
这两个类来描述“触发结果”与“取消原因”。因此,重构的第一件事就是对于执行阶段,我们也引入 CheckpointExecuteResult
以及 CheckpointAbortReason
这两个类来描述“执行结果”与“终止原因”。在 CheckpointAbortReason
中,我们将尝试对检查点所有中止/终止的原因以枚举的形式进行归类定义。在这一步完成之后,就可以将 PendCheckpoint
中很多的 abortXXX
方法重构为一个方法,形如:
public void abort(CheckpointAbortReason reason, Throwable cause) {
try {
onCompletionPromise.complete(CheckpointExecuteResult.failed(reason, cause));
reportFailedCheckpoint(cause);
assertAbortSubsumedForced(reason);
} finally {
dispose(true);
}
}
然后,我们会重构Flink原先的容忍检查点失败的逻辑,让 CheckpointCoordinator
重新掌握对检查点绝对的控制权。
引入CheckpointFailureManager
在第一步重构完成之后,我们将会引入一个新的管理控制类 CheckpointFailureManager
来集中管理检查点的失败处理逻辑,并实现 连续容忍多少次检查点失败才会让Job失败 的功能。这种失败处理方式,使Flink原先要么容忍要么不容忍的二元逻辑变得更具弹性,它能够适应:要么容忍(Long.MAX_VALUE),要么不容忍(0),要么容忍若干次(N)。
为此,我们会引入一个原子的失败计数器来实现它。借助于第一步重构中引入的 CheckpointExecuteResult
,我们将会对检查点两个阶段的失败来应用计数器,预期将会引入两个方法:
handleCheckpointTriggerResult
handleCheckpointExecuteResult
成功的检查点只有一种,失败的检查点却失败得千差万别。那么我们需要明确到底什么样的“失败”,才是我们认为的“有效失败”。这里我们需要枚举两个阶段所有的“失败原因”并判断我们是忽略(计数器维持不变)还是确认(计数器+1)。
目前,对每个原因的判定如下:
For CheckpointDeclineReason:
COORDINATOR_SHUTDOWN: ignore
PERIODICSCHEDULERSHUTDOWN: ignore
ALREADY_QUEUED: ignore
TOOMANYCONCURRENT_CHECKPOINTS: ignore
MINIMUMTIMEBETWEEN_CHECKPOINTS: ignore
NOTALLREQUIREDTASKSRUNNING: ignore
EXCEPTION: counter +1
EXPIRED: ignore
For CheckpointAbortReason:
CheckpointExpired: ignore
CheckpointSubsumed: ignore
CheckpointDeclined: counter +1
CheckpointCoordinatorShutdown: ignore
CheckpointCoordinatorSuspend: ignore
CheckpointUncertainError: counter +1
这里我们还需要界定一下我们所关注的“失败”的范围。目前,我们只关注检查点本身,不关注一些外部不可控因素(例如JM停止等原因触发的检查点中止)。另外,这个判定也许跟最终的实现略有差别。
恢复选择Savepoint还是Checkpoint
有人觉得Savepoint没有什么用,推荐总是用Checkpoint,我并不赞同这种看法。其实,Savepoint的主要优势在于触发机制的灵活性以及多版本的兼容性,不受Flink Checkpoint周期性触发性质的约束,并对Checkpoint相对固化的模式提供了一种很好的补充。“在任一时间点触发”的优势在Checkpoint周期相对较长的Job中体现地非常明显。
我们有一类在Standalone集群上跑的Job,由于业务的特殊性,为了保证现网稳定,它很难接受高频次的Checkpoint行为,所以我们将它的周期设置为20分钟一次。如果由于一些特殊情况,我们必须立即暂停一个Job,假设我们总是以Checkpoint来作为恢复依据,那么就只能回退到上一次Checkpoint的位置,而这取决于我们暂停的时间点,如果它恰好出现在下一个Checkpoint之前,那么这个回退代价无疑相当得高,而回退的时间越长,造成更长时间的“追赶消费”,对上游消息中间件与计算引擎本身的稳定性都会带来影响。而通过“Cancel with savepoint”,我们则可以尽可能降低恢复时回退消费的成本。
虽然我们总是应该考虑在使用场景与通用性之间作出权衡,但是作为一个通用的计算引擎,我们并不应该尽可能地假设所有的场景下都采用高频的Checkpoint。
毫无疑问Savepoint是有存在价值的,但有人提了FLINK-11159这个issue,它指出了Flink现有的恢复机制在Checkpoint与Savepoint同时工作的情况下,有可能会存在“破坏副作用”的情况。
在计算机领域,所谓“副作用”指:一个函数除了返回值之外,还对外部主调函数产生了附加影响。简单地理解就是导致了外部世界状态的改变。
举个例子,参考时间线上有这样一个序列 cp1->cp2->sp1
,如果sp1是最后一个,而恢复的时候基于cp2,那么 cp2->sp1
之间的“副作用”就会被“重放”。虽然基于Checkpoint机制,它可能并不会影响Job的正确性,但在一些场景下不必要的副作用被重放有时不可忽略。考虑一个场景,如果 cp2->sp1
之间网络有一波小的流量高峰,它可能是由一个持续几分钟的“秒杀”业务造成的,也就是说这个阶段单位时间的数据量要比常规阶段高很多,如果我们以cp2的时间点作为恢复依据,那么在恢复的过程中它必然还会经历这波高峰,它就有可能对整个集群的稳定性造成影响(副作用),而如果以sp1作为恢复依据,则可以跳过它。
继续回到FLINK-11159这个issue,但这个issue在讨论时有点被带偏了。它原本是想讨论是否可以给用户一个配置,来基于更近的Savepoint恢复。当然Savepoint主要的一个问题就是在恢复阶段不及Checkpoint高效。但讨论中提出了一个很好的观点:我们是否可以允许用户在完成一次Savepoint的时候,将快照同时保存一份Checkpoint的格式。这种模式类似“任意时刻的Checkpoint”功能。这是一个优化配置项,当用户希望以更快,更高效的方式来暂停并恢复Job(比如调整并行度)时,这种方式是比较好的选择,很显然这是典型的“空间换时间”的优化形式(当然它需要额外付出的代价是,Savepoint做完的时间可能会比原来长一些,这里我们可以选择以异步写Checkpoint格式数据的方式来尽量降低它的代价,甚至可以不保证它最终的结果)。
这个issue的设计文档目前我仍然在编写中,但无论对内部还是对社区而言,我都认为这个issue很有价值。
CheckpointCoordinator重构与失败处理逻辑改进的设计文档如下。请不要在意英文表述等非核心问题,如果有兴趣的话,将就着看看吧。