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。

posted @ 2022-03-09 11:34  Black_Knight  阅读(1015)  评论(0编辑  收藏  举报