前言

当我在测试SparkStreaming的状态操作mapWithState算子时,当我们设置timeout(3s)的时候,3s过后数据还是不会过期,不对此key进行操作,等到30s左右才会清除过期的数据。

百度了很久,关于timeout的资料很少,更没有解决这个问题的文章,所以说,百度也不是万能的,有时候还是需要靠自己。

所以我就在周末研究了一下,然后将结果整理了出来,希望能帮助大家更全面的理解Spark状态计算。

mapWithState

按理说Spark Streaming实时处理,数据就像流水,每个批次之间的数据都是独立的,处理完就处理完了,不留下任何状态。但是免不了一些有状态的操作,例如统计从流启动到现在,某个单词出现了多少次,所以状态操作就出现了。

状态操作分为updateStateByKey和mapWithState,两者有着很大的区别。简单的来说,前者每次输出的都是全量状态,后者输出的是增量状态。

过期原理

过期这一块估计很多人开始都理解错了,我刚开始理解就是数据从出现,经过多少秒之后就会过期。其实不是,这里的过期指的是空闲时间。

注释大概是这个意思:timeout()传入一个时间间隔参数,如果一个key在大于此间隔没有此key的数据流入,则被认为是空闲的,就会单独调用一次mapWithState中的func来清除这些空闲数据状态。

先写结论

使用了timeout()之后,需要使用以下代码来在间隔内清除失效key。

stream.checkpoint(Seconds(6))

checkpoint的时候,会开启全面扫描,才会对state中的失效key进行清理。

测试

   val conf = new SparkConf().setMaster("local[2]").setAppName("state")
val ssc = new StreamingContext(conf, Seconds(3))
ssc.checkpoint("./tmp")
val streams: DStream[(String, Int)] = ssc.socketTextStream("localhost", 9999)
.map(x => (x, 1)) val result = streams.mapWithState(StateSpec.function((k: String, v: Option[Int], state: State[Int]) => {
val count = state.getOption().getOrElse(0)
println(k)
println(v)
var sum = 0
if (!state.isTimingOut()) {
sum = count + v.get
state.update(sum)
} else {
println("timeout")
}
Option(sum)
})
.timeout(Seconds(3))
)
// 这行代码是触发清除机制的关键
// result.checkpoint(Seconds(6))
result.print()
ssc.start()
ssc.awaitTermination()

使用上面的代码进行测试,设置过期时间为3s。但是3s过后发现key并没有过期,也不会被清除,大概30S之后被清除。

在9999端口输入一个tom后,不再进行任何操作。测试结果如下:

tom
Some(1)
-------------------------------------------
Time: 1618228587000 ms
-------------------------------------------
Some(1) tom
None
timeout
-------------------------------------------
Time: 1618228614000 ms
-------------------------------------------
Some(0)

从测试结果可以看出,从输入到清除大概是27s。

我们现在将注释的代码放开,每6s进行checkpoint一次,输入tom:

tom
Some(1)
-------------------------------------------
Time: 1618228497000 ms
-------------------------------------------
Some(1) tom
None
timeout
-------------------------------------------
Time: 1618228506000 ms
-------------------------------------------
Some(0)

从生成到清除用了9秒,正好是过期时间 + 下一个窗口时间,触发了checkpoint。

猜想

第一次学状态操作的时候,就考虑如何去掉一些过期的key,通过timeout()的方法没有完成自己想法,从网上也没有找到解决方案,所以就暂且搁置在一边了。后来又回过头来考虑这个问题,然后根据自己的想法去猜想、去验证。

1. 我先看的是mapWithState()的返回值

2. MapWithStateDStreamImpl

每个Dstream的计算逻辑都在compute()中,这里是调用了internalStream的getOrCompute(),根据继承关系,调用的是父类Dstream的此方法:

getOrCompute()主要功能为:计算、缓存、checkpoint。这里只需要记住几个地方:checkpointDuration,即checkpoint间隔,和调用了checkpoint()。其实真正的计算还是调用了compute(),接着去看compute()

3. InternalMapWithStateDStream

compute()里面也调用了getOrCompute()方法,其实和上面调用的一样,都是Dstream的,这里主要看的是使用createFromRDD()生成的StateRDD。

4. MapWithStateRDD

这个StateRDD就是参与状态计算的数据集合,首先看它是如何生成的:

再看看StateRDD的compute()是如何计算的:

从compute()看出,当doFullScan为true的时候,才会触发过期key的清除,updateRecordWithData()负责全面扫描清除过期key

这不,思路就来了,我们只要找到开启FullScan的方法,不就可以自行触发清除机制了吗!

那么,我们先看看doFullScan的默认值:

默认是没开启的,接着通过快捷键看看哪些地方使用了doFullScan:

从图中看出,有两处代码修改了doFullScan,我们找到这两处代码:



第一个基本上排除,那么就剩下第二个:checkpoint(),我们要知道的是,状态操作必须要checkpoint

还记得在2中的getOrCompute()吗,当checkpointDuration不为null的时候,调用checkpoint()。

我们来看3中InternalMapWithStateDStream是如何定义这个duration的:



如图,sideDuration是窗口时间,乘以系数10就是默认的checkpoint时长,所以当我设置窗口为3s时,checkpoint周期就是30s,30s才会清理一次过期key。

而通过checkpoint(interval)可以设置checkpoint的间隔,所以覆盖了上面程序中默认的30s。

