实时计算的三种语义
- At-most-once:最多一次。每条数据记录最多被处理一次,也就是说数据会有丢失(没被处理掉)的可能。
- At-least-once:最少一次。每条数据记录至少被处理一次。
- Exactly-once:恰好一次。每条数据记录正好被处理一次。
Spark Streaming如果实现Exactly-once语义
一个典型的Spark Streaming应用程序会包含三个处理节点:接收数据,处理汇总,输出结果。每个阶段都需要做不同的处理才能实现相应的语义。
接收数据阶段
主要取决于上游数据源的特性。例如,从HDFS这类支持容错的文件系统中读取文件,能够直接支持Exactly-once语义。如果上游消息系统支持ACK,就可以结合Spark的Write Ahead Log特性来实现At-Leader-once语义。对于非可靠的数据接收器(如socketTextStream),当Worker或Driver节点发生故障时就会产生数据丢失,提供的语义也是未知的。而Kafka消息系统是基于偏移量(Offset)的。它的Direct API可以提供Exactly-once语义
数据处理阶段
在使用Spark RDD对数据进行转换或汇总时,我们可以天然获得Exactly-once语义,因为RDD本身就是一种具备容错性、不变形、以及计算确定性的数据结构。只要数据来源是可用的,且处理过程中没有伏左右,就能一直得到相同的计算结果。
结果输出阶段
结果输出默认符合At-Least-once语义,因为foreachRDD方法可能会因为Worker节点失效而执行多次,从而重复写入外部存储。可以通过幂等更新
和事务更新
方法来解决。
幂等写入实现Exactly-once
如果多次写入会产生相同的结果数据,我们可以认为这类写入操作是幂等的。saveAsTextFile
就是一种典型的幂等写入。如果消息中包含唯一主键,那么多次写入相同的数据也不会在数据库中产生重复记录。这种方式也就等价于Exactly-once意义了。
但需要注意的是,幂等写入只适用于Map-only型的计算流程,即没有Shuffle、Reduce、Repartition等操作。
使用事务写入实现Exactly-once
在使用事务性写入时,我们需要生成一个唯一的ID,这个ID可以使用当前批次的时间、分区号,或是Kafka偏移量来生成。之后,我们需要在一个事务中将处理结果和这个唯一的ID一同写入数据库。这一原子性的操作将带给我们Exactly-once语义,而且该方法可以同时适用于Map-only以及包含汇聚操作的计算流程。
网友评论