Spark 定制版:第14课 Spark Streaming源码解读之State管理之updateStateByKey和mapWithState解密

原文链接:http://blog.csdn.net/zisheng_wang_data/article/details/51712392

本讲内容:

a. updateStateByKey解密
b. mapWithState解密

注:本讲内容基于Spark 1.6.1版本(在2016年5月来说是Spark最新版本)讲解。

上节回顾

上一讲中,我们从Spark Streaming源码解读Driver容错安全性:那么什么是Driver容错安全性呢?

a. 从数据层面:ReceivedBlockTracker为整个Spark Streaming应用程序记录元数据信息

b. 从调度层面:DStreamGraph和JobGenerator是Spark Streaming调度的核心,记录当前调度到哪一进度,和业务有关

c. 从运行角度: 作业生存层面,JobGenerator是Job调度层面

谈Driver容错性我们需要考虑Driver中有那些需要维持状态的运行

a. ReceivedBlockTracker跟踪了数据,因此需要容错。通过WAL方式容错

b. DStreamGraph表达了依赖关系,恢复状态的时候需要根据DStream恢复计算逻辑级别的依赖关系。通过checkpoint方式容错

c. JobGenerator表面是基于ReceiverBlockTracker中的数据,以及DStream构成的依赖关系不断的产生Job的过程。也可以这么理解这个过程中消费了那些数据,并且跟踪进行到了一个怎样的程度

具体分析如下图:

这里写图片描述

最后我们可以这样总结道:

a. ReceivedBlockTracker是通过WAL方式来进行数据容错的。

b. DStreamGraph和JobGenerator是通过checkpoint方式来进行数据容错的。

开讲

本讲我们讲Spark Streaming中一个非常重要的内容:State状态管理

a. 为了说明state状态管理,拿两个非常具体非常有价值的方法updateStateByKey和mapWithState这两个方法来说明 sparkstreaming是如何实现对state状态管理的。Sparkstreaming是按照batchduration划分job的,但是有时 我们想算过去一个小时或者过去一天的数据,在大于batchduration的时候对数据进行符合业务逻辑的操作,这时候不可避免的要进行状态维护。 Sparkstreaming每个batchduration都会产生一个job,job里面都是RDD,所以现在面临的一个问题就是,他每个 batchduration产生RDD,怎么对他的状态进行维护的问题(像updateStateByKey)。例如计算一天的商品的点击量,这时候就需 要类似于updateStateByKey或者mapWithState这样的方法帮助完成核心的步骤

b. Spark 的状态管理其实有很多函数,比较典型的有类似的UpdateStateByKey、MapWithState方法来完成核心的步骤

updateStateByKey和mapWithState这两个方法在DStream中并不能找到。因为updateStateByKey和 mapWithState这两个方法都是针对key-value类型的数据进行操作,也就是pair类型的,和前边讲RDD是一样的,RDD这个类本事并 不会对key-value类型的数据进行操作,所以这时候就需要借助scala的语法隐式转换。隐式转换一般放在类的伴生对象中,将DStream转换成 PairDStreamFunctions。这是从地狱中召唤出来的功能,使用后又回到地狱。运行机制就是找不到DStream的 updateStateByKey和mapWithState,他们是PairDStreamFunctions的方法,就找隐式转换,隐式转换中发现 toPairDStreamFunctions这个功能,就使用了implicit功能

这里写图片描述

继续跟踪PairDStreamFunctions类中有次方法定义

updateStateByKey

在PairDStreamFunctions中updateStateByKey具体实现如下

在已有的历史基础上,updateFunc对历史数据进行更新。该函数的返回值是DStream类型的。

这里写图片描述

现在解读一下updateStateByKey方法,updateStateByKey是在已有状态基础上采用updateFunc对历史数据进行更新,具体怎么更新就是updateFunc这样的函数进行操作的,返回一个DStream

这里采用的是defaultPartitioner,不论是基于状态的计算还是基于batchduration的计算都是基于RDD的,RDD这里 面肯定需要Partitioner,默认采用的是HashPartitioner,HashPartitioner其实就对应着Hash的shuffle 或者Hash的计算方式。Hash的特点就是效率高,不需要进行排序等

