Flink中的广播变量-简单应用

Flink中的广播变量

Broadcast是一份存储在TaskManager内存中的只读的缓存数据

使用场景:

在执行job的过程中需要反复使用的数据,为了达到数据共享,减少运行时内存消耗,我们就用广播变量进行广播

好处:

1、从clinet端将一份需要反复使用的数据封装到广播变量中,分发到每个TaskManager的内存中保存

2、TaskManager中的所有Slot所管理的线程在执行task的时候如果需要用到该变量就从TaskManager的内存中读取数据,达到数据共享的效果,与Spark中的广播变量效果时一样

注意点:

1、广播变量中封装的数据集大小要适宜,太大,容易造成OOM

2、广播变量中封装的数据要求能够序列化,否则不能在集群中进行传输

广播变量的分类

广播变量的使用

DataSet广播变量使用

通常数据量特别大或者某个key的数据特别多的情况下shuffle可能会产生数据倾斜,所以我们可以用broadcast的mapjoin来优化:具体优化案例如下

普通的dataset-join

    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

    val ds1: DataSet[(Int, Char)] = env.fromElements((1, '男'), (2, '女'))

    val ds2: DataSet[(Int, String, Int, String, Int)] = env.fromElements((1001, "小红", 2, "北京市望京西里", 8888), (1002, "Mike", 1, "上海市爱民广场", 1000), (1003, "小小", 2, "深圳市财富港", 1888))

    ds1.join(ds2)
      .where(a => a._1)
      .equalTo(a => a._3)
      .map(a => (a._2._1, a._2._2, a._1._2, a._2._4, a._2._5))
      .print()

broadcast的mapjoin

package com.shufang.broadcast

import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration


import scala.collection.mutable


object BoundedBroadCastDemo {
  def main(args: Array[String]): Unit = {


    val env: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment

    val ds1: DataSet[(Int, Char)] = env.fromElements((1, '男'), (2, '女'))

    val ds2: DataSet[(Int, String, Int, String, Int)] = env.fromElements((1001, "小红", 2, "北京市望京西里", 8888), (1002, "Mike", 1, "上海市爱民广场", 1000), (1003, "小小", 2, "深圳市财富港", 1888))



    //ds2是主set,一般将比较小的ds1放进一个Map中
    ds2.map(new RichMapFunction[(Int, String, Int, String, Int), (Int, String, Char, String, Int)] {

      //用来存储从广播变量中读取的数据到TaskManager的内存中
      //这里的map存储的是性别信息,也就是ds1中的类型(Int,Char),接下来我们需要在open方法中对map进行初始化
      //NOTE:这里的container默认声明的是一个scala中的不可变map:immutable.Map[A, B],我们需要手动声明mutable
      var container: mutable.Map[Int, Char] = _

      /**
       * 用来进行初始化操作,这个方法只会执行一次
       *
       * @param parameters
       */
      override def open(parameters: Configuration): Unit = {

        //1.首先对容器进行初始化,这里不能用new mutable.Map(),而应该使用apply之后的mutable.Map()
        container = mutable.Map()

        //2.获取广播变量中的广播数据,这里返回的是一个java的list,我们需要手动声明,因为scala的底层没有这么智能
        val list: java.util.List[(Int, Char)] = getRuntimeContext.getBroadcastVariable("genderInfo")

        //需要导入这个类,通过将java中的list转化成scala中的list
        import scala.collection.JavaConversions._
        //3.将获取到的数据一次遍历添加到container中,不能用.+,因为+会形成一个新的container
        for (item <- list) {
          container.put(item._1, item._2)
        }

      }

      /**
       * 用来处理map方法中的逻辑与转换,每次处理dataset中的一个元素,这个方法都会触发一次
       * @param value
       * @return
       */
      override def map(value: (Int, String, Int, String, Int)): (Int, String, Char, String, Int) = {

        val key: Int = value._3
        val gender: Char = container.getOrElse(key, 'x')
        (value._1, value._2, gender, value._4, value._5)

      }

      /**
       * 用来关闭开启的资源,如果没有 ,可以不用管,这个方法也只会被调用一次
       */
      override def close(): Unit = super.close()
    }
    ).withBroadcastSet(ds1, "genderInfo")
      .print()

    /**
     * 最终的结果与join操作的一致:
     * (1001,小红,女,北京市望京西里,8888)
     * (1002,Mike,男,上海市爱民广场,1000)
     * (1003,小小,女,深圳市财富港,1888)
     */
  }
}

