Spark编程进阶

1 简介

介绍两种类型的共享变量: 累加器(accumulator)与广播变量(broadcast variable)累加器用来对信息进行聚合,而广播变量用来高效分发较大的对象

2 累加器

共享变量,即累加器。提供了将工作节点中的值聚合到驱动器程序中的简单语法。累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。如下是基于python的对文件中空行的统计:

data = sc.textFile(inputFile)
# 创建accumulator,并初始化为0
blankLines = sc.accumulator(0)
def extractCallSigns(line):
	global blankLines # 访问全局变量
	if (line == ""):
		blankLines +=1
	return line.split(" ")
callSigns = data.flatMap(extractCallSigns)
callSigns.saveAsTextFile(outputDir + "/callsigns")
print ("Blank lines: %d") % blankLines.value

我们创建了一个叫作 blankLines 的 Accumulator[Int] 对象,然后在输入中看到一个空行时就对其加 1。执行完转化操作之后,就打印出累加器中的值。注意,只有在运行 saveAsTextFile() 行动操作后才能看到正确的计数,因为行动操作前的转化操 flatMap() 是惰性的,所以作为计算副产品的累加器只有在惰性的转化操作 flatMap() 被 saveAsTextFile() 行动操作强制触发时才会开始求值。累加器的用法总结如下:

  • 通 过 在 驱 动 器 中 调 用 SparkContext.accumulator(initialValue) 方 法, 创 建 出 存 有 初 始值的累加器。返回值为 org.apache.spark.Accumulator[T] 对象,其中 T 是初始值 initialValue 的类型。
  • Spark 闭包里的执行器代码可以使用累加器的 += 方法(在 Java 中是 add)增加累加器的值。
  • 驱动器程序可以调用累加器的value 属性(在 Java 中使用 value() 或 setValue())来访
    问累加器的值。

2.1 容错性

Spark 会自动重新执行失败的或较慢的任务来应对有错误的或者比较慢的机器。例如,如果对某分区执行 map() 操作的节点失败了,Spark 会在另一个节点上重新运行该任务。即使该节点没有崩溃,而只是处理速度比别的节点慢很多,Spark 也可以抢占式地在另一个节点上启动一个“投机”(speculative)型的任务副本,如果该任务更早结束就可以直接获取结果。即使没有节点失败,Spark有时也需要重新运行任务来获取缓存中被移除出内存 的数据。因此最终结果就是同一个函数可能对同一个数据运行了多次,这取决于集群发生了什么。

3 广播变量

Spark 的第二种共享变量类型是广播变量,它可以让程序高效地向所有工作节点发送一个较大的只读值,以供一个或多个 Spark 操作使用。前面提过,Spark 会自动把闭包中所有引用到的变量发送到工作节点上。虽然这很方便, 但也很低效。原因有二: 首先,默认的任务发射机制是专门为小任务进行优化的; 其次, 事实上你可能会在多个并行操作中使用同一个变量,但是 Spark 会为每个操作分别发送。若发送的数据很大,从主节点为每个任务发送一个这样的数组就会代价巨大。广播变量其实就是类型为 spark. broadcast.Broadcast[T] 的一个对象,其中存放着类型为 T 的值。可以在任务中通过对 Broadcast 对象调用 value 来获取该对象的值。这个值只会被发送到各节点一次,使用的是一种高效的类似BitTorrent 的通信机制。具体操作形式如下:

broadcast_variable = sc.broadcast(data)
# call it
def call():
	data = broadcast_valiable.value

使用广播变量过程很简单,过程如下:

  • 通过对一个类型 T 的对象调用SparkContext.broadcast 创建出一个 Broadcast[T] 对象。 任何可序列化的类型都可以这么实现。
  • 通过 value 属性访问该对象的值
  • 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。

3.1 广播的优化

当广播一个比较大的值时,选择既快又好的序列化格式是很重要的,因为如果序列化对象的时间很长或者传送花费的时间太久,这段时间很容易就成为性能瓶颈。可以使用 spark.serializer属性选择另一个序列化库来优化序列化过程。也可以为你的数据类型实现自己的序列化方式。

4 基于分区进行操作

基于分区对数据进行操作可以让我们避免为每个数据元素进行重复的配置工作。Spark 提供基于分区的 map 和 foreach,让你的部分代码只对 RDD 的每个分区运行 一次,这样可以帮助降低这些操作的代价。如下是使用 mapPartitions 函数获得输入 RDD 的每个分区中的元素迭代器,而需要返 回的是执行结果的序列的迭代器。

rdd.mapPartitions(lambda rows: process(rows))
	

当基于分区操作 RDD 时,Spark 会为函数提供该分区中的元素的迭代器。返回值方面,也返回一个迭代器。除 mapPartitions() 外,Spark 还有一些别的基于分区的操作符。除了避免重复的配置工作,也可以使用 mapPartitions() 避免创建对象的开销。有时需要创 建一个对象来将不同类型的数据聚合起来。回忆一下第3章中,当计算平均值时,一种方法是将数值 RDD 转为二元组 RDD,以在归约过程中追踪所处理的元素个数。现在,可以为每个分区只创建一次二元组,而不用为每个元素都执行这个操作。

5 数值RDD的操作

Spark 的数值操作是通过流式算法实现的,允许以每次一个元素的方式构建出模型。这些统计数据都会在==调用 stats() 时通过一次遍历数据计算,并以 StatsCounter 对象返 回,如下是StatsCounter 上的可用方法:
在这里插入图片描述
如果你只想计算这些统计数据中的一个,也可以直接对 RDD 调用对应的方法,比如 rdd. mean() 或者 rdd.sum()。当多次使用同一个RDD的时候,应该把 这个 RDD 缓存下来,免得重复计算,降低效率。如下是移除异常值:

distanceNumerics = distances.map(lambda string: float(string))
stats = distanceNumerics.stats()
stdev = stats.stdev()
mean = stats.mean()
reasonbleDistances = distanceNumerics.filter(
	lambda x: math.fabs(x - mean) < 3 * stddev)
print (reasonableDistances.collect())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值