Broadcast是一份存储在TaskManager内存中的只读的缓存数据
使用场景:
在执行job的过程中需要反复使用的数据,为了达到数据共享,减少运行时内存消耗,我们就用广播变量进行广播
好处:
1、从clinet端将一份需要反复使用的数据封装到广播变量中,分发到每个TaskManager的内存中保存
2、TaskManager中的所有Slot所管理的线程在执行task的时候如果需要用到该变量就从TaskManager的内存中读取数据,达到数据共享的效果,与Spark中的广播变量效果时一样
注意点:
1、广播变量中封装的数据集大小要适宜,太大,容易造成OOM
2、广播变量中封装的数据要求能够序列化,否则不能在集群中进行传输
广播变量的分类
通常数据量特别大或者某个key的数据特别多的情况下shuffle可能会产生数据倾斜,所以我们可以用broadcast的mapjoin来优化:具体优化案例如下
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()
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)
*/
}
}
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中的广播变量实现这样的效果å
}
}