Spark之Join和聚合操作实例

一、环境

Spark、Hadoop环境搭建可参看之前文章。

开发环境:
    系统:Win10
    开发工具:scala-eclipse-IDE
    项目管理工具:Maven 3.6.0
    JDK 1.8
    Scala 2.11.11
    Spark 2.4.3

Spark运行环境:
    系统:Linux CentOS7(两台机:主从节点)
        master : 192.168.190.200
        slave1 : 192.168.190.201
    JDK 1.8
    Hadoop 2.9.2
    Scala 2.11.11
    Spark 2.4.3

二、案例简介 

1. 两个记录文件:name_addr.txt 和 name_phone.txt (每条记录的两个字段用空格分隔)
    name_addr.txt 中的字段:name addr
    name_phone.txt 中的字段:name phone

2. 实现功能:
    1)两个文件记录按name字段实现 Join 连接,(name, addr, phone);
    2)对name_addr.txt文件记录实现 聚合,统计每个地址所在邮编的总人数。

三、流程图

依赖解析:

1. 由于Spark是惰性的,其代码的执行顺序并非我们常规的顺序执行,而是在Action操作处开始: 
    1)Action操作处提交Job作业(此处属于finalStage,即整个作业的最后一个阶段)前,若果有上级Stage,先提交上级Stage
    2)如此向上递归,直到没有所有上级Stage全都提交之后,最后提交finalStage,开始执行整个作业。
2. 在本例中,按照Spark Standalone模式的资源调度模式(先进先出FIFO):
    先由saveAsTextFile(即join的这个流程)操作提交Job作业,执行完毕;
    再由collect(即reduceByKey这个聚合流程)操作提交Job作业,执行。
3. 两个Job流程各阶段RDD间依赖关系(向上依赖):
    1)Join流程的依赖关系:2个RDD读入 <- RDD1+RDD2 <- RDD3 <- RDD4 <- saveAsTextFile
    2)聚合流程的依赖关系:1个RDD读入 <- RDD1 <- RDD5 <- RDD6 <- collect
5. 从中可看出,RDD读入name_addr.txt <- RDD1 这个转换(transformation)操作在两个流程中都执行了一遍,即重复执行了;
    解决方式:对RDD1 使用cache持久化到内存,切断依赖链,再次使用时,无需重新计算。

四、代码(Maven项目:rddOperation)

