我正在开发一个Scala(2.11)/ Spark(1.6.1)流式项目,并使用mapWithState()
来跟踪以前批次中看到的数据。
状态分布在多个节点上的20个分区中,使用StateSpec.function(trackStateFunc _).numPartitions(20)
创建。在这种状态下,我们只有几个键(~100)映射到Sets
,有大约160,000个条目,这些条目在整个应用程序中都会增长。整个状态最多为3GB
,可由群集中的每个节点处理。在每个批次中,一些数据被添加到一个状态,但直到过程结束时才被删除,即大约15分钟。
在遵循应用程序UI时,与其他批次相比,每10个批次的处理时间非常长。见图:
黄色字段代表高处理时间。
更详细的“作业”视图显示,在这些批次中,在某个点发生,恰好在“跳过”所有20个分区时。或者这就是用户界面所说的内容。
我对skipped
的理解是每个状态分区都是一个未执行的可能任务,因为它不需要重新计算。但是,我不明白为什么每个工作中skips
的数量会有所不同,以及为什么上一个工作需要这么多处理。无论状态大小如何,都会出现更高的处理时间,只会影响持续时间。
这是mapWithState()
功能中的错误还是这种预期的行为?底层数据结构是否需要某种重新排列,状态中的Set
是否需要复制数据?或者它更可能成为我申请中的缺陷?
答案 0 :(得分:10)
这是mapWithState()功能中的错误还是这个错误 行为?
这是预期的行为。您看到的高峰是因为您的数据在给定批次结束时被检查点。如果您注意到较长批次的时间,您会发现它每100秒持续发生一次。这是因为检查点时间是常数,并且是按照batchDuration
计算的,这是您与数据源进行通信以读取批次乘以某个常量的频率,除非您明确设置{{1}间隔。
以下是DStream.checkpoint
的相关代码:
MapWithStateDStream
override def initialize(time: Time): Unit = {
if (checkpointDuration == null) {
checkpointDuration = slideDuration * DEFAULT_CHECKPOINT_DURATION_MULTIPLIER
}
super.initialize(time)
}
的位置:
DEFAULT_CHECKPOINT_DURATION_MULTIPLIER
与您所看到的行为完全一致,因为您的批量持续时间是每10秒=> 10 * 10 = 100秒。
这是正常的,这是使用Spark保持状态的成本。您的优化可能是考虑如何最小化您必须保留在内存中的状态的大小,以便尽可能快地进行此序列化。另外,确保数据遍布足够的执行程序,以便状态在所有节点之间均匀分布。此外,我希望您已打开Kryo Serialization而不是默认的Java序列化,这可以为您带来有意义的性能提升。
答案 1 :(得分:1)
除了接受的答案,指出与检查点相关的序列化价格之外,还有另一个鲜为人知的问题可能导致spikey行为:驱逐已删除的状态。
具体而言,“删除”或“超时”状态不会立即从地图中删除,而是标记为删除,并且仅在序列化过程中实际删除[在Spark 1.6.1中,请参阅writeObjectInternal()]
这有两个性能影响,每10批次只发生一次: