Spark2.1

spark-submit的使用
  • 执行SparkPi
  • 读取和写入文件:
# 1. 读取和写入本地文件系统
bin/spark-submit \
--class com.atguigu.spark.WordCount \
--master spark://master:7077 \
--executor-memory 1G \
./wordcount.jar \
file:///home/xxx/pycharm_proj/software/spark/RELEASE \
file:///home/xxx/pycharm_proj/software/spark/out
# 2. 读取和写入hdfs文件系统
bin/spark-submit \
--class com.atguigu.spark.WordCount \
--master spark://master:7077 \
--executor-memory 1G \
./wordcount.jar \
hdfs://master:9000/RELEASE \
hdfs://master:9000/out
  • 命令行解析
  • 注意几种模式:local是本地;spark(standalone)是spark自身集群;yarn-client/yarn-cluster是yarn集群,区别是是否自动分配driver进程的位置;
  • SparkPi实现
// scalastyle:off println
package org.apache.spark.examples

import scala.math.random

import org.apache.spark.sql.SparkSession

/** Computes an approximation to pi */
object SparkPi {
  def main(args: Array[String]) {
    val spark = SparkSession
      .builder
      .appName("Spark Pi")
      .getOrCreate()
    val slices = if (args.length > 0) args(0).toInt else 2
    val n = math.min(100000L * slices, Int.MaxValue).toInt // avoid overflow
    val count = spark.sparkContext.parallelize(1 until n, slices).map { i =>
      val x = random * 2 - 1  /*[-1,1]即边长为2的整个正方形采样*/
      val y = random * 2 - 1
      if (x*x + y*y <= 1) 1 else 0
    }.reduce(_ + _)
    println(s"Pi is roughly ${4.0 * count / (n - 1)}")
    spark.stop()
  }
}
// scalastyle:on println
spark-standalone运行机制
  • client创建driver进程和sparkcontext,并向master注册;master调度Worker创建Executor(一个worker可以多个Executor),Executor向client注册(注意Executor只向master报告状态,而直接跟client注册)
  • client接收Executor的注册后,直接给Executor分配Task(绕过master)
  • 不同工作模式(local、standalone、yarn、mesos),master根据设置不同对应不同的manager(driver、Executor保持不变)
spark history server配置
  • spark-defaults.conf设置:设置以下两个选项,注意第二个目录要存在
  • spark-env.sh设置:设置hs的访问端口,注意最后一个目录要和上面一致(下面要修改)
  • 打开spark的历史服务器:sbin/start-history-server.sh,访问4000端口进行查看(注意上面配置后已经可以写,这里只是查看使用);
spark-HA高可用配置
  • 利用zookeeper进行管理
  • 配置
  • 启动
  • 在另一个节点启动新的master服务(start-master.sh),该master会被zookeeper设置为standby(原master为alive);当发生alive-master意外停止时,会将standby-master设置为alive,由此保证HA
Yarn模式安装
  • 配置:注意第一个配置为关闭内存使用检查,因为yarn检查不太准确,容易报错;当然如果资源多,可以不用关闭;
  • 运行
WordCount过程解析

spark学习之路 spark在线教程_ide

spark学习之路 spark在线教程_ide_02

RDD介绍

spark学习之路 spark在线教程_ide_03


spark学习之路 spark在线教程_spark_04


spark学习之路 spark在线教程_学习_05


note:RDD的缓存,如果有血统关系如下:A->B->C 和 A->B->D,不缓存B的话计算C/D都要从A开始计算一遍,导致A->B重复计算多遍;

RDD的转换操作学习(容易出错部分)
1. mapPartitions & mapPartitionsWithIndex
  • 例子:rdd = sc.makeRDD(1 to 10, 4),请输出各partition的"|"拼接结果;
// 正确写法,要注意Iterator的使用是为了满足partition的要求;
=> rdd.mapPartitions(x => Iterator(x.mkString("|"))).collect()
=>
// 错误写法
=> rdd.mapPartitions(Iterator(_.mkString("|"))) 
=> error: **missing parameter type** for expanded function ((x$1) => x$1.mkString("|"))
=>
// 例子:要求对每个partition输出 partition序号 + ":" + 各元素用"|"拼接 的结果,如 0:1|2 1:3|4|5
=> rdd.mapPartitionsWithIndex((x,y) => Iterator(x+":"+y.mkString("|"))).collect
  • 分析:
  • 上述错误写法涉及scala就近类型转换问题(又叫eta-extention)
  • 对于Iterator(_.mkString(“|”)),scala将其转换为 Iterator(x => x.mkString(“|”)),并最终转换为 y => Iterator( x => x.mkString(“|”)),此时x不知道从哪里来,因此报错;
