1、spark运行机制
spark是在driver端将由一系列的算子形成的DAG封装成一个个的Task的形式发送到executor中执行的方式运行的,每一个task都是以序列化之后通过网络的形式发送。
2、为什么需要广播?
实际应用场景中会出现需要在task中包含一个很大的变量obj 100M(比如从mysql中查询到的用户信息),那么每次发送task的时候都会把这个obj封装起来一起发送过去,非常的占用网络带宽,而且task体积大占用executor更多的内存。
比如:有一个任务T1, 20个分区,每个分区10个task,一个task里面包含一个100M的数据,则需要占用20G的网络开销。
那么如何减少网络开销?减少task的体积? ==> 广播
3、如何广播,原理是?
Spark有两种广播方式:一种是HttpBroadcast(Spark2.1.0已经移除),另一种是TorrentBroadcast
HttpBroadcast:当执行broadcast.getValue()时,executor没有这个值是,都会通过http网络从driver请求获取,这样会导致driver端压力很大
TorrentBroadcast:在HttpBroadcast的基础上改造成先从“其他executor查找”,如果没有在请求driver。
-
3.1在driver端执行Broadcast<String> broadcast = jsc.broadcast("大变量");此时driver会将这个值“序列化”,然后得到一个唯一的id,最后生成这个broadcast返回,这个broadcast里面没有“大变量”这个值,只有一个id。所以广播变量一定需要实现序列化接口
Broadcast对象的内部变量
-
3.2、在算子中使用broadcast,这样broadcast就会被封装在task中一起封装序列化发送到executor中,当执行到broadcast.getValue()时,会根据这个id首先去executor找,如果没有再通过网络的方式请求driver(一定有),最后缓存到executor的blockManager中。
由此可以发现,每一个executor中只需要请求一次,后续在他上面运行的task都不需要再次请求driver消耗网络资源。比如现在3个executor,那么网络开销就变成了300M,而且这个大变量是保存在executor中经常要用到的,不需要频繁gc。
4、代码演示==> 在算子执行broadcast.getValue()才有效
````
package com.bigdata.study;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.broadcast.Broadcast;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* spark broadcast 测试
*
* 几点总结:
* 1、broadcasts的值必须可以序列化
*
* 2、要在算子中使用broadcast.getValue(),而不是在main方法中getValue的到值之后再被算子使用,这样是多此一举
*
* 3、算子中的外部变量也必须是可以序列化的,否则封装为task时,报task序列化报错
* 也就是说,即使不适用广播,直接在main方法里面创建一个不可序列化的对象(比如:mysql 的conn)
* 然后在算子中使用这个conn,也是不可以的
*
* 4、String、int、long、map等基本数据类型都已经实现了Serializable,所以可以直接广播或者在算子中直接使用
* mysql的conn、kafka producer、 jedis、hbase client等都没有实现序列化,所以不能广播,只能在算子中创建连接来使用
*
* 5、一般来说,广播的信息是:不可变的、较大的(几十M以上)数据。比如说从mysql里面查出来的一些匹配信息
*
* @Author liufu
* @E-mail 1151224929@qq.com
* @CreateTime 2018/11/19 11:11
*/
public class BroacostTest {
public static void main(String[] args) throws InterruptedException {
SparkConf conf = new SparkConf();
conf.setMaster("local[2]");
conf.setAppName("BroacostTest");
JavaSparkContext jsc = new JavaSparkContext(conf);
// 创建spark RDD
JavaRDD<String> rdd = jsc.textFile("e:/data.txt");
/**
* 广播一个变量TreeMap
* driver会把treeMap序列化之后得到一个唯一id,然后生成一个Broadcast对象:treeMapBroadcast
* treeMapBroadcast里面并没有包含数据:"很大的张三",只是有一个id
* 只有执行treeMapBroadcast.getValue()时,才会通过这个id去executor里面找,如果找不到,再请求driver(一定有),然后缓存到executor
*/
HashMap<String, String> brMap = new HashMap<>();
brMap.put("name", "很大的张三");
Broadcast<Map<String, String>> mapBroadcast = jsc.broadcast(brMap);
//广播使用方式一:在算子中使用treeMapBroadcast.getValue() ==> 正确,因为算子在executor中执行
rdd.foreachPartition(new VoidFunction<Iterator<String>>() {
@Override
public void call(Iterator<String> iterator) throws Exception {
/**
* 这个算子会封装成task发送到executor执行,而不会在这个main方法中执行
* 当执行到treeMapBroadcast.getValue()时,会先请求executor的blockManager
* 如果executor没有,则会请求driver的blockManager(一定有)
*/
Map<String, String> value = mapBroadcast.getValue();
System.out.println(value.get("name"));
}
});
//广播使用方式二:在driver取出,然后在算子中使用 ==> 错误,在driver取出来之后,又是一个大变量的具体值,会和算子一起封装在task中发送到executor执行,多此一举
Map<String, String> value = mapBroadcast.getValue();
rdd.foreachPartition(new VoidFunction<Iterator<String>>() {
@Override
public void call(Iterator<String> iterator) throws Exception {
System.out.println(value.get("name"));
}
});
}
}
````