小白学习Spark02-SparkCode(下)

4. 1 共享变量
  • 共享变量:是一种可以在Spark任务中使用的特殊类型的变量
  • 两种类型的共享变量
    • 累加器(accumulator):累加器用来对信息进行聚合
    • 广播变量(broadcast variable):广播变量用来高效分发较大的对象
  • 通常在向Spark传递函数时,比如使用map()函数或者filter()传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中对应的变量。
  • 累加器和广播变量 分别为 结果聚合与广播这两种常见的通信模式突破了这一限制
4.1.1 累加器
  • 累加器:提供了对工作节点中的值聚合到驱动器程序中的简单语法
  • 常见用途:在调试中对作业执行过程中的事件进行计数
    	//在Scala中累加空行
    	val sc = new SparkContext(...)
    	val file = sc.textFile("file.txt")
    	val blankLines=sc.accumulator(0)   //创建Accumulator[Int]并初始化为0
    	val callSings = file.flatMap(line => {
    		if(line == ""){
    			blankLines +=1 //累加器加1
    		}
    		line.split(" ")
    	})
    	callSigns.saveAsTextFile("output.txt")
    	println("Blank lines: "+ blankLines.value)
    
    • 累加器 在行动操作强制触发时才会开始求值
  • 累加器的用法
    • 通过在驱动器中调用SparkContext.accumulator(initialValue)方法,创建出存有初始值的累加器。返回值为org.apache.spark.Accumulator[T]对象,其中T是初始值initialValue的类型。
    • Spark闭包里的执行器代码可以使用累加器的+=方法来增加累加器的值
    • 驱动器程序可以调用累加器的value属性来访问累加器的值
  • 累加器的值只能在驱动器程序上可以访问,工作节点上的任务不能访问累加器的值,从这些角度看,累加器是一个 只写 变量,因此检查也应当在驱动器程序中完成。
  • 累加器与容错性
    • Spark会自动重新执行失败的或者比较慢的任务来应对有错误或者比较慢的机器,这样的结果就是同一个函数可能对同一个数据运行了多次,这样可能会导致累加器的值被疯狂的叠加,与实际情况不符合。
    • 若想要一个绝对可靠的累加器,则应该把累加器放在foreach()这样的行动操作中,因为转发操作中的累加器可能会发生不止一次的更新。
  • 自定义累加器
    • 累加器可以被自定义。
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>


  • 关注「一个热爱学习的计算机小白」公众号 ,对作者的小小鼓励,后续更多资源敬请关注。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值