1. 配置 pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com</groupId><!-- 组织名 -->
  <artifactId>rddOperation</artifactId><!-- 项目名 -->
  <version>0.1</version><!-- 版本号 -->
  <dependencies>
  	<dependency><!-- Spark依赖包 -->
  		<groupId>org.apache.spark</groupId>
  		<artifactId>spark-core_2.11</artifactId>
  		<version>2.4.3</version>
  		<scope>provided</scope><!-- 运行时提供,打包不添加,Spark集群已自带 -->
  	</dependency>
  	<dependency><!-- Log 日志依赖包 -->
  		<groupId>log4j</groupId>
  		<artifactId>log4j</artifactId>
  		<version>1.2.17</version>
  	</dependency>
  	<dependency><!-- 日志依赖接口 -->
  		<groupId>org.slf4j</groupId>
  		<artifactId>slf4j-log4j12</artifactId>
  		<version>1.7.12</version>
  	</dependency>
  </dependencies>
  <build>
  	<plugins>
  		<!-- 混合scala/java编译 -->
  		<plugin><!-- scala编译插件 -->
  			<groupId>org.scala-tools</groupId>
  			<artifactId>maven-scala-plugin</artifactId>
  			<executions>
  				<execution>
  					<id>compile</id>
  					<goals>
  						<goal>compile</goal>
  					</goals>
  					<phase>compile</phase>
  				</execution>
  				<execution>
  					<id>test-compile</id>
  					<goals>
  						<goal>testCompile</goal>
  					</goals>
  					<phase>test-compile</phase>
  				</execution>
  				<execution>
  					<phase>process-resources</phase>
  					<goals>
  						<goal>compile</goal>
  					</goals>
  				</execution>
  			</executions>
  		</plugin>
  		<plugin>
  			<artifactId>maven-compiler-plugin</artifactId>
  			<configuration>
  				<source>1.8</source><!-- 设置Java源 -->
  				<target>1.8</target>
  			</configuration>
  		</plugin>
  		<!-- for fatjar -->
  		<plugin><!-- 将所有依赖包打入同一个jar包中 -->
  			<groupId>org.apache.maven.plugins</groupId>
  			<artifactId>maven-assembly-plugin</artifactId>
  			<version>2.4</version>
  			<configuration>
  				<descriptorRefs>
  					<descriptorRef>jar-with-dependencies</descriptorRef>
  				</descriptorRefs>
  			</configuration>
  			<executions>
  				<execution>
  					<id>assemble-all</id>
  					<phase>package</phase>
  					<goals>
  						<goal>single</goal>
  					</goals>
  				</execution>
  			</executions>
  		</plugin>
  		<plugin>
  			<groupId>org.apache.maven.plugins</groupId>
  			<artifactId>maven-jar-plugin</artifactId>
  			<configuration>
  				<archive>
  					<manifest>
  						<!-- 添加类路径 -->
  						<addClasspath>true</addClasspath>
  						<!-- 设置程序的入口类 -->
  						<mainClass>sparkstreaming_action.rdd.operation.RDDOperation</mainClass>
  					</manifest>
  				</archive>
  			</configuration>
  		</plugin>
  	</plugins>
  </build>
  <repositories>
  	<repository>  
		<id>alimaven</id>  
		<name>aliyun maven</name>  
		<url>http://maven.aliyun.com/nexus/content/groups/public/</url>  
		<releases>  
			<enabled>true</enabled>  
		</releases>  
		<snapshots>  
			<enabled>false</enabled>  
		</snapshots>  
	</repository>
  </repositories>
</project>

2. 主程序: 

    1)输入输出路径采用HDFS分布式文件系统的路径:"hdfs://master:9000/user/spark/rddoperation/"

package sparkstreaming_action.rdd.operation

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext

//单例对象RDDOperation混入App特质,可直接运行
object RDDOperation extends App{
  //Spark配置项
  val conf = new SparkConf()
    .setAppName("rddOperation")
    .setMaster("spark://master:7077")
  //创建Spark上下文
  val sc = new SparkContext(conf)
  //HDFS文件路径:存放输入输出文件的根目录
  val IO_PATH_HDFS = "hdfs://master:9000/user/spark/rddoperation/"
  //文件名
  val txtNameAddr = IO_PATH_HDFS + "input/name_addr.txt"
  val txtNamePhone = IO_PATH_HDFS + "input/name_phone.txt"
  //读入用户地址文件,并按照格式切分,得到对应的RDD
  val rddNameAddr = sc.textFile(txtNameAddr).map(record => {
    //实现每条记录的处理函数
    val tokens = record.split(" ") //按空格分隔
    (tokens(0), tokens(1)) //返回格式:(name, addr)
  })  //RDD1
  rddNameAddr.cache()
  //读入用户电话文件,切分的后得到对应的RDD
  val rddNamePhone = sc.textFile(txtNamePhone).map(record => {
    val tokens = record.split(" ")
    (tokens(0), tokens(1))
  }) //RDD2
  //rddNamePhone.cache()
  //以用户名的key进行join操作(默认按key)
  val rddNameAddrPhone = rddNameAddr.join(rddNamePhone) //RDD3
  //对用户电话RDD进行HTML格式变化,产生新的RDD
  val rddHtml = rddNameAddrPhone.map(record => {
    //join后的数据集格式为:(name, (addr, phone))
    val name = record._1
    val addr = record._2._1
    val phone = record._2._2
    s"<h2>姓名:${name}</h2><p>地址:${addr}</p><p>电话:${phone}</p>"
  })  //RDD4
  //输出操作
  val rddOutPut = rddHtml.saveAsTextFile(IO_PATH_HDFS + "output/UserInfo")
  //根据地址格式得到邮编RDD
  val rddPostcode = rddNameAddr.map(record => {
    val postcode = record._2.split("#")(1)
    (postcode, 1)
  })  //RDD5
  //汇总邮编出现次数
  val rddPostcodeCount = rddPostcode.reduceByKey(_ + _)  //RDD6
  //汇总结果到Driver节点,打印结果
  rddPostcodeCount.collect().foreach(println)
  sc.stop
}

