一、上次课回顾

二、IDEA整合Maven搭建Spark开发环境

三、词频统计案例开发及上传jar包到服务器并准备测试数据

四、提交Spark应用程序运行

五、词频统计案例迭代之输出结果到HDFS

六、词频统计案例迭代之处理多个输入文件

七、词频统计案例之输入文件规则匹配

八、带排序的词频统计案例开发及执行过程剖析

九、带排序的词频统计案例spark-shell快速测试

##

一、上次课回顾

上次课博客总结:
1、若泽数据B站视频Spark05 - Spark-RDD的基本操作(一)



2、若泽数据B站视频Spark06 - Spark-RDD的基本操作(二)



思考:

  • 我们使用Maven进行项目的管理,拷贝jar包遇到各种冲突怎么办?

项目实战中Hadoop综合Phoenix、Hbase,保守几百个jar包,冲突解决不了,so使用Maven管理项目。

二、IDEA+Maven来构建我们的Spark Application

  1. 点击file->new->project,点击create from archetype;选择org.scala-tools.archetypes:scala-archetype-simple;
  2. 单机下一步GroupId:com.ruozedata.bigdata、ArtifactId:g6-spark;
  3. 选定maven_home 目录、使用maven_home下的settings.xml、再选择local_repository(这个是settings.xml中加的自动识别出来的)。

两个地方查看引入的Jar包:
1、External Libraries
2、View --> Tool Windows --> Maven Projects,点击项目名称,再点击Dependencies

在pom.xml文件中需要添加如下:
1、除自带的repository以外还需要添加一个,它默认的repository是访问不到的。
<repository>
      <id>cloudera</id>
      <name>cloudera</name>
      <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
    </repository>
    2、添加dependency依赖文件
    <!--添加spark-core依赖-->
    <dependency>
      <groupId>org.apache.spark</groupId>
      <artifactId>spark-core_2.11</artifactId>
      <version>${spark.version}</version>
    </dependency>

    <!--添加Hadoop.version的依赖-->
    <dependency>
      <groupId>org.apache.hadoop</groupId>
      <artifactId>hadoop-client</artifactId>
      <version>${hadoop.version}</version>
    </dependency>
    
    注意的是: 在properties文件下做出如下修改,便于后期对版本进行维护。
    <properties>
    <scala.version>2.11.8</scala.version>
    <spark.version>2.4.0</spark.version>
    <hadoop.version>2.6.0-cdh5.7.0</hadoop.version>
  </properties>

在pom.xml中,右键maven,点击reimport,重跑依赖包;再点击view->Tool Windows->Maven Projects,点开lifecycle,点击clean。再等一会就不会报错了。

Spark的使用 spark使用视频_hadoop

此处注意点:
1)更换scala版本
2)添加Spark-Core依赖
3)添加hadoop-client依赖
4)添加cdh仓库

三、词频统计案例开发及上传jar包到服务器并准备测试数据

1、快速的在IDEA下写一个WordCount程序:
新建一个WordCountApp:

package com.ruozedata.spark

import org.apache.spark.{SparkConf, SparkContext}

object WordCountApp {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf()
    val sc = new SparkContext(sparkConf)

    val textFile = sc.textFile(args(0))

    val wc = textFile.flatMap(line => line.split("\t"))
      .map((_,1)).reduceByKey(_+_)

    wc.collect.foreach(println)
    
    sc.stop()
  }

}

2、WordCountApp开发完成后,进入Maven Projects ==> Lifecycle ==> Run Maven Build,进行打包,打包完成后会有如下提示包的位置:

Building jar: G:\ruozedata_workspace\sparktrain\target\spark-train-1.0.jar

Spark的使用 spark使用视频_spark_02


3、通过命令上传至服务器

[hadoop@hadoop004 lib]$ pwd
/home/hadoop/lib
[hadoop@hadoop004 lib]$ ll
total 16
-rw-r--r-- 1 hadoop hadoop    2217 Jul 31  2019 spark-train-1.0.jar

小插曲:如何上传本地的文件至服务器?
生产上一般使用rz上传,ftp工具在生产上是不用的??

生产上都是跳板机,先登录跳板机,再从跳板机到服务器,
	中间还有一层堡垒机,ftp服务是绝对连接不上去的,ftp在生产上是不okay的。
	我们生产上使用rz肯定是没有问题的,rz不用经过堡垒机。

4、如何在hdfs上使用
hdfs数据准备:hdfs dfs -mkdir /wordcount/input/
hdfs dfs -put /home/hadoop/ruozeinput.txt /wordcount/input/

四、提交Spark应用程序运行

