EOS实现原理

在Kafka、Storm、Flink、Spark Streaming等分布式流处理系统中,存在三种消息传递语义(message delivery semantics),分别是:

  • at least once:每条消息会被消费1次或多次。例如发送方S在超时时间内没有收到接收方R的通知(如ack),或者收到了R的报错,就会不断重发消息直至R传回ack。
  • at most once:每条消息会被消费0次或1次。也就是说S只负责向R发送消息,R也没有任何通知机制。无论R最终是否收到,S都不会重发。
  • exactly once:是上面两个的综合,保证S发送的每一条消息,R都会“不重不漏”地恰好收到1次。它是最强最精确的语义,也最难实现。

Spark-Exactly-Once - 图1
Kafka与Spark Streaming集成时有两种方法:基于receiver的方法(不推荐使用,将废弃),基于direct stream的方法。

Φ 基于receiver的方法(接收器)

Spark-Exactly-Once - 图2
基于receiver的方法采用Kafka的高级消费者API,每个executor进程都不断拉取消息,并同时保存在executor内存与HDFS上的预写日志(write-ahead log/WAL)。当消息写入WAL后,自动更新ZooKeeper中的offset。它可以保证at least once语义,但无法保证exactly once语义。虽然引入了WAL来确保消息不会丢失,但还有可能会出现消息已经写入WAL,但offset更新失败的情况,Kafka就会按上一次的offset重新发送消息。这种方式还会造成数据冗余(Kafka broker中一份,Spark executor中一份),使吞吐量和内存利用率降低。现在基本都使用基于direct stream的方法了。

Φ 基于direct stream的方法(直连)

Spark-Exactly-Once - 图3
基于direct stream的方法采用Kafka的简单消费者API,它的流程大大简化了。executor不再从Kafka中连续读取消息,也消除了receiver和WAL。还有一个改进就是Kafka分区与RDD分区是一一对应的,更可控。driver进程只需要每次从Kafka获得批次消息的offset range,然后executor进程根据offset range去读取该批次对应的消息即可。由于offset在Kafka中能唯一确定一条消息,且在外部只能被Streaming程序本身感知到,因此消除了不一致性,达到了exactly once。
Spark RDD之所以被称为“弹性分布式数据集”,是因为它具有
不可变、可分区、可并行计算、容错的特征。一个RDD只能由稳定的数据集生成,或者从其他RDD转换(transform)得来。如果在执行RDD lineage的过程中失败,那么只要源数据不发生变化,无论重新执行多少次lineage,都一定会得到同样的、确定的结果。
最后,我们还需要保证输出过程也符合exactly once语义。Spark Streaming的输出一般是靠foreachRDD()算子来实现,它默认是at least once的。如果输出过程中途出错,那么就会重复执行直到写入成功。为了让它符合exactly once,可以施加两种限制之一:
幂等性写入(idempotent write)、事务性写入**(transactional write)。

  • 幂等性写入

幂等原来是数学里的概念,即f(f(x))=f(x)。幂等写入就是写入多次与写入一次的结果完全相同,可以自动将at least once转化为exactly once。这对于自带主键或主键组的业务比较合适(比如各类日志、MySQL binlog等),并且实现起来比较简单。但是它要求处理逻辑是map-only的,也就是只能包含转换、过滤等操作,不能包含shuffle、聚合等操作。如果条件更严格,就只能采用事务性写入方法。

  • 事务性写入

这里的事务与DBMS中的事务含义基本相同,就是对数据进行一系列访问与更新操作所组成的逻辑块。为了符合事务的ACID特性,必须引入一个唯一ID标识当前的处理逻辑,并且将计算结果与该ID一起落盘。ID可以由主题、分区、时间、offset等共同组成。事务操作可以在foreachRDD时进行。如果数据写入失败,或者offset写入与当前offset range不匹配,那么这一批次数据都将失败并且回滚。

EOS应用实现

一个流式计算框架如果要保证Exactly Once,那么需要满足:

  1. Source支持Replay(回放数据)。
  2. 流式计算引擎本身处理能保证Exactly Once(Spark DStream和RDD算子转换天然可保证Exactly Once)。
  3. Sink支持幂等(支持唯一键,如:MySQL、HBase、Redis、ZooKeeper)或者事务更新(支持唯一键,如:MySQL)。

注意:要保证端到端**Exactly Once,以上每个步骤都需要保证**Exactly Once。
点击查看【processon】

参考

社区:阿里云-Apache Spark开发者
https://developer.aliyun.com/group/apachespark?spm=a2c4e.11153940.0.0.3be92c722iZbLa&accounttraceid=837a4ae021fa4d7bad44caeca5c5a6a0kguv#/?_k=2049ff
社区:Spark SQL实践与优化
https://yunqivedio.alicdn.com/od/Kf8Rb1543482700458.mp4?spm=a2c6h.12873639.0.0.1357219eEkZWrC&file=Kf8Rb1543482700458.mp4
社区:从Spark Streaming到Structured Streaming
https://yq.aliyun.com/live/689?spm=a2c6h.12873639.0.0.1357219eYJwcK5
社区:Structured Steaming的进阶与实践
https://www.slidestalk.com/AliSpark/StructuredStreaming60695?spm=a2c6h.12873639.0.0.1357219eN7pIEr
博文:Kafka+Spark Streaming如何保证exactly once语义
https://www.jianshu.com/p/10de8f3b1be8
博文:Spark Streaming对Exactly Once的实现原理
https://blog.csdn.net/cymvp/article/details/52605987
案例实时流计算实时去重Kafka–>Spark Streaming–>Redis
http://lxw1234.com/archives/2018/02/901.htm
案例:Spark Streaming保证Exactly-Once语义(Kafka->**Spark Streaming**->Mysql
https://blog.csdn.net/wangpei1949/article/details/89277490
案例:Spark Streaming exactly once原理及编程示例(Kafka->**Spark Streaming**->STDOUT
https://blog.csdn.net/tom_fans/article/details/77477915
案例:Spark Streaming 中如何实现 Exactly-Once 语义(Kafka->**Spark Streaming**->Mysql
http://shzhangji.com/cnblogs/2017/08/01/how-to-achieve-exactly-once-semantics-in-spark-streaming/
视频:实时计算技术SparkStreaming(5课)
https://www.bilibili.com/video/BV12p4y1Q7ZW?p=1
https://www.cnblogs.com/cssdongl/p/6210757.html
视频:2019大数据sparkStreaming全套课程(21课)
https://www.bilibili.com/video/BV1Wx411Z7Ht?p=20