流处理系统中的“Exactly Once”语义保证
前言
最近在学习一些流处理相关的知识,对比与笔者接触比较多的离线处理系统,实时流处理的有些地方还是比较有意思的。在这里面,最常被人提到的词应该是“Exactly Once”语义 ,在工作面试中,如果做过实时流系统,肯定免不了被问到“xx框架是如何做到Exactly Once的”?笔者最近在阅读Spark Streaming的官方文档中,提到了这一点,于是来做个小小的总结归纳。如果感兴趣的同学,请继续往下阅读。
语义定义
在流处理系统中,我们对应数据记录的处理,有3种级别的语义定义,以此来衡量这个流处理系统的能力。
- At most once(最多一次)。每条数据记录最多被处理一次,潜台词也表明数据会有丢失(没被处理掉)的可能。
- At least once(最少一次)。每条数据记录至少被处理一次。这个比上一点强的地方在于这里至少保证数据不会丢,至少被处理过,唯一不足之处在于数据可能会被重复处理。
- Exactly once(恰好一次)。每条数据记录正好被处理一次。没有数据丢失,也没有重复的数据处理。这一点是3个语义里要求最高的。
基本语义
上一节介绍的语义是比较宽泛意义上的语义,这里我们再细分下里面的语义操作。比如说,我们可以把一个记录处理操作再划分为下面3个子操作:
- 接收数据的操作。从数据源接收数据的操作。
- 转换处理数据的操作。在这里面数据会被事先定义好的各种操作语义所处理。
- 输出数据操作。将处理好后的结果数据输出到外部系统文件系统,或数据集等等。
为什么这里笔者要提到上面涉及到的更细粒度级别的操作呢?其实在流处理过程中,失败现象就可能发生在上面3个步骤中的任何一步。如果要拿最高标准“Exactly Once”标准来看,我们要达到的理想效果应该是:
- 数据只被处理过一次,这里面可以包括曾经处理失败,然后再读取原始数据进行处理。
- 对于一个原始数据,我们保证最后结果数据输出是一致的,我们并不是说输出操作只是一次执行的。
所以从这里面我们可以看出,要想达到最高的“Exactly Once”标准,中间的处理操作是最最关键的。因为它会有各种意外情况发生。
“Exactly Once”的重要保证:输入数据源的可依赖性
因为数据在处理过程中有可能会有各种情况发生,所以这里的一个必要前提保证是:原始数据的可依赖性,或者是可访问性。简单的用一句话来说,就是数据处理失败了,我还能够访问到原始输入数,然后再执行处理操作。所以归结为一个本质问题:操作的“Exactly Once”语义问题实质上是输入数据源的可依赖程度。
这里我们将焦点转向输入数据源上,输入数据源可以分为以下2种。
基于文件系统的数据源
数据从文件中读取而来,因为文件本身存在于已经本身具备容错能力的文件系统(比如HDFS),所以我们可以认为这样的数据源是可以支持“Exactly Once”语义的,因为任何的数据处理失败恢复都可以从原始文件系统中进行数据的再次读取。
基于外部接收器的数据源
基于外部接收器的数据源指的是我们的数据从外部系统中(比如说Kafka)读取过来的。像这类情况,同样地,我们要分这些接收数据器是可信赖的还是不可信赖的。
- 可信赖的接收器。接收器收到数据后并且在多个节点做了replica副本操作,然后确认回复给数据源,之后出现失败情况时数据源就不会重发数据给接收器对象。否则,接收数据源在接收器重启的时候,会重发数据给接收器对象。
- 不可信赖的接收器。这种接收器不发任何确认回复消息给数据源,这就有数据丢失的可能性了,当worker节点挂掉的时候。
以上就是笔者简单学习总结的内容了,希望对对此感兴趣的同学有帮助。
参考资料
[1].http://spark.apache.org/docs/latest/streaming-programming-guide.html#fault-tolerance-semantics