Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行框架,Spark,拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是——Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。
三台Linux服务器,安装好JDK1.8、Hadoop2.6
下载安装包spark1.6.3
将安装包上传到第一台Linux服务器上
解压安装包到指定位置
tar -zxvf spark-1.6.3-bin-hadoop2.6.tgz -C /home/bigdata/installsoft/
将文件夹重命名为spark-1.6.3
进入spark安装目录下的conf目录
cd /home/bigdata/installsoft/spark-1.6.3/conf
将spark-env.sh.template重命名为spark-env.sh
mv spark-env.sh.template spark-env.sh
编辑spark-env.sh并添加配置
export JAVA_HOME=/home/bigdata/installsoft/jdk1.8.0_181/
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=cdhnocms01,cdhnocms02,cdhnocms03 -Dspark.deploy.zookeeper.dir=/spark"
export SPARK_MASTER_PORT=7077
export HADOOP_CONF_DIR=/home/bigdata/installsoft/hadoop-2.6.0-cdh5.13.2/etc/hadoop
将slaves.template重命名为slaves
mv slaves.template slaves
在slaves文件汇总添加worker节点所在的主机
cdhnocms01
cdhnocms02
cdhnocms03
在用户家目录下.bash_profile文件中添加
SPARK_HOME=/home/bigdata/installsoft/spark-1.6.3/
PATH= P A T H : PATH: PATH:SPARK_HOME/bin:$SPARK_HOME/sbin
保存退出后source .bash_profile
scp -r /home/bigdata/installsoft/spark-1.6.3/ cdhnocms02:/home/bigdata/installsoft/
scp -r /home/bigdata/installsoft/spark-1.6.3/ cdhnocms02:/home/bigdata/installsoft/
直接将环境变量文件发送到其他节点,或者在其他节点上一一配置环境变量
在cdhnocms01节点上执行/home/bigdata/installsoft/spark-1.6.3/sbin/start-all.sh
在cdhnocms02节点上执行/home/bigdata/installsoft/spark-1.6.3/sbin/start-master.sh
此时使用jps查看三台机器进程,如下表
cdhnocms01 | cdhnocms02 | cdhnocms03 |
---|---|---|
Master、Worker | Master、Worker | Worker |
注意:虽然配置了环境变量,但由于名称相同,如果直接在任意目录直接执行start-all.sh,启动的将会是hadoop的相关进程。解决办法:修改启动脚本的文件名。
运行官方自带的例子
spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://cdhnocms01:7077 \
--executor-memory 1G \
--total-executor-cores 2 \
/home/bigdata/installsoft/spark-1.6.3/lib/spark-examples-1.6.3-hadoop2.6.0.jar \
100
运行完成后可以在命令行中找到结果
在web监控页面:cdhnocms02:8080上可以查看任务状态
spark-shell是Spark自带的交互式Shell程序,方便用户进行交互式编程,用户可以在该命令行下用scala编写spark程序。
spark-shell \
--master spark://cdhnocms01:7077 \
--executor-memory 1G \
--total-executor-cores 2
参数说明
–master spark://cdhnocms02:7077 指定Master的地址
–executor-memory 1G 指定每个worker可用内存为1G
–total-executor-cores 2 指定整个集群使用的cup核数为2个
注意
如果启动spark shell时没有指定master地址,但是也可以正常启动spark shell和执行spark shell中的程序,其实是启动了spark的local模式,该模式仅在本机启动一个进程,没有与集群建立联系。
启动spark shell后,可以注意到在控制台有如下两条语句:
意思是Spark Shell中已经默认将SparkContext类初始化为对象sc,SQLContext类初始化为对象sqlContext。用户代码如果需要用到,则直接使用对应的对象名即可即可。
4.0.0
com
spark
1.0-SNAPSHOT
org.apache.spark
spark-core_2.11
1.6.3
src/main/day01
org.scala-tools
maven-scala-plugin
2.15.2
compile
testCompile
maven-compiler-plugin
3.6.0
1.8
org.apache.maven.plugins
maven-surefire-plugin
2.19
true
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SparkWordCount_scala {
def main(args: Array[String]): Unit = {
// 1.获取spark的conf
// 本地运行
val conf = new SparkConf().setAppName("spark_wordcount_scala").setMaster("local[2]")
// 提交到集群中运行
//val conf = new SparkConf().setAppName("spark_wordcount_scala").setMaster("spark://cdhnocms01:7077")
// 2.根据conf对象获取sparkContext(spark的上下文)
val sc = new SparkContext(conf)
// 读取hdfs中的数据
sc.hadoopConfiguration.addResource("core-site.xml")
sc.hadoopConfiguration.addResource("hdfs-site.xml")
//第二种读取hdfs的HA的文件数据
// sc.hadoopConfiguration.set("")
// 3.使用sc进行操作
// 读取数据源
val words:RDD[String] = sc.textFile(args(0))
val res:RDD[(String,Int)] = words.flatMap(_.split(" ")).map((_,1)).groupBy(_._1).mapValues(_.size)
// 打印
res.foreach(f=>println(f))
//4. 关闭sc
sc.stop()
}
}
可以直接在idea中配置好输入参数后运行,可以的到结果
使用Maven打包后,将jar包上传至Linux中,运行命令:
spark-submit \
--class SparkWordCount_scala \
--master spark://cdhnocms01:7077 \
/home/bigdata/userjars/spark-1.0-SNAPSHOT.jar \
hdfs://bigdata/userdata/wc.txt
代码的输出结果(在网页监控端口任务的stdout中查看):
RDD(Resilient Distributed Dataset),分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
- A list of partitions
- A function for computing each split
- A list of dependencies on other RDDs
- Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
- Optionally, a list of preferred locations to compute each split on (e.g. block locations for
an HDFS file)
属性详解:
由一个已经存在的Scala集合创建(Array、List、Seq等)
sc.parallelize(args(0),args(1))
第一个参数代表已存在的Scala集合,第二个参数代表分片个数,如果不指定则会采用默认值—分配的CPU core数量
由外部存储系统的数据集创建,包括本地的文件系统,还有所有Hadoop支持的数据集,比如HDFS、Cassandra、HBase等
sc.textFile(“hdfs://cdhnocms01:8020/userdata/wc.txt”)
RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。
Transformation | 含义 |
---|---|
map(func) | 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 |
filter(func) | 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T]=> Iterator[U] |
mapPartitionsWithIndex(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U] |
sample(withReplacement,fraction, seed) | 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) | 对源RDD和参数RDD求并集后返回一个新的RDD |
intersection(otherDataset) | 对源RDD和参数RDD求交集后返回一个新的RDD |
distinct([numTasks])) | 对源RDD进行去重后返回一个新的RDD |
groupByKey([numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD |
reduceByKey(func,[numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 |
aggregateByKey(zeroValue)(seqOp,combOp, [numTasks]) | |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending],[numTasks]) | 与sortByKey类似,但是可以指定根据什么排序 |
join(otherDataset,[numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset,[numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
cartesian(otherDataset) | 笛卡尔积 |
coalesce(numPartitions) | 重新分区 |
repartition(numPartitions) | 重新分区 |
repartitionAndSortWithinPartitions(partitioner) | 重新分区 |
Action | 含义 |
---|---|
reduce(func) | 通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的 |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 |
count() | 返回RDD的元素个数 |
first() | 返回RDD的第一个元素(类似于take(1)) |
take(n) | 返回一个由数据集的前n个元素组成的数组 |
takeSample(withReplacement,num, [seed]) | 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子 |
takeOrdered(n, [ordering]) | takeOrdered和top类似,只不过以和top相反的顺序返回元素 |
saveAsTextFile(path) | 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) | 将数据集中的元素以Hadoopsequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。 |
saveAsObjectFile(path) | |
countByKey() | 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。 |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新。 |
map/mapPartitions
val rdd1 = sc.parallelize(List(1,2,3,4,5,6), 2)
val rdd2 = rdd1.map(_ * 10)
val rdd2 = rdd1.mapPartitions(_.map(_ * 10))
rdd2.collect
Array[Int] = Array(10, 20, 30, 40, 50, 60)
Array[Int] = Array(10, 20, 30, 40, 50, 60)
map算子是将rdd中的每一个元素拿出来进行操作
mapPartitions算子是将一整个分片中的数据拿出来操作,所以需要继续对每一个分片中各个数据拿出来操作
rdd1.mapPartitions(_.toList.reverse.iterator).collect
此操作是将每一个分片中的数据翻转
mapWith
参数列表:(constructA: Int => A, preservesPartitioning: Boolean = false)(f: (T, A) => U)
其中preservesPartitioning指定是否需要使用父RDD的分片
rdd1.mapWith(i => i*10)((a, b) => b+2).collect
Array[Int] = Array(2, 2, 2, 12, 12, 12)
mapWith算子是将rdd的分片下标取出进行操作元组(a,b)中a指数据,b指该数据的下标
flatMapWith
参数列表:(constructA: Int => A, preservesPartitioning: Boolean = false)(f: (T, A) => Seq[U])
rdd1.flatMapWith(i => i, true)((x, y) => List((y, x))).collect
Array[(Int, Int)] = Array((0,1), (0,2), (0,3), (1,4), (1,5), (1,6))
flatMapWith算子类似于mapWith,但是每一个输入元素可以被映射为0或多个输出元素
mapPartitionsWithIndex
参数列表:(f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false)
val func = (index: Int, iter: Iterator[(Int)]) => {
iter.toList.map(x => "[partID:" + index + ", val: " + x + "]").iterator
}
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)
rdd1.mapPartitionsWithIndex(func).collect
Array[String] = Array([partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3], [partID:0, val: 4], [partID:1, val: 5], [partID:1, val: 6], [partID:1, val: 7], [partID:1, val: 8], [partID:1, val: 9])
aggregate
参数列表:(zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
zeroValue:初始值;seqOp:单个分区的合并操作;combOp:所有分区的汇总操作
def func1(index: Int, iter: Iterator[(Int)]) : Iterator[String] = {
iter.toList.map(x => "[partID:" + index + ", val: " + x + "]").iterator
}
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9), 2)
rdd1.mapPartitionsWithIndex(func1).collect
Array[String] = Array([partID:0, val: 1], [partID:0, val: 2], [partID:0, val: 3], [partID:0, val: 4], [partID:1, val: 5], [partID:1, val: 6], [partID:1, val: 7], [partID:1, val: 8], [partID:1, val: 9])
rdd1.aggregate(0)(math.max(_, _), _ + _)
rdd1.aggregate(5)(math.max(_, _), _ + _)
Int = 13 //首先在两个分区中各自获得最大值4、9,相加等于13
Int = 19 //首先在两个分区中各自获得最大值5、9,相加等于14,再加上初始值5等于19
val rdd2 = sc.parallelize(List("a","b","c","d","e","f"),2)
def func2(index: Int, iter: Iterator[(String)]) : Iterator[String] = {
iter.toList.map(x => "[partID:" + index + ", val: " + x + "]").iterator
}
rdd2.mapPartitionsWithIndex(func2).collect
Array[String] = Array([partID:0, val: a], [partID:0, val: b], [partID:0, val: c], [partID:1, val: d], [partID:1, val: e], [partID:1, val: f])
rdd2.aggregate("")(_ + _, _ + _)
rdd2.aggregate("=")(_ + _, _ + _)
String = abcdef 或 String = defabc //字符串拼接操作,在两个分区中先各自拼接,最终的拼接时的顺序是哪个分区先完成就哪个分区在前
String = ==def=abc 或 String = ==abc=def //同上,但是在拼接前先加上初始值"="
val rdd3 = sc.parallelize(List("12","23","345","4567"),2)
rdd3.aggregate("")((x,y) => math.max(x.length, y.length).toString, (x,y) => x + y)
String = 42 或 String = 24
前一个参数列表 (x,y):第一次时分别代表初始值和分区中的第一个值,以后是分别代表上一次结果的值和分区中新的值
max(0,2) = 2, max(2,2) = 2
max(0,3) = 3, max(3,4) = 4
后一个参数列表(x,y):第一次是代表初始值与第一个分区的结果拼接,以后代表上一次的结果和新的分区的结果拼接
同上,由于不同分区的完成时间不同,结果会出现两种情况
val rdd4 = sc.parallelize(List("12","23","345",""),2)
rdd4.aggregate("")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y)
String = 10 或 String = 01
关键在于"".length=0,“0”.length=1
val rdd5 = sc.parallelize(List("12","23","","345"),2)
rdd5.aggregate("")((x,y) => math.min(x.length, y.length).toString, (x,y) => x + y)
String = 11
aggregateByKey
相同的key进行操作
参数列表:(zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U, combOp: (U, U) => U): RDD[(K, U)
val pairRDD = sc.parallelize(List(("mouse", 2),("cat",2), ("cat", 5), ("mouse", 4),("cat", 12), ("dog", 12)), 2)
def func2(index: Int, iter: Iterator[(String, Int)]) : Iterator[String] = {
iter.toList.map(x => "[partID:" + index + ", val: " + x + "]").iterator
}
pairRDD.mapPartitionsWithIndex(func2).collect
Array[String] = Array([partID:0, val: (mouse,2)], [partID:0, val: (cat,2)], [partID:0, val: (cat,5)], [partID:1, val: (mouse,4)], [partID:1, val: (cat,12)], [partID:1, val: (dog,12)])
pairRDD.aggregateByKey(0)(math.max(_, _), _ + _).collect
pairRDD.aggregateByKey(100)(math.max(_, _), _ + _).collect
Array[(String, Int)] = Array((dog,12), (cat,17), (mouse,6)) // dog:12;cat:5+12;mouse:2+4
Array[(String, Int)] = Array((dog,100), (cat,200), (mouse,200)) // dog:100;cat:100+100;mouse:100+100
combineByKey
参数列表:(createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C)
val rdd1 = sc.textFile("hdfs://cdhnocms01:8020/userdata/wc.txt").flatMap(_.split(" ")).map((_, 1))
rdd1.combineByKey(x => x, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n).collect
Array[(String, Int)] = Array((word,2), (hello,2), (sql,1), (spark,3), (hadoop,2), (hi,1))
rdd1.combineByKey(x => x + 10, (a: Int, b: Int) => a + b, (m: Int, n: Int) => m + n).collect
Array[(String, Int)] = Array((word,12), (hello,12), (sql,11), (spark,13), (hadoop,12), (hi,11))
// 对每一个value加10
val rdd4 = sc.parallelize(List("dog","cat","gnu","salmon","rabbit","turkey","wolf","bear","bee"), 3)
val rdd5 = sc.parallelize(List(1,1,2,2,2,1,2,2,2), 3)
val rdd6 = rdd5.zip(rdd4)
rdd6.combineByKey(List(_), (x: List[String], y: String) => x :+ y, (m: List[String], n: List[String]) => m ++ n).collect
Array[(Int, List[String])] = Array((1,List(dog, cat, turkey)), (2,List(salmon, rabbit, wolf, bear, bee, gnu)))
countByKey / countByValue
val rdd1 = sc.parallelize(List(("a", 1), ("b", 2), ("b", 2), ("c", 2), ("c", 1)))
rdd1.countByKey
rdd1.countByValue
scala.collection.Map[String,Long] = Map(b -> 2, a -> 1, c -> 2) // 统计相同key出现的次数
scala.collection.Map[(String, Int),Long] = Map((b,2) -> 2, (c,2) -> 1, (a,1) -> 1, (c,1) -> 1) // 统计相同元素出现的次数
filterByRange
val rdd1 = sc.parallelize(List(("e", 5), ("c", 3), ("d", 4), ("c", 2), ("a", 1)))
val rdd2 = rdd1.filterByRange("c", "d")
rdd2.collect
Array[(String, Int)] = Array((c,3), (d,4), (c,2))
// 对给定的范围进行过滤
flatMapValues
val rdd3 = sc.parallelize(List(("a", "1 2"), ("b", "3 4")))
rdd3.flatMapValues(_.split(" ")).collect
Array[(String, String)] = Array((a,1), (a,2), (b,3), (b,4))
对value进行相应的操作后压频
foldByKey
val rdd1 = sc.parallelize(List("dog", "wolf", "cat", "bear"), 2)
val rdd2 = rdd1.map(x => (x.length, x))
val rdd3 = rdd2.foldByKey("")(_+_)
rdd3.collect
Array[(Int, String)] = Array((4,bearwolf), (3,dogcat))
val rdd = sc.textFile("hdfs://cdhnocms01:8020/userdata/wc.txt").flatMap(_.split(" ")).map((_, 1))
rdd.foldByKey(0)(_+_).collect
Array[(String, Int)] = Array((word,2), (hello,2), (sql,1), (spark,3), (hadoop,2), (hi,1))
foreachPartition
val rdd1 = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9), 3)
rdd1.foreachPartition(x => println(x.reduce(_ + _)))
keyBy
val rdd1 = sc.parallelize(List("dog", "salmon", "salmon", "rat", "elephant"), 3)
val rdd2 = rdd1.keyBy(_.length)
rdd2.collect
Array[(Int, String)] = Array((3,dog), (6,salmon), (6,salmon), (3,rat), (8,elephant))
// 将结果作为key-value的key
keys / values
val rdd1 = sc.parallelize(List("dog", "tiger", "lion", "cat", "panther", "eagle"), 2)
val rdd2 = rdd1.map(x => (x.length, x))
rdd2.keys.collect
rdd2.values.collect
Array[Int] = Array(3, 5, 4, 3, 7, 5) // 获得key
Array[String] = Array(dog, tiger, lion, cat, panther, eagle) // 获得value
collectAsMap
val rdd = sc.parallelize(List(("a", 1), ("b", 2)))
rdd.collectAsMap
scala.collection.Map[String,Int] = Map(b -> 2, a -> 1)
repartition, coalesce, partitionBy
重新分区
val rdd1 = sc.parallelize(1 to 10, 3)
val rdd2 = rdd1.coalesce(2, false)
rdd2.partitions.length
checkpoint
sc.setCheckpointDir("hdfs://cdhnocms01:8020/userdata/cp")
val rdd = sc.textFile("hdfs://cdhnocms01:8020/userdata/wc.txt").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_)
rdd.checkpoint
rdd.isCheckpointed
rdd.count
rdd.isCheckpointed
rdd.getCheckpointFile