4. 1 共享变量
共享变量:是一种可以在Spark任务中使用的特殊类型的变量 两种类型的共享变量
累加器(accumulator):累加器用来对信息进行聚合 广播变量(broadcast variable):广播变量用来高效分发较大的对象 通常在向Spark传递函数时,比如使用map()函数或者filter()传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中对应的变量。 累加器和广播变量 分别为 结果聚合与广播这两种常见的通信模式突破了这一限制
4.1.1 累加器
4.1.2 广播变量
广播变量 可以让程序高效地向所有工作节点发送一个较大的只读值,以供一个或者多个Spark操作使用。 Spark虽然会自动将闭包中所有引用到的变量发送到工作节点上,但这是相当低效的,原因:
默认的任务发射机制是专门为小任务进行优化的 你可能会在多个并行操作中使用同一个变量,但是Spark会为每个操作分别发送。
//在Scala中使用广播变量查询国家
//查询RDD contactCounts中的呼号的对应位置,将呼号前缀读取为国家代码来进行查询
val signPrefixes = sc.broadcast(loadCallSignTable())
val countryContactCounts = contactCounts.map{case (sign, count) =>
val country = lookupInArray(sign, signPrefixes.value)
(country, count)
}.reduceByKey((x ,y) => x+y)
countryContactCounts.saveAsTextFile(outputDir + "/countries.txt”)
如何使用广播变量
通过对一个类型T的对象调用SparkContext.broadcast 创建出一个Broadcast[T]对象。任何可序列化的类型都可以这样实现。 通过value属性访问该对象的值 变量只会被发到各个节点一次,应作为只读值处理(即修改这个值不会影响到其他的节点)。 (满足 只读 要求最容易的使用方式: 广播基本类型的值或者引用不可变对象) 广播的优化
当广播一个比较大的值是,选择快又好的序列化格式是非常重要的,Spark的Scala和Java API默认使用的序列化库是Java序列化,因此它对于除基本类型的数组以外的任何对象都比较低效。 可以使用spark.serializer属性选择另一个序列化库来优化序列化过程,也可以为你的数据类型实现自己的序列化方式。
4. 2 基于分区进行操作
基于分区对数据进行操作 可以避免 为每个数据元素进行重复的配置工作,比如打开数据库连接或者创建随机数生成器等操作。 Spark提供 基于分区的map和foreach,可以让你对RDD的每个分区运行一次,帮助降低操作的代价。 通过使用基于分区的操作,可以在每个分区内共享一个数据库连接池,来避免建立太多连接,同时还可以重用Json解析器。
//在Scala中使用共享连接池与JSON解析器
//在一个在线的业余电台呼号数据库中,可以使用这个数据库查询日志记录过的联系人呼号列表
// 使用mapPartitions函数获得输入RDD的每个分区中的元素迭代器,而需要返回的是执行结果的序列的迭代器。
val contactsContactLists = validSigns.distinct().mapPartitions{
signs =>
val mapper = createMapper()
val client = new HttpClient()
client.start()
//创建http请求
signs.map{sign =>
createExchangeForSign(sign)
//获取响应
}.map{ case (sign, exchange) =>
(sign,readExchangeCallLog(mapper, exchange))
}.filter(x => x._2 != null) //删除空的呼叫日志
}
当基于分区操作RDD时,Spark会为函数提供该分区中的元素的迭代器。返回值方面,也会返回一个迭代器,除mapPartitions()外,Spark还有一些其他基于分区的操作符 除了避免重复的配置工作,也可以适用mapPartitions()避免创建对象的开销。
4. 3 与外部程序间的管道
pipe():Spark提供该方法可以让我们使用任意一种语言实现Spark作业中的部分逻辑,只要它能读写Unix标准流。 通过pipe(),我们可以
将RDD中的各元素从标准输入流中以字符串形式读出,并对这些元素执行任何你需要的操作,接着就把结果以字符串的形式写入标准输出。 通过pipe()与R程序进行交互
//在Scala中使用pipe()调用finddistance.R的驱动器程序
//1.程序会把RDD的每个元素都以换行符作为分隔符写出去
// 2.而那个R程序输出的每一行都是字符串,用来构成结果RDD中的元素
//使用-个R语言外部程序计算每次呼叫的距离,将脚本添加到每个节点需要在本次作业中下载的文件的列表中
val distScript = "./src/R/finddistance.R"
val distScriptName="finddistance.R"
sc.addFile(distScript)
val distances = contactsContactLists.values.flatMap(x => x.map(y =>
s"$y.contactlay,$y.contactlong,$y.mylat,$y.mylong")).pipe(Seq(
SprakFiles.get(distScriptName)))
println(distances.collect().toList)
对pipe()的介绍就到这里,详细了解请Google/Baidu。
4. 4 数值RDD的操作
Spark对包含数值数据的RDD提供了一些描述性的统计操作 Spark的数值操作时通过流式算法实现的,允许以每次一个元素的方式构建出模型,这些统计数据都会调用stats()时通过一次遍历数据计算出来,并以StatsCounter对象返回。 使用汇总统计来从数据中移除一些异常值。
//通过Scala移除异常值
//现在要移除一些异常值,因为有些地点可能是误报的
//首先要获取字符串RDD并将它转换为双精度浮点型
val distanceDouble = distance.map(string => string.toDouble)
val stats = distanceDoubles.stats()
val stddev = stats.stdev
val mean = stddev.mean()
val reasonableDistances = distanceDoubles.filter(x => math.abs(x-mean) < 3*stddev)
println(reasonableDistance.collect().toList)
<br>
关注「一个热爱学习的计算机小白」公众号 ,对作者的小小鼓励,后续更多资源敬请关注。