一、实验环境
Ubuntu 20.04 - VMware Workstation
java - openjdk version “1.8.0_312”
Scala code runner version 2.11.12
Spark 2.1.0
sbt 1.3.8
二、Spark-shell交互式编程+HDFS操作
- 实验内容
chapter5-data1.txt数据集包含了某大学计算机系的成绩,数据格式如下所示:
- HDFS操作:根据原始数据集中的姓名字段生成分班表class.txt,并将其存入HDFS中(地址自定)。按照姓名首字母分班:其中A-G为1班,H-N为2班,O-T为3班,U-Z为4班。class.txt每行包含2个字段,格式为(班级编号,姓名)
- Spark-shell交互式编程:从HDFS中读取class.txt, 从本地读取chapter5-data1.txt,已知班级信息表与成绩表,对于每一门课,找出班级平均分>50的班级,并将结果写入HDFS中。最终输出的格式为:课程名,班级编号,平均分, 并按照课程名降序排列。
- 代码实现
使用命令:
cd /usr/local/spark
./bin/spark-shell
启动进入spark-shell
object Task1{
def main(args:Array[String]) : Unit = {
//chapter5-data1.txt的存放在本地,地址如下,用绝对路径调用
val lines = sc.textFile("file:///usr/local/spark/mycode/t1/chapter5-data1.txt")
//将原有成绩表格每一行按","分割,取第0位即学生的姓名,用map函数映射为(姓名,1)的rdd形式
//用reduceByKey函数对Key去重,Value值全部变为1,随后去除Value单独取出学生姓名的String
val name = lines.map(row=>(row.split(",")(0),1)).reduceByKey((x,y)=>1).map(_._1)
//使用take函数取学生姓名首字母,用matches函数匹配后在filter函数中分别过滤出对应的学生
//随后用map函数将班级编号作为key学生姓名作为Value,得到格式(班级编号,姓名)的rdd
val f1 = name.filter(row=>row.take(1).matches("[A-G]")).map(row=>(1,row))
val f2 = name.filter(row=>row.take(1).matches("[H-N]")).map(row=>(2,row))
val f3 = name.filter(row=>row.take(1).matches("[O-T]")).map(row=>(3,row))
val f4 = name.filter(row=>row.take(1).matches("[U-Z]")).map(row=>(4,row))
//由于上述rdd格式相同,使用自带的合并方法 ++ 进行合并即可得到需要存储的class.txt的内容
val f = f1 ++ f2 ++ f3 ++ f4
分班后的部分信息显示如下
//第二个终端
//cd /usr/local/hadoop
//./sbin/start-dfs.sh
//打开HDFS后,将格式为(班级编号,姓名)的rdd存储在HDFS的/user/hadoop目录下,文件名为class.txt
f.saveAsTextFile("class.txt")
存储后,在第二个终端输入 ./bin/hdfs dfs -ls .
可以查看在HDFS的/user/hadoop目录,发现多了一个class.txt文件
//读取HDFS/user/hadoop目录下的class.txt文件,获得分班表
val classline = sc.textFile("hdfs://localhost:9000/user/hadoop/class.txt")
//读取本地chapter5-data1.txt,获得学生科目成绩表
val lines = sc.textFile("file:///usr/local/spark/mycode/t1/chapter5-data1.txt")
//对分班表进行处理,使用map函数将原本读取的String处理成(姓名,班级编号)格式的rdd
//其中使用split函数对","进行分割,分割出班级编号和名字;再使用filterNot函数去除String中的括号。班级信息用toInt转为Int型
val classinfo = classline.map(row=>(row.split(",")(1).filterNot(c=>c==')'),row.split(",")(0).filterNot(c=>c=='(').toInt))
//处理学生科目成绩表,用map函数将其映射成(姓名,(科目,成绩))的key-value的rdd形式
val info = lines.map(row=>(row.split(",")(0),(row.split(",")(1),row.split(",")(2).toInt)))
//处理后的分班信息表和科目成绩表的key都为学生姓名,用join函数将两个表合并,join后的格式为(姓名,((科目,成绩),班级编号))
//由于之后需要对不同班级的不同学科进行统计,故将(科目,班级编号)作为key,用map函数将其处理成((科目,班级编号),成绩)的key-value形式的rdd
val info1 = info.join(classinfo).map(x=>((x._2._1._1,x._2._2),x._2._1._2))
//将value从成绩映射为(成绩,1)的形式后,用reduceByKey和mapValues的操作进行求平均,方法和练习一样
//随后用map函数映射成(科目,班级编号,平均成绩)的形式,用filter函数过滤出平均成绩大于50的行
//再用map函数映射成(科目,(班级编号,平均成绩))的key-value形式的rdd,用sortByKey(false)按照课程名降序排序,最后再map成(科目,班级编号,平均成绩)的形式
info1.mapValues(x=>(x,1)).reduceByKey((x,y)=>(x._1+y._1,x._2+y._2)).mapValues(x=>(x._1/x._2)).map(x=>(x._1._1,x._1._2,x._2)).filter(x=>x._3>50).map(x=>(x._1,(x._2,x._3))).sortByKey(false).map(x=>(x._1,x._2._1,x._2._2)).collect()
}
}
- 最终结果
可以看到最后的结果按照学科和班级编号进行了平均分的计算,并按照学科名降序显示出了平均分大于50的信息。
三、编写独立应用程序实现求二次排序问题
- 实验内容
每个输入文件表示班级学生某个学科的成绩,每行内容由4个字段组成,第一个是学生编号,第二个是学生名字,第三个是科目名称,第4个是学生成绩;要求编写 Spark 独立应用程序:将以下3个file中的成绩合并到同一个文件中,需要按照学生编号升序排序,若学生编号相同,则按照成绩降序排列,最后将结果输出到一个新文件中。下面是输入文件和输出文件的一个样例,供参考。
File1.txt
1 小明 Algorithm 92
2 小红 Algorithm 87
3 小新 Algorithm 82
4 小丽 Algorithm 90
File2.txt
1 小明 Database 95
2 小红 Database 81
3 小新 Database 89
4 小丽 Database 85
File3.txt
1 小明 Python 82
2 小红 Python 83
3 小新 Python 94
4 小丽 Python 91
输出文件:
1 小明 Database 95
1 小明 Algorithm 92
1 小明 Python 82
2 小红 Algorithm 87
2 小红 Python 83
2 小红 Database 81
3 小新 Python 94
3 小新 Database 89
3 小新 Algorithm 82
4 小丽 Python 91
4 小丽 Algorithm 90
4 小丽 Database 85
- 代码实现
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
import org.apache.spark.HashPartitioner
object JoinUp {
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("JoinUp")
val sc = new SparkContext(conf)
//分别读取三个文件中的内容
val dataFile1 = "file:///home/hadoop/hm2/File1.txt"
val dataFile2 = "file:///home/hadoop/hm2/File2.txt"
val dataFile3 = "file:///home/hadoop/hm2/File3.txt"
val data1 = sc.textFile(dataFile1,3)
val data2 = sc.textFile(dataFile2,3)
val data3 = sc.textFile(dataFile3,3)
//文件中读取后的格式为 “编号 学生名字 科目名称 学生成绩” 的String
//用map函数按照空格进行分割,处理成(编号,学生名字,科目名称,学生成绩)的rdd,其中编号和成绩转为Int格式
val info1 = data1.map(row=>(row.split(" ")(0).toInt,row.split(" ")(1),row.split(" ")(2),row.split(" ")(3).toInt))
val info2 = data2.map(row=>(row.split(" ")(0).toInt,row.split(" ")(1),row.split(" ")(2),row.split(" ")(3).toInt))
val info3 = data3.map(row=>(row.split(" ")(0).toInt,row.split(" ")(1),row.split(" ")(2),row.split(" ")(3).toInt))
//三个表经过处理后的格式相同,用 ++ 进行合并
val info = info1 ++ info2 ++ info3
//根据题目要求,需要按照学生编号升序、成绩降序排序,使用sortBy函数,在降序列前加上负号即可
//最后用map函数处理成和源文件格式相同的String写入文件
val res = info.sortBy(x=>(x._1,-x._4)).map(x=>x._1.toString+" "+x._2+" "+x._3+" "+x._4.toString)
res.saveAsTextFile("file:///home/hadoop/hm2/result")
} }
- 具体操作
- 在终端进入目录/usr/local/spark/mycode/t2,mkdir -p src/main/scala,并新建一个task2.scala文件,将上述代码复制到其中
- 在目录/usr/local/spark/mycode/t2目录下新建task2.sbt,在其中写入
- 在目录/usr/local/spark/mycode/t2下执行打包程序
sudo /usr/local/sbt/sbt package
这里由于更新花费了不少时间,更新完成后重新输入出现信息
- 最后在目录/usr/local/spark/mycode/t2下执行命令提交程序
/usr/local/spark/bin/spark-submit --class "JoinUp" /usr/local/spark/mycode/t2/target/scala-2.11/task2-project_2.11-1.0.jar
- 在文件中指定的目录/home/hadoop/hm2/result 下即可得到结果文件,结果文件包括
- 结果文件
可以看到,结果文件为已经二次排序好的格式