2. flatMap
  • 例子:rdd = sc.makeRDD(1 to 10, 4),使用flatMap进行各元素加1操作;
=> rdd.flatMap(x => Array(x+1))  
=> // 注意简写会报错;此外注意Array的使用(不一定是Array,但是要Iterable,参考源代码)
3.sample
  • 例子:rdd = sc.parallelize(1 to 20),从中随机采样30%的数据,可重复
=> rdd.sample(true, 0.3, seed=2)
4. sortBy
  • 例子:rdd = sc.parallelize(1 to 20),对5取余后升序
// 注意要传入一个函数,用于生成排序的key
=> rdd.sortBy(_%5)  
=> res17: Array[Int] = Array(5, 10, 15, 20, 1, 6, 11, 16, 2, 7, 12, 17, 3, 8, 13, 18, 4, 9, 14, 19)
5. union/subtract/intersection/cartesian
  • 例子
=> val rdd = sc.parallelize(Array(1,1,2))
=> rdd.union(sc.parallelize(2 to 5)).collect  // 以下省略collect动作
=> res19: Array[Int] = Array(1, 1, 2, 2, 3, 4, 5)  // 注意没有去重
=> 
=> rdd.subtract(sc.parallelize(2 to 5))
=> res21: Array[Int] = Array(1, 1)  // 注意从前者去除后者出现的元素
=>
=> rdd.intersection(sc.parallelize(2 to 5))  // 取交集
=>
=> rdd.cartesian(sc.parallelize(2 to 5))  // 笛卡尔积
=> res23: Array[(Int, Int)] = Array((1,2), (1,3), (1,4), (1,5), (1,2), (1,3), (1,4), (1,5), (2,2), (2,3), (2,4), (2,5))
6.pipe
  • 可用于执行外部脚本;例子
# p.sh
#!/bin/sh
echo "AA"
while read LINE; do
	echo ">>>"${LINE}
done
// 调用上述脚本
=> sc.parallelize(1 to 3, 2).pipe("p.sh").collect
=> Array[String] = Array(AA, >>>1, AA, >>>2, >>>3)  //注意到脚本是对分区执行的,所以才只打印2次AA(2、3在同一个分区)
7. join、cogroup
  • rdd间的结合;例子
=> val rdd1 = sc.parallelize(1 to 10).map((_, 1))
=> val rdd2 = sc.parallelize(6 to 15).map((_, 1))		
// 共有部分进行拼接
=> rdd1.join(rdd2).collect
=> res25: Array[(Int, (Int, Int))] = Array((6,(1,1)), (7,(1,1)), (8,(1,1)), (9,(1,1)), (10,(1,1)))
// 左外连接:以左边rdd为基准,进行拼接(注意key是左rdd所有key)
=> rdd1.leftOuterJoin(rdd2).collect
=> res26: Array[(Int, (Int, Option[Int]))] = Array((1,(1,None)), (2,(1,None)), (3,(1,None)), (4,(1,None)), (5,(1,None)), (6,(1,Some(1))), (7,(1,Some(1))), (8,(1,Some(1))), (9,(1,Some(1))), (10,(1,Some(1))))
// 右外连接:以右边rdd为基准,进行拼接(注意key是右rdd所有key)
=> res27: Array[(Int, (Option[Int], Int))] = Array((6,(Some(1),1)), (7,(Some(1),1)), (8,(Some(1),1)), (9,(Some(1),1)), (10,(Some(1),1)), (11,(None,1)), (12,(None,1)), (13,(None,1)), (14,(None,1)), (15,(None,1)))
// cogroup:去重合并所有key,并把value转化为Iterable
=> val rdd3 = sc.parallelize(Array(0,2,4,3,3).map((_,1)))
=> val rdd4 = sc.parallelize(Array(0,1,5,3).map((_,1)))
=> rdd3.cogroup(rdd4).collect
=> res30: Array[(Int, (Iterable[Int], Iterable[Int]))] = Array((0,(CompactBuffer(1),CompactBuffer(1))), (1,(CompactBuffer(),CompactBuffer(1))), (2,(CompactBuffer(1),CompactBuffer())), (3,(CompactBuffer(1, 1),CompactBuffer(1))), (4,(CompactBuffer(1),CompactBuffer())), (5,(CompactBuffer(),CompactBuffer(1))))
8. combineByKey
  • 函数及其说明如下
  • 例子:某PairRDD存储了一系列(key,value),key可能重复,要求计算每个唯一key的平均值;