详见:http://spark.apache.org/docs/latest/submitting-applications.html
Once a User application is bundled, it can be launched using the bin/spark-submit script. This script takes care of setting up the classpath with Spark and its dependencies, and can support different cluster manages and deploy modes that Spark supports.
译:一旦一个应用程序被构建完成,它能够使用bin/spark-submit脚本;这个脚本使用它的classpath和依赖项,它能够支持不同的集群管理和部署模式。

1、如何在hdfs上使用:
hdfs数据准备:hdfs dfs -mkdir /wordcount/input/
hdfs dfs -put /home/hadoop/ruozeinput.txt /wordcount/input/

[hadoop@hadoop002 bin]$ ./spark-submit --help

Usage: spark-submit [options] <app jar | python file | R file> [app arguments]

//[options] 可以选择的选项,application jar或者python file或者R file,[app argumnets]这个又是可选的。
Usage: spark-submit --kill [submission ID] --master [spark://...]			//干掉进程
Usage: spark-submit --status [submission ID] --master [spark://...]		//查看状态
Usage: spark-submit run-example [options] example-class [example args]		//运行案例

Options:
	--master Master URL		spark://host:port, mesos://host:port,yarn,or local (default:local[*])
	//可以指定master的URL
	--deploy-mode DEPLOY_MODE	whether to launch the driver program locally ("client") or on one of the worker machines inside the cluster ("cluster")
	
	//部署模式,有client和cluster模式。
	--class CLASS_NAME	Your application's main class (for java / scala apps)	//你的应用程序的主类仅仅是对于Java、scala文件来说的。
	--name	 Name		A name of your application		//指定spark的作业名称,可以在UI上看到
    --jars JARS		Comma-seprated list of maven coordinates of jars to include on the driver and executor classpaths.
    
    最主要的执行片段:
    ./bin/spark-submit \
    --class <main-class> \
    --depoly-mode <deploy-mode> \
    --conf <key>=<values> \
    ........# other options
    <application-jar> \
    [application-arguments]

执行代码:
运行时把 \ 去掉,并在一行输入:

1、通过hdfs访问:
[hadoop@hadoop002 spark-2.4.2-bin-2.6.0-cdh5.7.0]$ ./bin/spark-submit \
--class com.ruozedata.spark.WordCountApp \ 
--master local[2] \ 
/home/hadoop/lib/spark-train-1.0.jar \
hdfs://10.0.0.132:9000/wordcount/input/ruozeinput.txt
--------------------------------------------------------------------
注意:Hadoop通过路径和链接访问hdfs:
10.0.0.132:9000同下面的<value></value>中的内容
hdfs://10.0.0.132:9000/wordcount/input/
[hadoop@hadoop002 hadoop]$ cat core-site.xml 
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
        <!--Yarn 需要使用 fs.defaultFS 指定NameNode URI -->
        <property>
                <name>fs.defaultFS</name>
                <value>hdfs://10.0.0.132:9000</value>
       </property>
</configuration>

Spark的使用 spark使用视频_spark_03


2019-07-31执行spark-submit时报错,发现打包的代码写在test类下面哈哈,所以提示java.lang.ClassNotFoundException

Spark的使用 spark使用视频_spark_04

五、词频统计案例迭代之输出结果到HDFS

承接4,输出在控制台上一点用都没有

1、修改4中的代码:
wc.collect().foreach(println)		==>		wc.saveAsTextFile(args(1))
重新打包编译上传至服务器

2、我们查看源码中saveAsTextFile方法:
概念:把RDD的结果存储到一个路径,我们指定为hdfs上的路径:hdfs://10.0.0.132:9000/wordcount/output4

3、命令测试:
 ./bin/spark-submit --class com.ruozedata.spark.WordCountApp --master local[2] /home/hadoop/lib/spark-train-1.0.jar hdfs://10.0.0.132:9000/wordcount/input/ruozeinput.txt hdfs://10.0.0.132:9000/wordcount/output4/
 
4、去到hdfs上查看文件:
19/07/23 22:36:12 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Found 3 items
-rw-r--r--   1 hadoop supergroup          0 2019-07-23 22:35 /wordcount/output4/_SUCCESS
-rw-r--r--   1 hadoop supergroup         60 2019-07-23 22:35 /wordcount/output4/part-00000.bz2
-rw-r--r--   1 hadoop supergroup         50 2019-07-23 22:35 /wordcount/output4/part-00001.bz2

执行过程中要关注控制台跳出的信息:
19/07/23 22:35:32 INFO mapred.FileInputFormat: Total input paths to process : 1
==>代表的是输入的文件数.

六、词频统计案例迭代之处理多个输入文件

多传几个文件进入/wordcount/input/ 目录下

1、此时hdfs目录下/wordcount/input/  目录下有多个文件
hdfs dfs -put /home/hadoop/data/ruozeinput.txt /wordcount/input/2
hdfs dfs -put /home/hadoop/data/ruozeinput.txt /wordcount/input/3

2、再次执行命令:
 ./bin/spark-submit --class com.ruozedata.spark.WordCountApp --master local[2] /home/hadoop/lib/spark-train-1.0.jar hdfs://10.0.0.132:9000/wordcount/input/* hdfs://10.0.0.132:9000/wordcount/output5/
此时控制台上:Total input paths to process : 3			//因为输入文件是3个

3、hdfs上查看执行结果:
[hadoop@hadoop002 lib]$ hdfs dfs -text /wordcount/output5/*
19/07/23 22:50:24 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
19/07/23 22:50:25 INFO compress.CodecPool: Got brand-new decompressor [.bz2]
(world,6)
19/07/23 22:50:25 INFO compress.CodecPool: Got brand-new decompressor [.bz2]
(hello,9)
19/07/23 22:50:25 INFO compress.CodecPool: Got brand-new decompressor [.bz2]
(john,3)

七、词频统计案例之输入文件规则匹配

此时hdfs上/wordcount/input下有好几个文件

1、此时有3个文件:
[hadoop@hadoop002 lib]$ hdfs dfs -ls /wordcount/input/
19/07/23 22:57:51 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Found 3 items
-rw-r--r--   1 hadoop supergroup         35 2019-07-23 22:47 /wordcount/input/1
-rw-r--r--   1 hadoop supergroup         35 2019-07-23 22:47 /wordcount/input/2
-rw-r--r--   1 hadoop supergroup         35 2019-07-20 05:46 /wordcount/input/ruozeinput.txt

2、spark-submit支持通配符:
输入文件中:hdfs://10.0.0.132:9000/wordcount/input/* .txt		//它就自动会去找/wordcount/input/目录下以.txt结尾的文件.
otal input paths to process : 1			//因为输入文件中只有一个文件结尾是.txt

八、带排序的词频统计案例开发及执行过程剖析

1、再次修改源码:
//    wc.collect().foreach(println)\
    val SortTest = wc.map(x => (x._2,x._1)).sortByKey(false).map(x =>(x._2,x._1)
         SortTest.saveAsTextFile(args(1))

2、再次打包上传进行测试(注意更换输出目录避免重复):
./bin/spark-submit --class com.ruozedata.spark.WordCountApp --master local[2] /home/hadoop/lib/spark-train-1.0.jar hdfs://10.0.0.132:9000/wordcount/input/* hdfs://10.0.0.132:9000/wordcount/output6/

3、读取出/wordcount/output6/目录下内容,证明降序排列成功:
[hadoop@hadoop002 spark-2.4.2-bin-2.6.0-cdh5.7.0]$ hdfs dfs -text /wordcount/output7/* 
19/07/23 23:17:06 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
19/07/23 23:17:07 INFO compress.CodecPool: Got brand-new decompressor [.bz2]
(hello,9)
19/07/23 23:17:07 INFO compress.CodecPool: Got brand-new decompressor [.bz2]
(world,6)
19/07/23 23:17:07 INFO compress.CodecPool: Got brand-new decompressor [.bz2]
(john,3)

4、成功了,Are You Okay

5、升序排列:val SortTest = wc.map(x => (x._2,x._1)).sortByKey(true).map(x =>(x._2,x._1)
把false ==> true

九、带排序的词频统计案例spark-shell快速测试

1、val textFile = sc.textFile("hdfs://10.0.0.132:9000/wordcount/input/*.txt")
textFile: org.apache.spark.rdd.RDD[String] = hdfs://10.0.0.132:9000/wordcount/input/*.txt MapPartitionsRDD[7] at textFile at <console>:24

2、scala> val wc = textFile.flatMap(line => line.split("\t")).map((_,1)).reduceByKey(_+_)
wc: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[10] at reduceByKey at <console>:25

3、scala> wc.map(x =>(x._2,x._1)).collect
res10: Array[(Int, String)] = Array((3,hello), (2,world), (1,john))

4、scala> wc.map(x => (x._2,x._1)).sortByKey(false).collect
res12: Array[(Int, String)] = Array((3,hello), (2,world), (1,john))

问题:sortByKey中有两个默认参数,如果没有参数,括号可以省略;如果声明的是默认参数,括号不能省略。

sortByKey方法在源码中定义:
sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)