二十一、Flink容错机制之状态一致性
状态一致性
之前说到检查点又叫作“一致性检查点”,是Flink容错机制的核心。接下来就对状态一致性的概念进行说明,结合理论和实际应用场景,讨论Flink流式处理架构中的应对机制。
一、一致性的概念和级别
在分布式系统中,一致性(consistency)是一个非常重要的概念;在事务(transaction)中,一致性也是重要的一个特性。Flink中一致性的概念,主要用在故障恢复的描述中,所以更加类似于事务中的表述。那到底什么是一致性呢?
简单来讲,一致性其实就是结果的正确性。对于分布式系统而言,强调的是不同节点中相同数据的副本应该总是“一致的”,也就是从不同节点读取时总能得到相同的值;而对于事务而言,是要求提交更新操作后,能够读取到新的数据。对于Flink来说,多个节点并行处理不同的任务,要保证计算结果是正确的,就必须不漏掉任何一个数据,而且也不会重复处理同一个数据。流式计算本身就是一个一个来的,所以正常处理的过程中结果肯定是正确的;但在发生故障、需要恢复状态进行回滚时就需要更多的保障机制了。通过检查点的保存来保证状态恢复后结果的正确,所以主要讨论的就是“状态的一致性”。
一般说来,状态一致性有三种级别:
-
最多一次(AT-MOST-ONCE)
当任务发生故障时,最简单的做法就是直接重启,别的什么都不干;既不恢复丢失的状态,也不重放丢失的数据。每个数据在正常情况下会被处理一次,遇到故障时就会丢掉,所以就是“最多处理一次”。
可以发现,如果数据可以直接被丢掉,那其实就是没有任何操作来保证结果的准确性;所以这种类型的保证也叫“没有保证”。尽管看起来比较糟糕,不过如果我们的主要诉求是“快”,而对近似正确的结果也能接受,那这也不失为一种很好的解决方案。 -
至少一次(AT-LEAST-ONCE)
在实际应用中,一般会希望至少不要丢掉数据。这种一致性级别就叫作“至少一次”(at-least-once),就是说是所有数据都不会丢,肯定被处理了;不过不能保证只处理一次,有些数据会被重复处理。
在有些场景下,重复处理数据是不影响结果的正确性的,这种操作具有“幂等性”。比如,如果统计电商网站的UV,需要对每个用户的访问数据进行去重处理,所以即使同一个数据被处理多次,也不会影响最终的结果,这时使用at-least-once语义是完全没问题的。当然,如果重复数据对结果有影响,比如统计的是PV,或者之前的统计词频wordcount,使用at-least-once语义就可能会导致结果的不一致了。
为了保证达到at-least-once的状态一致性,需要在发生故障时能够重放数据。最常见的做法是,可以用持久化的事件日志系统,把所有的事件写入到持久化存储中。这时只要记录一个偏移量,当任务发生故障重启后,重置偏移量就可以重放检查点之后的数据了。Kafka就是这种架构的一个典型实现。 -
精确一次(EXACTLY-ONCE)
最严格的一致性保证,就是所谓的“精确一次”(exactly-once,有时也译作“恰好一次”)。这也是最难实现的状态一致性语义。exactly-once意味着所有数据不仅不会丢失,而且只被处理一次,不会重复处理。也就是说对于每一个数据,最终体现在状态和输出结果上,只能有一次统计。
exactly-once可以真正意义上保证结果的绝对正确,在发生故障恢复后,就好像从未发生过故障一样。
很明显,要做的exactly-once,首先必须能达到at-least-once的要求,就是数据不丢。所以同样需要有数据重放机制来保证这一点。另外,还需要有专门的设计保证每个数据只被处理一次。Flink中使用的是一种轻量级快照机制——检查点(checkpoint)来保证exactly-once语义。
二、端到端的状态一致性
检查点可以保证Flink内部状态的一致性,而且可以做到精确一次(exactly-once)。那是不是说,只要开启了检查点,发生故障进行恢复,结果就不会有任何问题呢?
没那么简单。在实际应用中,一般要保证从用户的角度看来,最终消费的数据是正确的。而用户或者外部应用不会直接从Flink内部的状态读取数据,往往需要将处理结果写入外部存储中。这就要求不仅要考虑Flink内部数据的处理转换,还涉及从外部数据源读取,以及写入外部持久化系统,整个应用处理流程从头到尾都应该是正确的。
所以完整的流处理应用,应该包括了数据源、流处理器和外部存储系统三个部分。这个完整应用的一致性,就叫作“端到端(end-to-end)的状态一致性”
,它取决于三个组件中最弱的那一环。一般来说,能否达到at-least-once一致性级别,主要看数据源能够重放数据;而能否达到exactly-once级别,流处理器内部、数据源、外部存储都要有相应的保证机制。