附代码|Flink实时广播实战讲解

Distributed Cache为我们提供了一种扩展数据的方案,但有些时个并不能满足需求,如我们有一个MySql表中存储了部分一些字典数据,并且它可能随时更新,这时我们需要动态感知其变化(近实时)来对数据进行计算。

这时可以使用一个通用的做法:将小"表"广播出去。以下是我们运行类all.in.one.c06.Chapter06时,WebUI给出的图:

其主要的实现逻辑在

someDataStream
    .connect(toBroadcastStream.broadcast(descriptor))
    .process(new C06BroadcastProcessFunction(descriptor))
    .print()

将toBroadcastStream广播出去,使用connect将两个流连接,会得到一个BroadcastConnectedStream,然后分别处理当数据流和广播流数据到达时的逻辑即可。

在处理广播数据时,使用了方法ctx.getBroadcastState,它返回的是BroadcastState。它专为广播设计的,实现了Flink中的State接口。

State

State是Flink在流计算过程中,计算节点的中间计算结果或元数据属性,如聚合过程中计算中间聚合结果、Source为Kafka时记录offset等。官方说明即:State就是与时间相关的,Apache Flink任务的内部数据(计算数据和元数据属性)的快照。

State主要有两大作用:

  1. 增量计算:流计算中,计算场景大多数是增量计算,我们要基于上一次计算的结果之上进行处理,State提供了计算数据的存储能力(持久化)

  2. Failover:在出现机器、网络、脏数据等等原因出现程序错误任务重启时,为了保证我们可以重新恢复现场,需要State提供的数据持久化能力支持(Checkpoint)

简单介绍下Flink提供的四种常用state存储实现:

  1. 基于内存的HeapStateBackend: 在debug模式使用,不建议在生产模式下应用

  2. 基于HDFS的FsStateBackend: 分布式文件持久化,每次读写都产生网络IO,整体性能不佳

  3. 基于RocksDB的RocksDBStateBackend: 本地文件+异步HDFS持久化,生产环境常用

  4. 基于Niagara(Alibaba内部实现)NiagaraStateBackend: 分布式持久化,在Alibaba生产环境应用

除了上面提到的BroadcastState,State通常按以下方式划分:

  1. KeyedState:一般与KeyingBy产生的Keyed Stream配合使用,即每个key可以有它自己的值

  • ValueState<T>: 存储单值

  • ListState<T>: 存储列表

  • ReducingState<T>: 保存单值,但可以定义一个ReduceFunction来表示和原来已经存在的值进行何种方式的聚合

  • AggregatingState<IN, OUT>: 保存单值,与ReducingState唯一不同在于聚合后的值可能类型不同

  • MapState<UK, UV>: 保存映射列表

  • OperatorState: 定义Source时,保存source读取的位置(如kafka的offset)

  • package all.in.one.c06
    
    
    import all.in.one.utils.random
    import org.apache.flink.api.common.state.MapStateDescriptor
    import org.apache.flink.streaming.api.functions.co.BroadcastProcessFunction
    import org.apache.flink.streaming.api.functions.source.SourceFunction
    import org.apache.flink.streaming.api.scala._
    import org.apache.flink.util.Collector
    import org.joda.time.DateTime
    
    
    object Chapter06 extends App {
    
    
      val env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI()
    
    
      // 定义要广播的stream
      val toBroadcastStream = env.addSource(new SourceFunction[(String, String)] {
        var running = true
    
    
        override def run(ctx: SourceFunction.SourceContext[(String, String)]): Unit =
          do {
            val now = DateTime.now()
            val state = if (now.getSecondOfMinute % 2 == 0) "even" else "odd"
            val timeStr = now.toString("HH:mm:ss.SSS")
            ctx.collect(state -> timeStr)
            Thread.sleep(random.int(5000, 15000).toLong)
          } while (running)
    
    
        override def cancel(): Unit = running = false
      })
      // 定义一个正常stream
      val someDataStream = env.addSource(new SourceFunction[Int] {
        var running = true
        override def run(ctx: SourceFunction.SourceContext[Int]): Unit =
          do {
            ctx.collect(1)
            Thread.sleep(1000L)
          } while (running)
        override def cancel(): Unit = running = false
      })
    
    
      // 定义Descriptor
      val descriptor: MapStateDescriptor[String, String] =
        new MapStateDescriptor("C06 - broadcast", classOf[String], classOf[String])
      // 将数据流与广播流连接
      someDataStream
        .connect(toBroadcastStream.broadcast(descriptor))
        .process(new C06BroadcastProcessFunction(descriptor))
        .print()
      env.execute("Chapter 06")
    
    
      /**
        * 定义处理流的process function
        */
      class C06BroadcastProcessFunction(mapStateDescriptor: MapStateDescriptor[String, String])
          extends BroadcastProcessFunction[Int, (String, String), String] {
    
    
        /**
          * 当数据流数据到达的时候执行此方法处理
          */
        override def processElement(
            value: Int,
            ctx: BroadcastProcessFunction[Int, (String, String), String]#ReadOnlyContext,
            out: Collector[String]
        ): Unit = {
          val even = ctx.getBroadcastState(mapStateDescriptor).get("even")
          val odd = ctx.getBroadcastState(mapStateDescriptor).get("odd")
          val evenStr = if (even != null) s"even -> [$even]" else ""
          val oddStr = if (odd != null) s"odd -> [$odd]" else ""
          val toPrint =
            if (evenStr.isEmpty && oddStr.isEmpty) "无"
            else if (evenStr.isEmpty && oddStr.nonEmpty) oddStr
            else if (evenStr.nonEmpty && oddStr.isEmpty) evenStr
            else s"$evenStr, $oddStr"
          out.collect(toPrint)
        }
    
    
        /**
          * 当广播流数据到达的时候执行此方法处理
          */
        override def processBroadcastElement(
            value: (String, String),
            ctx: BroadcastProcessFunction[Int, (String, String), String]#Context,
            out: Collector[String]
        ): Unit = {
          // 将数据更新到state中
          ctx.getBroadcastState(mapStateDescriptor).put(value._1, value._2)
        }
      }
    
    
    }
    

    喜欢flink开发的可以加微信:vx(喜欢学习的来哈)

    公众号回复:“资料全集”,海量PPT等你来拿。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值