http://spark.apache.org/docs/latest/sql-data-sources-json.html
http://jsonlines.org/examples/ 官网的准备的数据集合
启动Spark集群
bin/spark-shell \
--master spark:centoshadoop1:7077
官方文档:
Spark RDD
Spark提供了一种对数据的核心抽象,称为弹性分布式数据集(Resilient Distributed Dataset,RDD)。每个RDD被分为多个分区,这些分区运行在集群中的不同节点上。也就是说,RDD是跨集群节点分区的元素集合,并且这些元素可以并行操作。
在编程时,可以把RDD看作是一个数据操作的基本单位。Spark中对数据的操作主要是对RDD的操作。
1:创建RDD
RDD中的数据来源可以是程序中的对象集合,也可以来源于外部存储系统中的数据集,例如共享文件系统,HDFS,HBase或任何提供Hadoop InputFormat的数据源。
scala> val rdd = sc.parallelize(List(1,2,3,4,5,6))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[7] at parallelize at
scala> val rdd = sc.makeRDD(List(1,2,3,4,5,6,7,8))
rdd: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[8] at makeRDD at
从返回的信息可以看出,上述创建的RDD中存储的是Int类型数据集合。实际上,RDD也是一个集合,与常用的List集合不同的是,RDD集合的数据分布于多台计算机上。
2:从外部存储系统创建RDD
Spark的textFile()方法可以读取本地文件系统或外部其他系统中的数据,并创建RDD。所不同的是,数据的来源路径不同.
(1)创建hadoop的工作目录
hdfs dfs -mkdir -p /library/SparkSQL/Data
上传文件到该工作目录
hdfs dfs -put ./tools/jsonfile/* /library/SparkSQL/Data/
查询上传的文件
hdfs dfs -ls -R /library/SparkSQL/Data
-rw-r--r-- 3 hadoop supergroup 115 2020-03-26 12:00 /library/SparkSQL/Data/department.json
-rw-r--r-- 3 hadoop supergroup 255 2020-03-26 12:00 /library/SparkSQL/Data/newPeople.json
-rw-r--r-- 3 hadoop supergroup 516 2020-03-26 12:00 /library/SparkSQL/Data/people.json
bin/spark-shell \
--master spark://centoshadoop1:7077
val sqlContext=new org.apache.spark.sql.SQLContext(sc)
val people=
sqlContext.jsonFile("hdfs://centoshadoop1:8020/library/SparkSQL/Data/people.json")
scala> people.collect
res0: Array[org.apache.spark.sql.Row] =
Array([33,1,male,001,Mechael,3000], [30,2,female,002,Andy,4000], [19,3,male,003,Justin,5000], [32,1,male,004,John,6000], [20,2,female,005,Herry,7000], [26,3,male,006,Jack,3000])
注意下面的/library/SparkSQL/Data/目录必须是通过hdfs或者hadoop命令创建的逻辑目录,否则报错该目录找不到
(2)读取本地系统文件,在系统目录/library/SparkSQL/Data/下有一个Sparksc.txt文件.
Sparksc.txt 文档内容为
hello hadoop
hello java
scala
上传文档到逻辑目录:
hdfs dfs -put /home/hadoop/tools/jsonfile/Sparksc.txt /library/SparkSQL/Data/
使用textFile()方法将上述文件内容转化为一个RDD,并使用collect()方法(该方法是RDD的一个行动算子)
scala> val rdd = sc.textFile("/library/SparkSQL/Data/Sparksc.txt")
rdd: org.apache.spark.rdd.RDD[String] =
/library/SparkSQL/Data/Sparksc.txt MapPartitionsRDD[14] at textFile at
读取该rdd中的多个元素
scala> rdd.collect
res4: Array[String] = Array(hello hadoop, hello java, scala)
scala> var data = rdd.collect
data: Array[String] = Array(hello hadoop, hello java, scala)
scala> for(element<-data)println(element)
hello hadoop
hello java
Scala
(3)读取HDFS系统文件
scala> val rdd = sc.textFile("hdfs://mycluster:8082/input/README.txt")
rdd: org.apache.spark.rdd.RDD[String] =
hdfs://mycluster:8082/input/README.txt MapPartitionsRDD[16] at textFile at
执行下面的命令时报错,该信息可知搭建的数hadoop是高可用集群的,不支持端口
scala> rdd.collect
java.io.IOException: Port 8082 specified in URI hdfs://mycluster:8082/input/README.txt but host 'mycluster' is a logical (HA) namenode and does not use port information.
解决方案:val rdd = sc.textFile("hdfs://mycluster/input/README.txt")
scala> rdd.collect
再次运行上面的命令,无错误信息。
scala> val dl=spark.read.textFile("hdfs://mycluster/input/README.txt")
dl: org.apache.spark.sql.Dataset[String] = [value: string]
scala> dl.show()
+--------------------+
| value|
+--------------------+
|For the latest in...|
| |
| http://hadoop....|
| |
| and our wiki, at:|
| |
| http://wiki.ap...|
| |
|This distribution...|
|which you current...|
|possession, use, ...|
|encryption softwa...|
|check your countr...|
|import, possessio...|
|see if this is pe...|
| information.|
| |
|The U.S. Governme...|
|Security (BIS), h...|
|Control Number (E...|
+--------------------+
only showing top 20 rows
3任务RDD算子
RDD被创建后是只读的,不允许修改,Spark提供了丰富的用于操作RDD的方法,这些方法被称为算子。一个创建完的RDD只支持两种算子:转化(Transformation)算子和行动(Action)算子.
(1)转化算子
转化算子负责对RDD中的数据进行计算并转化为新的RDD。Spark中的所有转化算子都是惰性的,因为它们不会立即计算结果,而只是记住对某个RDD的具体操作过程,直到遇到行动算子才会与行动算子一起执行。
例如,map()是一种转化算子,它接收一个函数作为参数,并把这个函数应用于RDD的每个元素,最后将函数的返回值作为结果RDD中的对应元素的值。
如下代码所示,对rdd1应用map()算子,将rdd1中的每个元素加1并返回一个名为rdd2的新RDD:
scala> val rdd1 = sc.parallelize(List(1,2,3,4,5,6))
rdd1: org.apache.spark.rdd.RDD[Int] =
ParallelCollectionRDD[9] at parallelize at
scala> val rdd2 = rdd1.map(x => x+1)
rdd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[10] at map at
scala> rdd2.collect
res5: Array[Int] = Array(2, 3, 4, 5, 6, 7)
上述代码中,向算子map()传入了一个函数x=>x+1。其中x为函数的参数名称,也可以使用其他字符,例如a=>a+1。Spark会将RDD中的每一个元素传入该函数的参数中。当然,也可以将参数使用下划线”_”代替。例如以下代码:
scala> val rdd1= sc.parallelize(List(1,2,3,4,5,6))
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[11] at parallelize at
scala> val rdd2 = rdd1.map(_+1)
rdd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[12] at map at
上述代码中的下划线代表rdd1中的每个元素。rdd1和rdd2中实际上没有任何数据,因为parallelize()和map()都为转化算子,调用转化算子不会立即计算结果。
若需要查看计算结果可使用行动算子collect()。例如以下代码中的rdd2.collect表示执行计算,并将结果以数组的形式收集到当前Driver。因为RDD的元素为分布式的,可能分布在不同的节点上。
scala> rdd2.collect()
res6: Array[Int] = Array(2, 3, 4, 5, 6, 7)
4其他常用的转化算子介绍如下:
filter(func)算子
通过函数func对源RDD的每个元素进行过滤,并返回一个新的RDD。
例如以下代码,过滤出rdd1中大于3的所有元素,并输出结果。
scala> val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7))
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[13] at parallelize at
scala> val rdd2 = rdd1.filter(_>3)
rdd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[14] at filter at
scala> rdd2.collect()
res8: Array[Int] = Array(4, 5, 6, 7)
备注: 下划线”_”代表rdd1中的每个元素
flatMap(func)算子
与map()算子类似,但是每个传入函数func的RDD元素返回0元素会返回0到多个元素,最终会将返回的所有元素合并到一个RDD。
例如以下代码,将集合List转化为rdd1,然后调用rdd1的flatMap()算子将rdd1的每一个元素按照空格分割成多个元素,最终合并所有元素到一个新的RDD。
scala> val rdd1 = sc.parallelize(List("hadoop hello scala","spark hello"))
rdd1: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[15] at parallelize at
scala> val rdd2 = rdd1.flatMap(_.split(" "))
rdd2: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[16] at flatMap at
scala> rdd2.collect()
res9: Array[String] = Array(hadoop, hello, scala, spark, hello)
reduceByKey(func)算子
reduceByKey()算子的作用对象是元素为(key,value)形式(Scala元组)的RDD,可以将相同key的元素聚集到一起,最终把所有相同key的元素合并成为一个元素。该元素的Key不变,value可以聚合成一个列表或者进行求和等操作。最终返回的RDD的元素类型和原有类型保持一致。
例如,有两个同学zhangsan和lisi,zhangsan的语文和数学成绩分别为98,78,lisi的语文和数学成绩分别为88,79,现需要分别求zhangsan和lisi的总成绩。代码如下:
scala> val list = List(("zhangsan",98),("zhangsan",78),("lisi",88),("lisi",79))
list: List[(String, Int)] = List((zhangsan,98), (zhangsan,78), (lisi,88), (lisi,79))
scala> val rdd1 = sc.parallelize(list)
rdd1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[17] at parallelize at
scala> val rdd2 = rdd1.reduceByKey((x,y)=>x+y)
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[18] at reduceByKey at
scala> rdd2.collect()
res10: Array[(String, Int)] = Array((zhangsan,176), (lisi,167))
上述代码使用了reduceByKey()算子,并传入了函数(x,y)=>x+y,x和y代表key相同的两个value值。该算子会寻找相同key的元素,当找到这样的元素时会对其value执行(x,y)=>x+y处理,即只保留求和后的数据作为value。
此外,上述代码中的rdd1.reduceByKey((x,y)=>x+y)也可以简化以下代码:
rdd1.reduceByKey(_+_)
union()算子
该算子将两个RDD合并为一个新的RDD,主要用于对不同的数据来源进行合并,两个RDD中的数据类型要保持一致。
例如以下代码,通过集合创建了两个RDD,然后将两个RDD合并成一个RDD:
scala> val rdd1 = sc.parallelize(Array(1,2,3,4,5))
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[19] at parallelize at
scala> val rdd2 = sc.parallelize(Array(4,5,6,7,8,8,9))
rdd2: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[20] at parallelize at
scala> val rdd3 = rdd1.union(rdd2)
rdd3: org.apache.spark.rdd.RDD[Int] = UnionRDD[21] at union at
scala> rdd3.collect()
res11: Array[Int] = Array(1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 8, 9)
sortBy(func)算子
该算子将RDD中的元素按照某个规则进行排序。该算子的第一个参数为排序函数,第二个参数是一个布尔值,指定升序(默认)或降序。若需要降序排列,则将第二个参数值为false。
例如,一个数组中存放了三个元组,将该数组转化为RDD集合,然后对该RDD按照每个元素中的第二个值进行降序排列。代码如下:
scala> val rdd1 = sc.parallelize(Array(("hadoop",12),("java",32),("spark",22)))
rdd1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[23] at parallelize at
scala> val rdd2 = rdd1.sortBy(x=>x._2,false)
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[28] at sortBy at
scala> rdd2.collect
res12: Array[(String, Int)] = Array((java,32), (spark,22), (hadoop,12))
上述代码sortBy(x=>x._2,false)中的x代表rdd1中的每个元素。由于rdd1的每个元素是一个元组,因此使用x._2取得每个元素的第二个值。当然,sortBy(x=>x._2,false)也可以直接简化为sortBy(_._2,false)。
5行动算子
Spark中的转化算子并不会立即进行运算,而是在遇到行动算子时才会执行相应的语句,触发Spark的任务调度。
Spark常用的行动算子及其介绍如下表
行动算子 |
介绍 |
reduce(func) |
将RDD中的元素进行聚合运算 |
collect() |
向Driver以数组形式返回数据集的所有元素。通过对于过滤操作或其他返回足够小的数据子集的操作非常有用 |
count() |
返回数据集中元素的数量 |
first() |
返回集合中第一个元素 |
take(n) |
返回包含数据集的前n个元素的数组 |
takeOrdered(n,[ordering]) |
返回RDD中的前n个元素,并以自然顺序或自定义的比较器顺序进行排序 |
saveAsSequenceFile(path) |
将数据集中的元素持久化为一个Hadoop SequenceFile文件,并将文件存储在本地文件系统,HDFS或其他Hadoop支持的文件系统的指定目录中。实现了Hadoop Writable接口的键值对形式的RDD可以使用该操作 |
SaveAsTextFile(path) |
将数据集中的元素持久化为一个或一组文本文件,并将文件存储在本地文件系统,HDFS或其他Hadoop支持的文件系统的指定目录中。Spark会对每个元素调用toString()方法,将每个元素转化为文本文件中的一行。 |
saveAsObjectFile(path) |
将数据集中的元素序列化成对象,存储到文件找中。然后可以使用SparkContext.objectFile()对该文件进行加载
|
countByKey() |
统计RDD中key相同的元素的数量,仅元素类型为键值对(key,value)的RDD可用,返回的结果类型为map |
foreach(func) |
对RDD中的每一个元素运行给定的函数func |
下面对其中的几个行动算子进行实例讲解:
(1)reduce(func)算子
将数字1到100所组成的集合转化为RDD,然后对该RDD进行reduce()算子计算,统计RDD中所有元素值的总和。代码如下:
scala> rdd2.collect
res12: Array[(String, Int)] = Array((java,32), (spark,22), (hadoop,12))
scala> val rdd1 = sc.parallelize(1 to 200)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[29] at parallelize at
scala> rdd1.reduce(_+_)
res13: Int = 20100
上述代码中的下划线”_”代表RDD中的元素
(2)count()算子
统计RDD集合中元素的数量,代码如下:
scala> val rdd1 = sc.parallelize(1 to 300)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[30] at parallelize at
scala> rdd1.count
res14: Long = 300
(3)countByKey()算子
List集合中存储的键值对形式的元组,使用该List集合创建一个RDD,然后对其进行countByKey()的计算,代码如下:
scala> val rdd1 = sc.parallelize(List(("zhang",87),("zhang",79),("li",90)))
rdd1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[31] at parallelize at
scala> rdd1.countByKey()
res15: scala.collection.Map[String,Long] = Map(zhang -> 2, li -> 1)
(4)take(n)算子
返回集合中前5个元素组成的数组,代码如下:
scala> val rdd1 = sc.parallelize(1 to 100)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[34] at parallelize at
scala> rdd1.take(5)
res16: Array[Int] = Array(1, 2, 3, 4, 5)