反压(Back Pressure)机制主要用来解决流处理系统中,处理速度比摄入速度慢的情况。是控制流处理中批次流量过载的有效手段。
反压机制原理
Spark Streaming中的反压机制是Spark 1.5.0推出的新特性,可以根据处理效率动态调整摄入速率。
当批处理时间(Batch Processing Time
)大于批次间隔(Batch Interval,即 BatchDuration
)时,说明处理数据的速度小于数据摄入的速度,持续时间过长或源头数据暴增,容易造成数据在内存中堆积,最终导致Executor OOM或任务奔溃。
在这种情况下,若是基于Receiver的数据源,可以通过设置spark.streaming.receiver.maxRate
来控制最大输入速率;若是基于Direct的数据源(如Kafka Direct Stream),则可以通过设置spark.streaming.kafka.maxRatePerPartition
来控制最大输入速率。当然,在事先经过压测,且流量高峰不会超过预期的情况下,设置这些参数一般没什么问题。但最大值,不代表是最优值,最好还能根据每个批次处理情况来动态预估下个批次最优速率。在Spark 1.5.0以上,就可通过背压机制来实现。开启反压机制,即设置spark.streaming.backpressure.enabled
为true
,Spark Streaming会自动根据处理能力来调整输入速率,从而在流量高峰时仍能保证最大的吞吐和性能。
Spark Streaming的反压机制主要是通过RateController
组件来实现。RateController
继承自接口StreamingListener
,并实现了onBatchCompleted
方法。每一个Batch处理完成后都会调用此方法,具体如下:
override def onBatchCompleted(batchCompleted: StreamingListenerBatchCompleted) {
val elements = batchCompleted.batchInfo.streamIdToInputInfo
for {
// 处理结束时间
processingEnd <- batchCompleted.batchInfo.processingEndTime
// 处理时间,即`processingEndTime` - `processingStartTime`
workDelay <- batchCompleted.batchInfo.processingDelay
// 在调度队列中的等待时间,即`processingStartTime` - `submissionTime`
waitDelay <- batchCompleted.batchInfo.schedulingDelay
// 当前批次处理的记录数
elems <- elements.get(streamUID).map(_.numRecords)
} computeAndPublish(processingEnd, elems, workDelay, waitDelay)
}
可以看到,接着又调用的是computeAndPublish
方法,如下:
private def computeAndPublish(time: Long, elems: Long, workDelay: Long, waitDelay: Long): Unit =
Future[Unit] {
// 根据处理时间、调度时间、当前Batch记录数,预估新速率
val newRate = rateEstimator.compute(time, elems, workDelay, waitDelay)
newRate.foreach { s =>
// 设置新速率
rateLimit.set(s.toLong)
// 发布新速率
publish(getLatestRate())
}
}
更深一层,具体调用的是rateEstimator.compute
方法来预估新速率,如下:
def compute(
time: Long,
elements: Long,
processingDelay: Long,
schedulingDelay: Long): Option[Double]
该方法是接口RateEstimator
中的方法,会计算出新的批次每秒应摄入的记录数。PIDRateEstimator
,即PID速率估算器
,是RateEstimator
的唯一实现,具体估算逻辑可看PIDRateEstimator.compute
方法,逻辑很复杂,用到了微积分相关的知识,总之,一句话,即根据当前Batch的结果和期望的差值来估算新的输入速率。
反压机制相关参数
spark.streaming.backpressure.enabled
默认值false
,是否启用反压机制。
spark.streaming.backpressure.initialRate
默认值无,初始最大接收速率。只适用于Receiver Stream
,不适用于Direct Stream
。
spark.streaming.backpressure.rateEstimator
默认值pid,速率控制器,Spark 默认只支持此控制器,可自定义。
spark.streaming.backpressure.pid.proportional
默认值1.0,只能为非负值。当前速率与最后一批速率之间的差值对总控制信号贡献的权重。用默认值即可。
spark.streaming.backpressure.pid.integral
默认值0.2,只能为非负值。比例误差累积对总控制信号贡献的权重。用默认值即可。
spark.streaming.backpressure.pid.derived
默认值0.0,只能为非负值。比例误差变化对总控制信号贡献的权重。用默认值即可。
spark.streaming.backpressure.pid.minRate
默认值100,只能为正数,最小速率。
反压机制的使用
启用反压
//启用反压
conf.set("spark.streaming.backpressure.enabled","true")
//最小摄入条数控制
conf.set("spark.streaming.backpressure.pid.minRate","1")
//最大摄入条数控制
conf.set("spark.streaming.kafka.maxRatePerPartition","12")
注意:
A. 反压机制真正起作用时需要至少处理一个批
由于反压机制需要根据当前批的速率,预估新批的速率,所以反压机制真正起作用前,应至少保证处理一个批。
B. 如何保证反压机制真正起作用前应用不会崩溃
要保证反压机制真正起作用前应用不会崩溃,需要控制每个批次最大摄入速率。若为Direct Stream
,如Kafka Direct Stream
,则可以通过spark.streaming.kafka.maxRatePerPartition
参数来控制。此参数代表了 每秒每个分区最大摄入的数据条数。假设BatchDuration
为10秒,spark.streaming.kafka.maxRatePerPartition
为12条,kafka topic 分区数为3个,则一个批(Batch)最大读取的数据条数为360条(3*12*10=360
)。同时,需要注意,该参数也代表了整个应用生命周期中的最大速率,即使是背压调整的最大值也不会超过该参数。
查看日志
创建速率控制器
INFO PIDRateEstimator: Created PIDRateEstimator with proportional = 1.0, integral = 0.2, derivative = 0.0, min rate = 1.0
计算当前批次速率
// records 记录数(对应WebUI: Input Size)
// processing time 处理时间,毫秒(对应WebUI: Processing Time)
// scheduling delay 调度时间,毫秒(对应WebUI: Scheduling Delay)
TRACE PIDRateEstimator:
time = 1558888897224, # records = 33, processing time = 24548, scheduling delay = 8
预估新批次速率
TRACE PIDRateEstimator:
latestRate = -1.0, error = -2.344305035033404
latestError = -1.0, historicalError = 0.0010754440280267231
delaySinceUpdate = 1.558888897225E9, dError = -8.623482003280801E-10
第一次计算跳过速率估计
TRACE PIDRateEstimator: First run, rate estimation skipped
当前批次没有记录或没有延迟则跳过速率估计
TRACE PIDRateEstimator: Rate estimation skipped
以新的预估速率运行
TRACE PIDRateEstimator: New rate = 1.0
查看WebUI
可以看到,开启反压后,摄入速率Input Rate
可以根据处理时间Processing Time
来调整,从而保证应用的稳定性。