DataStream广播流变量使用

package com.shufang.broadcast

import org.apache.flink.api.common.state.MapStateDescriptor
import org.apache.flink.api.common.typeinfo.{BasicTypeInfo, TypeInfo}
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


/**
 * 主要是实现一个无界流的广播刘变量的使用,实现mapjoin
 */
object UnboundedBroadCastDemo {
  def main(args: Array[String]): Unit = {
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment


    val ds1: DataStream[(Int, Char)] = env.fromElements((1, '男'), (2, '女'))

    val ds2: DataStream[(Int, String, Int, String, Double)] = env.socketTextStream("localhost", 9999)
      .map(
        line => {
          val strings: Array[String] = line.split(",")
          val id: Int = strings(0).trim.toInt
          val name: String = strings(1).trim
          val gendercode: Int = strings(2).trim.toInt
          val address: String = strings(3).trim
          val price: Double = strings(4).trim.toDouble
          (id, name, gendercode, address, price)
        }
      )

    //自定义状态描述器
    val genderInfo: MapStateDescriptor[Integer, Character] = new MapStateDescriptor(
      "genderInfo",
      BasicTypeInfo.INT_TYPE_INFO,
      BasicTypeInfo.CHAR_TYPE_INFO
    )

    //通过将ds1将自己进行广播
    val bcStream: BroadcastStream[(Int, Char)] = ds1.broadcast(genderInfo)

    //这里可以尝试用mapWithState来代替process的状态管理
    ds2.connect(bcStream).process(
      new BroadcastProcessFunction[(Int, String, Int, String, Double), (Int, Char), (Int, String, Char, String, Double)]() {


        //一、首先将广播变量的信息放进taskManager的内存中,通过State.put()方法来实现
        override def processBroadcastElement(value: (Int, Char),
                                             ctx: BroadcastProcessFunction[(Int, String, Int, String, Double), (Int, Char), (Int, String, Char, String, Double)]#Context,
                                             out: Collector[(Int, String, Char, String, Double)]): Unit = {
          //put:将genderinfo状态中的k,v放进taskManager的内存中
          ctx.getBroadcastState(genderInfo).put(value._1, value._2)
        }

        //二、用来处理主datastream的方法,进行字段之间的拼接
        override def processElement(value: (Int, String, Int, String, Double),
                                    ctx: BroadcastProcessFunction[(Int, String, Int, String, Double), (Int, Char), (Int, String, Char, String, Double)]#ReadOnlyContext,
                                    out: Collector[(Int, String, Char, String, Double)]): Unit = {
          // 获取主stream需要被转换的k作为k,去状态中查询
          //1.从value中获取gender信息
          val gendercode: Int = value._3

          val gender: Char = ctx.getBroadcastState(genderInfo).get(gendercode).charValue()

          //2.封装OUT的类型
          val PeopleInfo: (Int, String, Char, String, Double) = (value._1, value._2, gender, value._4, value._5)

          //将数据传给下一个task
          out.collect(PeopleInfo)
        }
      }
    ).print(s"${Thread.currentThread().getName}:")



    //进行connect操作
    env.execute("stream-broadcast-var")

    /*********************************************
     * main::2> (1001,小红,女,北京市望京西里,1888.0)*
     * main::4> (1003,小丽,女,广州市珠江CDB,1888.0)*
     * main::3> (1002,小博,男,上海市人民广场,1888.0)*
     * main::5> (1004,小花,女,深圳市市民中心,1888.0)*
     * main::7> (1003,小丽,女,广州市珠江CDB,1888.0)*
     * main::6> (1002,小博,男,上海市人民广场,1888.0)*
     * main::8> (1004,小花,女,深圳市市民中心,1888.0)*
     * main::2> (1003,小丽,女,广州市珠江CDB,1888.0)*
     * main::1> (1002,小博,男,上海市人民广场,1888.0)*
     * main::3> (1004,小花,女,深圳市市民中心,1888.0)*
     *********************************************/

    //TODO怎么使用spark中的广播变量实现这样的效果å
  }
}

发布了85 篇原创文章 · 获赞 5 · 访问量 5885

猜你喜欢

转载自blog.csdn.net/shufangreal/article/details/105719971