5.MapWithStateRDDRecord

最后提一提,FullScan是在这个类中开启的,所以先看看这个Record的注释介绍:

意思就是负责存储StateRDD的状态KV,updateRecordWithData()负责清除过期的Record,我们来看看这个方法的实现:

removeTimedoutData就是是否开启全面扫描,即doFullScan的值。

结语

写完看起来感觉真的是简简单单,逻辑看起来也比较清晰,但是自己去解决这个问题的时候也是花了一下午时间,过期key的清除与checkpoint有关也是我凭空弄猜想,然后分析了两次,某一瞬间才找到他们之间的关系。所以说,猜想和运气还是很重要的。

当然,找不到关于这块的文章和资料可能是因为这个知识点太小了。所以这次过后,要开始系统阅读Spark源码了,也希望在某一天能结合着自己的理解,写一下Spark的文章。


95后小程序员,写的都是日常工作中的亲身实践,置身于初学者的角度从0写到1,详细且认真。

文章会在公众号 [入门到放弃之路] 首发,期待你的关注。



SparkStreaming使用mapWithState时,设置timeout()无法生效问题解决方案的更多相关文章

  1. 测试setsockopt设置超时是否生效代码

    // 测试setsockopt设置超时是否生效代码 #include <arpa/inet.h> #include <netinet/in.h> #include <st ...

  2. HTTP请求的python实现(urlopen、headers处理、 Cookie处理、设置Timeout超时、 重定向、Proxy的设置)

    python实现HTTP请求的三中方式:urllib2/urllib.httplib/urllib 以及Requests urllib2/urllib实现 urllib2和urllib是python两 ...

  3. listview当选中某一个item时设置背景色其他的不变

    listview当选中某一个item时设置背景色其他的不变: 可以使用listview.setOnFoucsChangeListener(listener) ; /** * listview获得焦点和 ...

  4. [转]Loadrunner11之VuGen运行时设置Run-Time Setting

    转自:http://www.51testing.com/html/92/450992-248065.html General 1.Run Logic运行逻辑 脚本如何运行的,每个action和acti ...

  5. LoadRunner 学习笔记(2)VuGen运行时设置Run-Time Setting

    定义:在Vugen中Run-Time Setting是用来设置脚本运行时所需要的相关选项

  6. input 类型为number型时,maxlength不生效?

    input 类型为number型时,maxlength不生效? 可以加oninput属性来控制最大长度:<input id="numInput" type="num ...

  7. AForge调用摄像头拍照时设置分辨率

    简单记录下AForge2.2.5.0版本调用摄像头拍照时设置分辨率的方法. FilterInfo info = _videoDevices[0];//获取第一个摄像头 _cameraDevice = ...

  8. c/c++ linux epoll系列3 利用epoll_wait设置timeout时间长度

    linux epoll系列3 利用epoll_wait设置timeout时间长度 epoll_wait函数的第四个参数可以设置,epoll_wait函数的等待时间(timeout时间长度). 例子1, ...

  9. loadrunner 运行场景-运行时设置

    运行场景-运行时设置 by:授客 QQ:1033553122 A.   查看.修改单个脚本的运行时设置 a)   途径1: Scenario Groups.Scenario Groups Script ...

随机推荐

  1. webassembly & google

    webassembly & google https://developers.google.com/web/updates/2018/08/wasm-av1 https://develope ...

  2. c++ 遍历当前程序的线程

    #include <iostream> #include <Windows.h> #include <Psapi.h> #include <TlHelp32. ...

  3. qt DateTime 计算时间

    qdatetime doc 获取当前时间 QDateTime t1 = QDateTime::currentDateTime(); qDebug() << t1.toString(&quo ...

  4. 关于Sidecar Pattern

    本文转载自关于Sidecar Pattern 导语 Sidecar 是一个很纠结的名字,我们在翻译技术雷达时采用了一个比较通俗的翻译,即边车,而这个词随着微服务的火热与 Service Mesh 的逐 ...

  5. Python序列之列表(一)

    在Python中,列表是一种常用的序列,接下来我来讲一下关于Python中列表的知识. 列表的创建 Python中有多种创建列表的方式 1.使用赋值运算符直接赋值创建列表 在创建列表时,我们直接使用赋 ...

  6. close() 和fluse()区别

    1.close()默认包含了一次flush()操作,关闭之后,就不能再写入了. 2.flush()刷新,flush()之后,可以接着写入. 3.缓冲区默认大小是8192字节,如果小于8192字节,不会 ...

  7. 维格表2月更新:智能图表上线,唤醒全量工作数据堪比AI

    你是否曾经想过,你的维格表数据有朝一日变化出如科幻电影般的数据图表? 你是否感到厌倦,对当前的后台系统密密麻麻的数据累觉不爱? 你是否一直期待,拥有一个专属大数据 BI 看板,让你的规划如有神助,挥斥 ...

  8. IDEA中引用不到HttpServlet的解决方案

    原文链接:https://blog.csdn.net/xiaozaizi666/article/details/87805564

  9. 微信小程序:数组拼接

    一开始用concat进行拼接,总是不行,代码如下: handleItemChange(e){ console.log(e) var itemList = e.detail.value itemList ...

  10. 安装并运行Nacos

    方式一:源码或者安装包 一.下载源码或者安装包 git clone https://github.com/alibaba/nacos.git 二.安装 cd nacos/ mvn -Prelease- ...