Spark WordCount 源码解析
学习Spark有一段时间了,现在只处于会用的阶段,对内部代码和原理不怎么熟悉,乘着这几天有时间,解读一下源码,同时也熟悉一下scala语言
环境:
Spark 2.1.0
IntelliJ IDEA 15.0.2
Scala 2.10.4
废话不说,先上完整代码:
然后我们开始逐条代码分析:
if (args.length < 1) {
System.err.println("Usage: JavaWordCount <file>");
System.exit(1);
}
因为我们的程序是计算单词的个数,所以需要有个单词源,即输入文件。所以在程序的开始先判断一下是否有输入目录,如果没有文件目录输入,程序打印错误日志,直接退出。如果输入正常,程序向下执行。
SparkSession spark = SparkSession
.builder()
.appName("JavaWordCount")
.getOrCreate();
在spark2.0之后,引入了SparkSession这个类,SparkSession中包含SparkConf、SparkContext、SQLContext,再使用Spark的API时只需要创建一个SparkSession就可以了。
进入SparkSession中的getOrCreate()方法:
首先先获取一个SparkSession,这个SparkSession可以为null
var session = activeThreadSession.get()
我们先看一下activeThreadSession的声明
private val activeThreadSession = new InheritableThreadLocal[SparkSession]
InheritableThreadLocal 是 ThreadLocal的子类,是用来实现变量的线程安全的
获取SparkSession之后,
if ((session ne null) && !session.sparkContext.isStopped) {
options.foreach { case (k, v) => session.sessionState.conf.setConfString(k, v) }
if (options.nonEmpty) {
logWarning("Using an existing SparkSession; some configuration may not take effect.")
}
return session
}
如果SparkSession不为null,把option中的配置赋值给SparkSession中的conf,然后返回。
如果当前线程没有SparkSession,直接从全局session中获取,看如下分析
session = defaultSession.get()
我们先看一下defaultSession的是什么
private val defaultSession = new AtomicReference[SparkSession]
这里的AutoicReference是jdk中的方法,主要用来的对对象进行原子操作,保证SparkSession对象的原子性(不会被调度机制打断),简单来说,同一时间,只有一个线程或进程对SparkSession进行操作,
if ((session ne null) && !session.sparkContext.isStopped) {
options.foreach { case (k, v) => session.sessionState.conf.setConfString(k, v) }
if (options.nonEmpty) {
logWarning("Using an existing SparkSession; some configuration may not take effect.")
}
return session
}
从option(HashMap)中读取配置信息给SparkSession中的conf设置,然后返回。
最后如果全局session也没有话,那么只能创建一个新的session。
val sparkContext = userSuppliedContext.getOrElse {
// set app name if not given
val randomAppName = java.util.UUID.randomUUID().toString
val sparkConf = new SparkConf()
options.foreach { case (k, v) => sparkConf.set(k, v) }
if (!sparkConf.contains("spark.app.name")) {
sparkConf.setAppName(randomAppName)
}
val sc = SparkContext.getOrCreate(sparkConf)
// maybe this is an existing SparkContext, update its SparkConf which maybe used
// by SparkSession
options.foreach { case (k, v) => sc.conf.set(k, v) }
if (!sc.conf.contains("spark.app.name")) {
sc.conf.setAppName(randomAppName)
}
sc
}
session = new SparkSession(sparkContext)
options.foreach { case (k, v) => session.sessionState.conf.setConfString(k, v) }
defaultSession.set(session)
创建session过程中,如果没有设置一些配置,比如 executor_memory ,程序会设置默认值,比如
创建完session之后返回到主函数继续执行。
JavaRDD<String> lines = spark.read().textFile(args[0]).javaRDD();
读取文件内容并转化RDD,默认文件类型parquet
JavaRDD<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) {
return Arrays.asList(SPACE.split(s)).iterator();
}
});
读取每一行数据进行切分,返回一个新的RDD
JavaPairRDD<String, Integer> ones = words.mapToPair(
new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) {
return new Tuple2<>(s, 1);
}
});
创建一个PairRDD,为了之后的计算的单词出现的总次数
JavaPairRDD<String, Integer> counts = ones.reduceByKey(
new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) {
return i1 + i2;
}
});
对相同key的单词进行求和计算
List<Tuple2<String, Integer>> output = counts.collect();
for (Tuple2<?,?> tuple : output) {
System.out.println(tuple._1() + ": " + tuple._2());
}