假设存在这样一种场景,需要实时对运行在我们集群上的程序进行日志监控。但是程序的监控规则经常变更。这个时候就需要我们在处理各程序日志数据的时候要实时和当前程序的监控规则进行匹配判断,而且监控规则的变更要实时的被我们处理逻辑感知到。
这个时候就可以使用广播状态,将程序的日志数据看做是一个流ActionStream,监控规则数据也看做是一个流RuleStream,将RuleStream流中数据下发到ActionStream流中,使得在ActionStream流中每一个Task都能获取到RuleStream流中所有数据。这种行为称为广播,RuleStream流称为广播流
,ActionStream称为非广播流
,流入到ActionStream流中的rule数据称之为广播数据,放入到Flink的状态中就称之为广播状态。
下边我们就通过一个简单的例子来学习理解一下
我们将kafka topic名为test中的数据读取到flink中作为非广播流--actionStream,kafka topic名为test_1中的数据读取到flink中作为广播流--ruleStream。当我们处理actionStream中每条记录时去和当前ruleStream最新的记录进行最简单的连接操作。我们通过BroadcastState
(本质上是一个MapState)这个广播状态来保存ruleStream中的最新记录。
主要的操作步骤
1.定义一个广播流
// 广播状态描述
val broadcastStateDesc: MapStateDescriptor[String, String] =
new MapStateDescriptor[String, String]("broadcast-desc", classOf[String], classOf[String])
// 将普通的非广播流转为广播流
val ruleStream: BroadcastStream[String] = normalStream.broadcast(broadcastStateDesc)
将一个正常非广播流转化为广播流时需要指定它的广播状态描述,并且只能是 MapStateDescriptor类型,在后续的处理中可通过该描述获取到广播状态。
2.连接非广播流和广播流
val connectedStream: BroadcastConnectedStream[(String, Int), String] = actionStream.connect(ruleStream)
通过connect算子来将两条流连接在一起,此时广播流ruleStream就会被广播到非广播流actionStream中,得到的是一个BroadcastConnectedStream的流。BroadcastConnectedStream流本质上包含了广播流ruleStream和非广播流actionStream。
3.后续通过process算子来处理BroadcastConnectedStream流
connectedStream.process(...)
此时process算子中的参数类型会根据非广播流actionStream的类型分为两种。如果actionStream有经过keyBy算子操作后转为KeyedStream类型那么process()中为KeyedBroadcastProcessFunction否则为BroadcastProcessFunction。(此处就只简单使用下KeyedBroadcastProcessFunction,它们两具体的区别和功能大家去参考下官网哈)。在使用上都有两个方法:processElement处理非connected流数据并且只可读取广播状态,processBroadcastElement处理connectedStream流数据并且可读写广播状态。因为flink里面没有跨任务通信的机制,在一个任务实例中的修改不能在并行任务间传递。 得保证BroadcastState在算子的并行实例是相同的,所以不能让单个任务去修改状态,只能让广播方修改。
4.代码如下
def main(args: Array[String]): Unit = {
// 获取执行流处理引擎
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
// 行为流 -- 非广播流
val actionStream: KeyedStream[(String, Int), String] = env
.addSource(new FlinkKafkaConsumer010[String]("test", new SimpleStringSchema(), initProps()))
.map((_, 1))
.keyBy(new KeySelector[(String, Int), String] {
override def getKey(in: (String, Int)): String = in._1
})
// 广播状态描述
val broadcastStateDesc: MapStateDescriptor[String, String] =
new MapStateDescriptor[String, String]("broadcast-desc", classOf[String], classOf[String])
// 规则流 -- 广播流
val ruleStream: BroadcastStream[String] = env
.addSource(new FlinkKafkaConsumer010[String]("test_1", new SimpleStringSchema(), initProps()))
.broadcast(broadcastStateDesc) // 将基础流转为广播流的时候需要指定广播流的描述信息
// 使用connect算子将 主体基本流 和 广播流连接起来
val connectedStream: BroadcastConnectedStream[(String, Int), String] = actionStream.connect(ruleStream)
// 处理连接流数据
connectedStream
.process(new MyKeyedBroadcastProcessFunction(broadcastStateDesc))
.print()
env.execute("broadcast_stream")
}
class MyKeyedBroadcastProcessFunction(broadcastStateDesc: MapStateDescriptor[String, String])
extends KeyedBroadcastProcessFunction[String, (String, Int), String, String] {
// 每当 主体基本流新增一条记录,该方法就会执行一次
override def processElement(in1: (String, Int),
readOnlyCtx: KeyedBroadcastProcessFunction[String, (String, Int), String, String]#ReadOnlyContext,
collector: Collector[String]): Unit = {
// 从 广播状态中根据key获取数据(规则数据)
val ruleString: String = readOnlyCtx.getBroadcastState(broadcastStateDesc).get("rule")
collector.collect(in1 + ruleString)
}
// 每当 广播流新增一条记录,该方法就会执行一次
override def processBroadcastElement(in2: String,
ctx: KeyedBroadcastProcessFunction[String, (String, Int), String, String]#Context,
collector: Collector[String]): Unit = {
// 获取广播状态并更新状态数据(规则数据)
ctx.getBroadcastState(broadcastStateDesc).put("rule", in2)
}
}
下边说明需要注意的是:我们仅对BroadcastState中key为"rule"对应的value值进行更改操作
第一步,生产数据step_01到test_1中,此时控制台没有打印信息
但此时,我们已经将step_01这条数据存放到了broadcastState中
第二步,生产数据step_02到test中,此时控制台打印信息(step_02,1)step_01
说明actionStream中的数据和从broadcastState捕获到规则数据拼接并打印
第三步,生产数据step_03到test中,actionStream中的数据和从broadcastState捕获到规则数据拼接并打印
第四步,生产数据step_04到test_1中,此时控制台没有打印信息
但此时,我们已经更新了broadcastState中存放的数据为step_04
第五步,生产数据step_05到test中,根据打印信息可以得知,我们当前获取到了broadcastState中最新的值
也就是,我们的非广播流数据实时的感知到广播流数据的流动情况
以后我们遇到这种监控程序日志时,监控规则经常变更的需求时就可以考虑使用广播流来进行处理。