Spark Streaming对Exactly Once的实现原理

9 篇文章 0 订阅

Exactly Once实现的整体性
首先一个很重要的道理是: 整个系统对exactly once的保证,从来都不是靠系统中某一部分来实现就能搞定的,需要整个流式系统一起努力才可以实现。

对Spark Streaming来说, Exactly once的实现,需要系统中三部分的整体保证: 

    输入源 --> Spark Streaming计算 ---> 输出操作

    "输入源"对于exactly once的实现: Kafka的directly API其实就是在解决输入源输入数据的exactly once语义;

    "Spark Streaming"部分的exactly once的shi实现: 使用WAL保证(注意我没有提checkpoint和replication, 因为这两个failover机制,并不是专门解决exactly once这个问题的)。

    "输出操作"对于exactly once的实现: 需要输出结果保证幂等性, 这点官方文档已经说的比较清楚:

In order to achieve exactly-once semantics for output of your results, your output operation that saves the data to an external data store must be either idempotent, or an atomic transaction that saves results and offsets (see Semantics of output operations in the main programming guide for further information).
 其中, "输入源"对于exactly once的实现可以专门再找专题讲述, 不涉及对本文核心内容的理解; "输出操作"对exactly once的实现很好理解,无非就是独立出每次batch计算的结果并保证可重入, 所以本文重点介绍Spark Stream核心对Exactly Once的实现原理。

Spark Streaming计算框架对failover的实现
从网上能查到的千篇一律的Streaming计算逻辑,我这里就不讲了, 重点介绍一下计算框架对failover的处理部分。

计算框架使用3种方式来实现整体的failover机制:

    1 checkpoint(注意,这个checkpoint和RDD的checkpoint是两码事): 在Driver层实现, 用于在Driver崩溃后,恢复Driver的现场;

    2 replication: 在Receiver中用于解决单台executor挂掉后,未保存的数据丢失的问题。

    3 WAL: 在Driver和Receiver中实现,用于解决:

      (1) Driver挂掉,所有executor都会挂掉,那么所有未保存的数据都丢掉了,replication就不管用了;

      (2) Driver挂掉后,  哪些block在挂掉前注册到了driver中,以及挂掉前哪些block分配给了当时正在运行的batch job, 这些信息就都丢失了;所以需要WAL对这些信息做持久化。

其中, 第3个机制中的第2条,才是计算框架用于实现Exactly Once的努力, 下面针对这一点详细展开。

WAL解决数据丢失问题的原理
Driver对输入数据处理的步骤:

    1 addBlock: 将输入数据保存转化为block,并保存到blockQueue中; //在ReceiverTrackerEndpoint线程处理;
    2 allocateBlocksToBatch: 将当前所有未处理的block,分配给batch,然后删除blockQueue中的所有block;   //在JobGenerator的eventLoop线程中处理;

    3 用分给本次batch的所有数据进行job计算; //在JobHandler线程中处理;
上面的前2个步骤,完全是在2个不同的线程里异步执行,而且会对数据的形式进行变换(buffer -> block -> batch data), 
每一步的完成都会删除前面一种数据形式的数据。所以,前2步,都会进行WAL操作,持久化数据。
简单说,WAL中保存的数据是这样的:
       A             B                    C                             D              E
   addblock1 --> addblock2 --> 将所有block分给batch然后删除所有block --> addblock3 --> addblock4....
这样任何一个阶段driver崩溃再恢复的时候,根据WAL,就可以恢复当时的数据;
举例来说:
    1 当执行到C时, 做了C的WAL保存,然后开始运行对应的batch job, 这时Driver挂掉了; 再restart恢复时,发现WAL里面
有A -> B -> C, 会一次重放执行, 也就是先将block1和block2放入blockQueue,然后将这2个block分配个batch job,然后删除
掉blockQueue里的这两个block;这样数据就恢复到了崩溃之前的现场;
    2 当执行到E时,Driver崩溃, 那么重新恢复后,一次执行A,B,C,D,E; 执行结果就是, block1和block2分配给了batch job
并执行,block3和block4存放在blockQueue中,等待下次到时分配给下一个batch job.
Spark Streaming计算框架无法解决的job运行一半的问题
但这里仍无法解决一个问题,就是如果job运行一半(比如已经写了一部分到结果数据库中),driver崩溃,再恢复时,这个job会重新运行,但是上一次运行一半的job已经写了部分到数据库了。
解决这个问题,就需要"输出操作"是幂等的,这就不是Spark Streaming解决的问题了,需要应用程序自己来保证。 
总结
输入源对于Exactly Once需要实现的是:  崩溃前一部分数据已经作为block1输入到Spark了, 那么崩溃后恢复,这部分数据不能再输入到spark中;

Spark Streaming计算框架对于Exactly Once需要实现的是:  接收输入数据以及分配给Batch job数据,这两部分的持久化一步都不能少,因为流入数据形成block和将block数据分给Batch,是分离的两个步骤,没有事务化;这其实也是本质上造成必须有Kafka Direct API的原因。比如只做了持久化block,但是没有持久化哪些block应该分配给batch job,那么下次恢复时,哪些数据该属于这个batch job,就完全不可知。最要命的是, 比如正在跑2个batch job,先跑的job1, 再跑的job2; 如果job2先跑完了,job1还在跑,这时崩溃了,那么恢复后,如果没有持久化分配给batch job的block信息, 那么会将所有数据都分配给一个batch job执行, 那么相当于job2的数据被执行输出了2次。
Spark Streaming框架,只能保证流入的数据不丢失,且崩溃前正在执行的batch job,崩溃后恢复时,分配给这个batch job的数据(无论从数据内容,还是数据大小),都要与崩溃前跑的这个batch job完全一致(具体就是使用WAL实现)。至于输入源是否会重复发送数据给Spark Streaming框架,Spark Streaming框架完全不可控。

总的来说, Spark Streaming整体架构实现Exactly Once, 是要依靠"输入源", "计算框架自身", "输出操作"共同协调完成的, 如果没有Kafka direct API, 即使计算框架自身实现了WAL,也无法提供真正的Exactly Once语义(其实Kafka direct API已经可以自行实现Exactly Once语义而不需要抛掉WAL了, 不过实现方式其实是将输入源与计算框架做了融合)。
--------------------- 

原文:https://blog.csdn.net/cymvp/article/details/52605987 
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值