流处理中状态的概念
流处理中,有个状态(state)的概念:
- 无状态的:当前批次处理完之后,数据只与当前批次有关
- 有状态的:前后批次的数据处理完之后,之间是有关系的
官网的介绍:
http://spark.apache.org/docs/latest/streaming-programming-guide.html#updatestatebykey-operation
updateStateByKey解读
updateStateByKey:返回的是一个新的并且带有状态的DStream,会根据每一个key进行更新,更新的规则是根据自己定义的function来确定的
需要2个步骤:
- 定义state;以wordcount为例,value是作为state来处理的,是根据key来更新我们的value的
- 定义一个state update function:将旧state的值与新state的值根据所定义的规则给相互作用在一起
自定义updateFunction
我们自定义的updateFunction函数如下所示:
def updateFunction(newValues: Seq[Int], preValues: Option[Int]): Option[Int] = {
val curCount = newValues.sum //当前
val preCount = preValues.getOrElse(0) //老的能获取就获取,获取不到就直接赋值为0
Some(curCount + preCount)
}
可以发现针对于newValues定义的类型为Seq,针对于preValues定义的类型为Option
比如有两个批次:
批次1 a a a d d
批次2 b b b c c a
newValues代表当前批次的值,key所对应的新值可能有多个,因此定义为Seq类型
preValues代表以前批次的累加值,key可能存在也可能不存在(比如b和c),因此定义为Option类型
checkpoint
直接运行产生报错
提示我们需要去设置checkpoint,ssc.checkpoint(“.”)
原因在于目前是有状态的,需要当前批次和以前批次的值去累加起来的,因此是需要将这些状态通过checkpoint的方式给落地存起来的
设置完之后,运行程序就完全OK了
目前所存在的问题
关闭Streaming程序后,进行重启,发现原先进来的几个批次处理计算累加过后的数据,在重启之后不再进行展示了,直接是从头开始的,啥都没有了
假设我们的流处理程序某个时间点挂了,重启之后就会发现什么都没了,因此目前的这种方式肯定是还存在一定的问题的
checkpoint官网解读
参照checkpoint在Spark官网的相关内容
官网:
http://spark.apache.org/docs/latest/streaming-programming-guide.html#checkpointing
Spark Streaming的应用程序必须是7*24h的,因此对于可靠性的考虑是必要的(比如系统挂掉、JVM crash等等)。因此对于这些可能存在的问题,Spark Streaming需要我们去checkpoint,将足够的信息给存到可容错的存储系统中去(比如HDFS)
checkpoint的信息可以分为两大类:
- Metadata checkpoint:
包括了Configuration、DStream operations、Incomplete batches(未完成的批次) - Data checkpoint:真正传输过来的数据
何时开启checkpoint
- 使用带状态的transformation算子的时候,比如:updateStateByKey、reduceByKeyAndWindow
- 应用程序的driver端挂掉后,对应的metadata需要能够通过checkpoint的方式给恢复过来
如何配置checkpoint
其实核心代码就一句:
// 当作业挂了时,从checkpoint中去获取StreamingContext的相关内容
val ssc = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext)
这样配置之后,每次重启之后,都会将历史的数据信息给获取到
使用注意点
如果想使用checkpoint,必须保证一点:代码不能发生任何变化;一旦代码发生了变化,也就意味着metadata发生了变化,重启过后,会存在不生效的情况,即对于历史状态的数据信息会读取不到,因此checkpoint这种方式在生产中慎用(尤其是在offset维护的场景中)
代码
object StreamingCheckpointApp {
val checkpointDirectory = "."
def main(args: Array[String]): Unit = {
// 当作业挂了时,从checkpoint中去获取StreamingContext的相关内容
val ssc = StreamingContext.getOrCreate(checkpointDirectory, functionToCreateContext)
ssc.start()
ssc.awaitTermination()
}
def functionToCreateContext(): StreamingContext = {
val ssc = ContextUtils.getStreamingContext(this.getClass.getSimpleName, 5)
ssc.checkpoint(checkpointDirectory)
val lines = ssc.socketTextStream("localhost", 9999)
val result = lines.flatMap(_.split(","))
.map((_,1))
.updateStateByKey(updateFunction)
result.print()
ssc
}
def updateFunction(newValues: Seq[Int], preValues: Option[Int]): Option[Int] = {
val curCount = newValues.sum //当前
val preCount = preValues.getOrElse(0) //老的能获取就获取,获取不到就直接赋值为0
Some(curCount + preCount)
}
}