Flink广播流使用举例-过滤字符串流
1. 参考资料
https://nightlies.apache.org/flink/flink-docs-release-1.14/zh/docs/dev/datastream/fault-tolerance/broadcast_state/
2. 过滤字符串流
需求:
- 打开一个SocketStream实时接收用户的输入,过滤掉数据流中不想要的字符串,把剩余的结果打印出来。
- 用另外一个SocketStream接收我们不想看到的字符串,为了简化实现,在同一时刻最多只能设置一个不想看到的字符串。
3. 实现步骤
- 定义两个输入流,接受输入的字符串
val wordStream: DataStream[String] = env.socketTextStream("localhost", 12345, '\n')
val ruleStream: DataStream[String] = env.socketTextStream("localhost", 12346, '\n')
- 定义一个MapStateDescriptor来描述我们要广播的数据的格式,此处是String类型,我们想要的广播字符串
val ruleStateDescriptor: MapStateDescriptor[String, String] = new MapStateDescriptor[String, String]( "RuleBroadcastState", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO )
- 把ruleStream注册成广播流
val broadcastStream: BroadcastStream[String] = ruleStream.broadcast(ruleStateDescriptor)
- 非广播流使用connect方法连接广播流,在process方法中提供BroadcastProcessFunction来处理广播流和非广播流中的数据
val output: DataStream[String] = wordStream.connect(broadcastStream).process( new BroadcastProcessFunction[String, String, String] { override def processElement(in1: String, readOnlyContext: BroadcastProcessFunction[String, String, String]#ReadOnlyContext, collector: Collector[String]): Unit = { val filter: String = readOnlyContext.getBroadcastState(ruleStateDescriptor).get("filter") if (filter == null || !in1.equals(filter)) { collector.collect(in1) } } override def processBroadcastElement(in2: String, context: BroadcastProcessFunction[String, String, String]#Context, collector: Collector[String]): Unit = { context.getBroadcastState(ruleStateDescriptor).put("filter", in2) } } )
- 实现 BroadcastProcessFunction 中的 processElement和processBroadcastElement方法
- processElement用来处理非广播流中的数据,传入的参数中有一个readOnlyContext可以获取broadState,不过是只读的。此处从broadcastState中查找key等于"filter"的值,如果没有查找到,或者当前元素不等于查找到的元素,则输出,否则不做任何操作。
- processBroadcastElement用来处理广播流中的数据,此处仅仅是把收到的元素加入到broadState中,key固定为"filter"。
4. 完整代码
import org.apache.flink.api.common.state.MapStateDescriptor import org.apache.flink.api.common.typeinfo.BasicTypeInfo import org.apache.flink.streaming.api.datastream.BroadcastStream import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction import org.apache.flink.streaming.api.scala._ import org.apache.flink.util.Collector object WordCountWithPatternJob { def main(args: Array[String]): Unit = { val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment val wordStream: DataStream[String] = env.socketTextStream("localhost", 12345, '\n') val ruleStream: DataStream[String] = env.socketTextStream("localhost", 12346, '\n') val ruleStateDescriptor: MapStateDescriptor[String, String] = new MapStateDescriptor[String, String]( "RulesBroadcastState", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO ) val broadcastStream: BroadcastStream[String] = ruleStream.broadcast(ruleStateDescriptor) val output: DataStream[String] = wordStream.connect(broadcastStream).process( new BroadcastProcessFunction[String, String, String] { override def processElement(in1: String, readOnlyContext: BroadcastProcessFunction[String, String, String]#ReadOnlyContext, collector: Collector[String]): Unit = { val filter: String = readOnlyContext.getBroadcastState(ruleStateDescriptor).get("filter") if (filter == null || !in1.equals(filter)) { collector.collect(in1) } } override def processBroadcastElement(in2: String, context: BroadcastProcessFunction[String, String, String]#Context, collector: Collector[String]): Unit = { context.getBroadcastState(ruleStateDescriptor).put("filter", in2) } } ) output.print() env.execute("filter word streaming") } }
5. 总结
称做广播流应该不太严谨,从实现来看实际上的把一个MapState给广播出去,其他流通过connect一个广播流可以获取到这个广播的状态,并且是只读的。
这里有一些 broadcast state 的重要注意事项,在使用它时需要时刻清楚:
-
没有跨 task 通讯:如上所述,这就是为什么只有在
(Keyed)-BroadcastProcessFunction
中处理广播流元素的方法里可以更改 broadcast state 的内容。 同时,用户需要保证所有 task 对于 broadcast state 的处理方式是一致的,否则会造成不同 task 读取 broadcast state 时内容不一致的情况,最终导致结果不一致。 -
**broadcast state 在不同的 task 的事件顺序可能是不同的:**虽然广播流中元素的过程能够保证所有的下游 task 全部能够收到,但在不同 task 中元素的到达顺序可能不同。 所以 broadcast state 的更新不能依赖于流中元素到达的顺序。
-
所有的 task 均会对 broadcast state 进行 checkpoint:虽然所有 task 中的 broadcast state 是一致的,但当 checkpoint 来临时所有 task 均会对 broadcast state 做 checkpoint。 这个设计是为了防止在作业恢复后读文件造成的文件热点。当然这种方式会造成 checkpoint 一定程度的写放大,放大倍数为 p(=并行度)。Flink 会保证在恢复状态/改变并发的时候数据没有重复且没有缺失。 在作业恢复时,如果与之前具有相同或更小的并发度,所有的 task 读取之前已经 checkpoint 过的 state。在增大并发的情况下,task 会读取本身的 state,多出来的并发(
p_new
-p_old
)会使用轮询调度算法读取之前 task 的 state。 -
不使用 RocksDB state backend: broadcast state 在运行时保存在内存中,需要保证内存充足。这一特性同样适用于所有其他 Operator State。