=> val rdd1 = sc.parallelize(List(("c", 1),("c", 2),("c", 3),("c", 4),("a", 10),("a", 20),("a", 30),("a", 40),("b", 50),("b", 60),("b", 70),("b", 80)))
=> val r2 = r1.combineByKey(x=>(x, 1), (x:(Int,Int),y)=>(x._1+y, x._2+1), (x:(Int,Int),y:(Int,Int))=>(x._1+y._1, x._2+y._2))
=> // 注意到上面的类型声明,不然会报错丢失类型
=> r2.collect
=> res1: Array[(String, (Int, Int))] = Array((a,(100,4)), (b,(260,4)), (c,(10,4)))
=> // 取平均
=> r2.map(x=>(x._1, x._2._1/x._2._2)).collect
=> res4: Array[(String, Int)] = Array((a,25), (b,65), (c,2))
=> // 另一种写法
=> r2.map{case (x,y)=>(x, y._1/y._2)}.collect
9. aggregateByKey
  • 函数及其说明如下:与上一个函数不同的是,该函数可以指定默认值,此后遇到任何key,都在默认值上累积,不用像上一个函数一样先初始化新key的对象;
  • 例子:同上
=> val r1 = sc.parallelize(List(("c", 1),("c", 2),("c", 3),("c", 4),("a", 10),("a", 20),("a", 30),("a", 40),("b", 50),("b", 60),("b", 70),("b", 80)))
=> val r3 = r1.aggregateByKey((0,0))((x,y)=>(x._1+y, x._2+1), (x,y)=>(x._1+y._1, x._2+y._2))
=> r3.collect
=> res8: Array[(String, (Int, Int))] = Array((a,(100,4)), (b,(260,4)), (c,(10,4)))
=> // 以下求平均值同上一个例子
RDD的运行
1. RDD的宽窄依赖
  • 针对父RDD->子RDD:窄依赖就是一对一,或者多对一;宽依赖就是一对多;宽依赖发生一个父RDD的partition被shuffle到子RDD的多个partition,故称为宽依赖(可理解为很宽松地被依赖)
  • 宽窄依赖的划分用于对确定RDD的运行阶段,见下面说明
2. RDD任务切分
  • 说明:
  • Application:jar包
  • Job:每个行动算子算是一个Job
  • Stage:发生宽依赖的地方划分stage
  • Task:并行度,即partition个数
3. RDD的运行规划图
  • 这里主要说明stage的划分:如下图,spark是倒着进行stage的划分的,遇到宽依赖(这里是reduceByKey),则把宽依赖及其前面操作整个视为一个stage(这里是stage1),并把宽依赖前面的所有操作又视为一个stage(这里是stage2);注意划分不含输入输出(这里是HDFS的读写操作)
4. RDD的检查点机制

spark学习之路 spark在线教程_学习_06

5. 键值对RDD数据分区

spark学习之路 spark在线教程_ide_07

6. 数据读取与保存
  • TextFile, SequenceFile(调用HadoopFile), ObjectFile(本质上也是调用SequenceFile)
  • ObjectFile例子
  • JDBC读取sql数据
7. 累加器
  • 例子
=> val sc = new SparkContext(conf)
=> var sum = 0
=> val arr = Array(1,2,3,4,5)
=> val rdd = sc.makeRDD(arr)
=> rdd.map{
 		sum += x
 		x
 		}.collect()
=> println(sum)  // 这里打印结果是0
// 问题出在 driver、executor身上,各个数据分区(如3个分区数据为[1,2],[3,4],[5])的executor都有一个sum(分别为3、7、5),driver也有一个sum,没有拉取各executor的sum求和,所以始终为0;
// 为了解决上述问题,就需要使用累加器,代码如下
=> var sum = sc.accumulator(0)  // 改写废弃
=> // 中间部分不变
=> println(sum.value)
  • 累加器要放在行动算子里,放在转换算子可能因为多次使用该转换算子,造成重复累加;
  • 自定义行动算子
  • 以下举例 添加string的自定义累加器
class CustomerAccu extends AccumulatorV2[String, java.util.Set[String]]{
	var logStr = new util.HashSet[String]()
	// 判断当前对象是否为空
	override def isZero: Boolean = logStr.isEmpty
	// 复制
	override def copy(): AccumulatorV2[String, util.Set[String]] = {
		val accu = new CustomerAccu
		accu.logStr.addAll(logStr)
		accu
	}
	// 重置对象
	override def reset(): Unit = logStr.clear()
	// 分区内添加数据
	override def add(v: String): Unit = logStr.add(v)
	// 合并
	override def merge(other: AccumulatorV2[String, util.Set[String]]): Unit = logStr.addAll(other.value)
	// 返回当前值
	override def value: util.Set[String] = logStr
}
// 使用该累加器
val rdd = sc.makeRDD(Array("abc", "def", "ghi", "jkl"))
val accum = new CustomerAccu
sc.register(accum, "CustomerAccu")  // 需要先注册
rdd.map{
	x =>
	accu.add(x)
	x
}.collect()
accu.value  // 内部调用copy、merge,输出上面完整Array