这里写图片描述

spark sql如果操作hive数据仓库中表的时候,假设自己设制了一个并行度,对spark sql on hive是否会生效?也就是分多少partition会不会受自定义分片的控制?:不会,这是spark sql特殊的一个地方,不会的时候有时候就会导致一种情况就是并行度太低,因为RDD的操作是后面的RDD继承自前面的RDD的partition,前面 并行度太低就会影响后面的计算,影响GC等等。这时候就需要repartition而不是使用collase的方式

这里写图片描述

rememberPartitioner前面传的是true,所以Partitioner都会传下去。new StateDStream

这里写图片描述

继续跟踪StateDStream,继承了DStream,如果对状态不断的操作就会产生很多的StateDStream状态对象

这里写图片描述

这里进行persist是在磁盘上,super.persist(StorageLevel.MEMORY_ONLY_SER),因为计算的是过去 很长时间的数据,数据可能会很多。核心的代码是val cogroupedRDD = parentRDD.cogroup(prevStateRDD, partitioner),cogroup就是按照key对value进行聚合,按照key对所有数据进行扫描然后聚合,这样做好处是对rdd的计算;

这里写图片描述

不好的地方就是性能,cogroup对所有数据进行扫描,随着时间流逝数据规模越来越大性能越低,cogroup rdd和另一个cogroup rdd数据进行扫描合并

这里写图片描述

所以,如果数据很多的时候不建议使用updateStateByKey

updateStateByKey函数实现如下:

这里写图片描述

mapWithState

下面来看mapWithState,返回一个MapWithStateDStream,使用一个函数不断作用于这个DStream中key- value的元素,基于key进行状态维护和更新。mapWithState这个方法参数是StateSpec,这里边封装了对数据操作的函数。对 State可以理解为是一个内存数据表,这个表中记录了所有的历史状态,可以对表中不同key的数据进行操作。更新的时候根据key在state的基础上 更新对应的value。就相当于对一个表进行增删改查

这里写图片描述

StateSpecImpl是一个case class,里面封装了对数据操作的function

这里写图片描述

走进 MapWithStateDStream源码

这里写图片描述

可以看到MapWithStateDStreamImpl这个类的计算交给了内部一个类InternalMapWithStateDStream。这个类是对数据进行更新

这里写图片描述

InternalMapWithStateDStream的compute方法创建了一个 MapWithStateRDD,MapWithStateRDD中包含了mapWithState的数据,以及对数据怎么操作。这个RDD的每一个 partition被一个MapWithStateRDDRecord代表的

这里写图片描述

这里写图片描述

MapWithStateRDDRecord有对数据的具体更新,这里边有两个关键的数据结构mappedData 和wrappedState 。newStateMap 是对以前RDD数据进行复制。每次更新数据的时候是对当前barchduration的数据进行遍历,而不是像updateStateByKey一样要遍 历所有的数据

dataIterator.foreach dataIterator是当前barchduration的数据。每次遍历的时候会先记录以往数据中相同key的state,再根据指定的函数对遍历的 数据进行操作,然后把操作后的状态保存。没有对历史数据进行从新遍历。这个效率会高很多。返回的还是MapWithStateRDDRecord ,从RDD的角度看

MapWithStateRDDRecord 并没有变,但是它内部变了。相当于RDD原先指向一条数据,这条数据不能修改。现在RDD也是指向一条数据,只是这个数据中又封装了数据。可以改变封装的 数据,从RDD角度看,RDD指向的数据并没有变。所以RDD可以处理变化的数据。只是要自定义一个RDD指向的数据结构

这里写图片描述

这里写图片描述

MapWithState实现如下:

这里写图片描述

最后我们附上代码执行流程图:

来源:http://blog.csdn.net/hanburgud/article/details/51545414,感谢作者

这里写图片描述

备注:
1、DT大数据梦工厂微信公众号DT_Spark
2、Spark专家:王家林
3、新浪微博: http://www.weibo.com/ilovepains

posted @ 2016-06-25 16:52  my_blog17  阅读(1840)  评论(0编辑  收藏  举报