Spark基于事件时间的“状态”流的深层分析 - withWatermark与mapGroupsWithState的关系

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bluishglc/article/details/80824522

不管是基于watermark的窗口计算还是自维护的状态流,它们都是有状态的,watermark只是规定了数据进入“状态”(有资格参与状态计算)的条件,并没有(也不适合)声明状态的“退出”机制。对于watermark的窗口计算来说,它们的“退出”机制是:如果最近某个还处于active状态的窗口它的EndTime比当前批次中最新的一个事件时间减去watermark规定的阈值还要“早”,说明这个窗口所有的数据都就绪了,不会再被更新了,就可以把正式“decommission”了。由于这个逻辑对于watermark的窗口计算来说是通行的, 所以被Spark封装在窗口计算中,对开发人员是透明的,但是对于自维护的状态来说“退出”机制是要根据实际情况进行处理的,因此必须要由开发员人员通过编码来实现,这其中除了业务逻辑上决定的“主动”退出(例如接收到了某类session关闭的消息)之外,还需要有一种“保底”的推出机制:状态超时,对于某些有状态的流,可能并没有对应的关闭消息,可以约定在多长时间内没有收到消息就认定状态终结了,那这时就是基于时间阈值的判断,那就又会涉及到是基于事件时间还是处理时间,显然,Spark是同时支持两种模式的,只是有一点会让新人疑惑的是:在基于事件时间的有状态的流计算上,在API层面“似乎”没有给开发人员一个声明“哪个字段是事件时间”的地方,转而是这样约定的:如果开发人员需要开发基于事件时间的有状态的流计算,则必须使用watermark机制,对应到代码层面就是,当你使用mapGroupsWithState(GroupStateTimeout.EventTimeTimeout())(yourGroupStateUpdateFunc)时,前面一定要先声明withWatermark("yourEnenTimeColumnName", yourWatermarkDuration)

sparkSession
    ...
    .withWatermark("yourEnenTimeColumnName", yourWatermarkDuration)
    ...
    .groupByKey(...)
    .mapGroupsWithState(GroupStateTimeout.EventTimeTimeout())(yourGroupStateUpdateFunc)
    ...

我们怎么解读这两者之间的关系呢?首先:我们应该清晰地认识到:这里涉及到的是两个“时间”点和三种“度量”标准的问题,两个“时间”点指的是:数据“进入”状态参与计算的“时效条件”与状态退出的“时效条件”,三种”度量“标准是指以事件时间为准还是以处理时间为准还是不指定时间维度上度量阈值,以下是对它们之间逻辑上的关联关系的总结:

基于事件时间 基于处理时间 不指定
由Spark维护的有状态计算(如window计算),决定数据是否能进入状态的“时效条件” withWatermark(…) N/A,不适用,因为当事件没有进入状态前,是没有”处理时间”这个值的,也就不存在这种case 如果不指定,即没有声明withWatermark,直接进行窗口计算,此时的含义将变为“不设条件”,所有事件“无条件”进入窗口进行计算。
由Spark维护的有状态计算(如window计算),决定状态是否退出的“时效条件” 首先前提是必须声明了withWatermark(…),这是确保基于事件时间的前提。如果该窗口的EndTime比当前批次中最新的一个事件时间减去watermark规定的阈值还要“早”,则自动退出,相关中间状态和退出动作由Spark封装,不需要显式的编码工作 N/A,不适用,对于状态的退出,虽然可以参照处理时间,但是Spark并没有提供这方面的支持,显然,Spark认为基于事件时间是更有实际意义的 如果不指定,即没有申明withWatermark,则所有的窗口对应的状态永不“退出”,不管过去多长时间都可以被更新(实际编码时是不可取的)
自维护的状态,决定数据是否能进入状态的“时效条件” withWatermark(…) N/A,不适用,因为当事件没有进入状态前,是没有”处理时间”这个值的,也就不存在这种case 如果不指定,即没有声明withWatermark,直接进行窗口计算,此时的含义将变为“不设条件”,所有事件“无条件”进入窗口进行计算。
自维护的状态,决定状态是否退出的“时效条件” 首先前提是必须声明了withWatermark(…),这是确保基于事件时间的前提。然后使用mapGroupsWithState (GroupStateTimeout.EventTimeTimeout())(…),显式地表明状态的超时将以事件时间为准,超时阈值在每个GroupState中使用setTimeoutTimestamp单独设定! 不声明withWatermark,表示不使用事件时间框架,然后使用 mapGroupsWithState (GroupStateTimeout.ProcessingTimeTimeout())(…),显式地表明状态的超时将以处理时间为准,超时阈值在每个GroupState中使用setTimeoutDuration单独设定! 即不设置不在GroupState中设置任何超时,这时状态永远不会退出!(不推荐)

从是否使用withWatermark的角度总结是:

  1. 一旦使用withWatermark(…), 就意味着:在决定数据是否能进入状态时,完全基于withWatermark指定的事件时间去计算,而在决定状态是否退出时,对于基于窗口的由Spark来维护的状态计算,会基于withWatermark指定的阈值基于事件时间进行判断,对于自维护的状态,可以基于事件时间,也可以基于处理时间。
  2. 但是如果没有使用withWatermark(…),就意味着:在决定数据是否能进入状态时,是无条件的,所有数据都会进入状态,而在决定状态是否退出时,对于基于窗口的由Spark维护的状态计算,状态永不退出,对于自维护的状态,可以永不退出,如果基于时间进行退出,只能基于“处理时间”,因为没有使用withWatermark(…)就意味着没有开启时间时间框架,数据进入状态与状态退出都不能依赖事件时间。

从“状态”是由Spark维护还是自维护的角度总结是:

  1. 如果“状态”是由Spark维护的基于窗口的流,如果使用withWatermark(…),则数据进入状态与状态退出都是基于事件时间的。如果没有使用withWatermark(…),不存在基于哪种时间决定数据进入状态与状态退出,而是无条件进入与永不退出!
  2. 如果是“状态”自维护的计算,如果使用withWatermark(…),则数据是否进入状态是基于事件时间判断的,状态退出可以基于事件时间或处理时间判断,取决于设置的是GroupStateTimeout.EventTimeTimeout还是GroupStateTimeout.ProcessTimeTimeout

以上总结比较晦涩难懂,究其原因,我认为是Spark Structured Streaming的API没有在一开始很好的规划这些事情,而是在早期没有考虑完善的版本上逐渐累加新API导致的,这可以从Spark的版本历史中看出来,相信Spark以后会梳理出一些更加完备而直白的API。

本文原文链接: http://blog.csdn.net/bluishglc/article/details/80824522 转载请注明出处。

展开阅读全文

没有更多推荐了,返回首页