五、打包运行

1.在项目的根目录下运行命令行窗口(在目录下 "shift+右键",选择命令行窗口 Power Shell)
  执行如下命令:(编译代码)
    > mvn install
    编译成功后,会在当前目录的 ".\target\" 下产生两个jar包;
    其中的 rddOperation-0.1-jar-with-dependencies.jar 用来提交给Spaek集群

2.上传输入文件至HDFS(默认开启Hadoop集群)
  创建输入文件,单词用空格分隔:
    $ mkdir /opt/rddoperation
    $ vi /opt/rddoperation/name_addr.txt
    bob shanghai#200000
    amy beijing#100000
    alice shanghai#200000
    tom beijing#100000
    lulu hangzhou#310000
    nick shanghai#200000
    lily nanjing#210000

    $ vi /opt/rddoperation/name_phone.txt
    bob 15700079421
    amy 18700079458
    alice 17730079427
    tom 16700379451
    lulu 18800074423
    nick 14400033426
    lucy 18900045781

  上传至HDFS目录下:
    $ hadoop fs -mkdir /user/spark
    $ hadoop fs -mkdir /user/spark/rddoperation
    $ hadoop fs -mkdir /user/spark/rddoperation/input
    $ hadoop fs -put /opt/rddoperation/* /user/spark/rddoperation/input

3.将Jar包提交至主节点上,执行Spark作业:
  提交Spark作业:(需先配置Spark_HOME环境变量)
    $ spark-submit \
      --class sparkstreaming_action.rdd.operation.RDDOperation \
      /opt/rddOperation-0.1-jar-with-dependencies.jar
    注1:其中每行的末尾 "\" 代表不换行,命令需在一行上输入,此处只为方便观看
    注2:提交的Jar包放在 /opt/ 目录下
  运行成功后,屏幕输出如下邮编地区人数统计结果:
    (310000,1)
    (200000,3)
    (210000,1)
    (100000,2)

4.查看Join结果:
  查看join结果输出目录:
    $ hadoop fs -ls /user/spark/rddoperation/output/UserInfo
    /user/spark/rddoperation/output/UserInfo/_SUCCESS    --代表作业成功(标识文件)
    /user/spark/rddoperation/output/UserInfo/part-00000    --结果文件1
    /user/spark/rddoperation/output/UserInfo/part-00001    --结果文件2
  查看join结果(使用/*查看UserInfo/下所有文件):
    $ hadoop fs -cat /user/spark/rddoperation/output/UserInfo/*
    <h2>姓名:alice</h2><p>地址:shanghai#200000</p><p>电话:17730079427</p>
    <h2>姓名:tom</h2><p>地址:beijing#100000</p><p>电话:16700379451</p>
    <h2>姓名:lulu</h2><p>地址:hangzhou#310000</p><p>电话:18800074423</p>
    <h2>姓名:nick</h2><p>地址:shanghai#200000</p><p>电话:14400033426</p>
    <h2>姓名:amy</h2><p>地址:beijing#100000</p><p>电话:18700079458</p>
    <h2>姓名:bob</h2><p>地址:shanghai#200000</p><p>电话:15700079421</p>

5.成功运行结束

 六、参考文章

1. 《Spark Streaming 实时流式大数据处理实战》第三章 Spark编程模型

2. 《Spark 最佳实践》4.3 DAG调度

3. 《深入理解Spark》5.4 任务提交

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值