一、Spark-Core基础篇回顾
二、Spark如何进行大数据的逻辑处理
三、Spark的运行架构(重要指数五颗星)
1、为什么选择Spark?
2、RDD的两种创建方式:
textFile:local(本地)、只要是兼容hdfs的都可以
Parallelize:仅适用于本地测试
3、Transformation:
特点:Lazy延迟执行,写一堆代码并不会马上执行
4、Action算子
return a value to Driver(返回结果到Driver)
典型的Action算子:
IDEA下的一个常见错误: A master URL must be set in your configuration;有人不经会问,我在sparkConf中不是已经设置了AppName和Master么;
查看SparkContext的源码:
def this() = this(new SparkConf()) //在未传入参数时,它会new一个SparkConf,使得我们原先定义的sparkConf失效:
1、点击val sc = new SparkContext()中的SparkContext中去:
/**
* Create a SparkContext that loads settings from system properties (for instance, when
* launching with ./bin/spark-submit).
*/
def this() = this(new SparkConf())
2、再点击这个SparkConf中去:
/** Create a SparkConf that loads defaults from system properties and the classpath */
def this() = this(true)
//此处又创建了一个SparkConf使得我们的原先new出来sparkConf不生效了:所以需要把sparkConf传值传进去。
3、如下代码就会出现报错:
import org.apache.spark.{SparkConf,SparkContext}
object TestApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("TestApp").setMaster("local[2]")
val sc = new SparkContext()
sc.parallelize(List(1,2,3,4,5,6,7,8)).foreach(println)
sc.stop()
}
}
4、在3的基础上把SparkConf传值传进去即可:
val sparkConf = new SparkConf().setAppName("TestApp").setMaster("yarn")
val sc = new SparkContext(sparkConf)
数据来源:自行准备的日志
主要字段:traffic(流量位于第20个字段)、domain(域名位于第11个字段)
需求分析:按照域名进行分组,然后组内求流量之和
求和:主要使用reduceByKey算子
import org.apache.spark.{SparkConf,SparkContext}
object LogApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("LogApp").setMaster("local[2]")
val sc = new SparkContext(sparkConf)
val lines = sc.textFile("file:///d:/baidu.log")
val result = lines.map( x => {
val splits = x.split("\t")
val domain = splits(10)
val traffic = splits(19)
(domain,traffic)
}).reduceByKey(_+_).take(10).foreach(println)
sc.stop()
}
改代码:以增强代码的健壮性
从打进来的字符长度进行判断,是不是分隔符分割完后就是72个字符长度,是的话就没问题了:
import org.apache.spark.{SparkConf,SparkContext}
object LogApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("LogApp").setMaster("local[2]")
val sc = new SparkContext(sparkConf)
val lines = sc.textFile("file:///d:/baidu.log")
val result = lines.map( x => {
val splits = x.split("\t")
if (splits.length == 72) {
val domain = splits(10)
val traffic = splits(19).toLong
(domain, traffic)
} else {
("null",0L)
}
}).reduceByKey(_+_).take(10).foreach(println)
sc.stop()
}
}
万一打进来的这两个字段不是string类型和long类型,是别的类型,那这段代码是不是就会报错了:所以需要try catch一下:
import org.apache.spark.{SparkConf,SparkContext}
object LogApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("LogApp").setMaster("local[2]")
val sc = new SparkContext(sparkConf)
var lines = sc.textFile("file:///d:/baidu.log")
val result = lines.map( x => {
val splits = x.split("\t")
var traffic = 0L
if (splits.length == 72){
val domain = splits(10)
try{
traffic = splits(19).toLong
} catch {
case e:Exception => 0L
}
(domain,traffic)
} else
{
("null",0L)
}
}).reduceByKey(_+_).take(10).foreach(println)
sc.stop()
}
}
catch中不写case e:Exception =>0L,这个traffic还是0;此时的代码才显的稍稍有些健壮性可言。
数据准备:
1、如下这样的一份数据,第一列数据可能是单独的ip或者ip:port,第二列是访问用户,第三列可有可无用来测试:
121.228.247.192:3306 john
121.28.247.191 sail
121.228.247.192 john
121.28.247.191:80 sail
121.228.247.192 john
121.28.247.191:954 sail
121.228.247.192 john
121.28.247.191 sail
121.28.247.191 ron 白领
1、进入如下的github网址下载项目:
2、项目下载到本地,进入cmd控制台对这个项目进行编译打jar包,需要进入到ipdatabase这个目录下面;
3、cmd下执行命令,将这个jar包上传到本地maven仓库上去:
mvn install:install-file -Dfile:C:\Users\Administrator\Desktop\ipdatabase-master\ipdatabase-master\target\ipdatabase-1.0-SNAPSHOT.jar \
-DgroupId=com.ggstar \
-DartifactId=ipdatabase \
-Dversion=1.0 \
-Dpackaging=jar
去到maven的仓库目录下就能看见这个包已经被打了进去,下次可以直接使用,命令解析:
-Dfile 后面跟着的是打包编译后jar包位置所在的全路径
-DgroupId 后面跟着的是包的名称
-DartifactId 项目的名称
-Dversion 版本名字
4、此时在Spark项目中引入依赖:
<dependency>
<groupId>com.ggstar</groupId>
<artifactId>ipdatabase</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
5、需要新建一个resources目录,把源代码中国的两个文件"ipDatabase.csv"和"ipRegion.xlsx"复制到resources目录下;并且在project structure中把这个目录设置为资源文件(file --> Project Structure --> Modules):
1、使用别人写好的工具测试一个基础的可以解析出省份:
import com.ggstar.util.ip.IpHelper
object ParseIpApp {
def main(args: Array[String]):Unit = {
println(getCity("112.80.63.242"))
}
def getCity(ip:String) = {
IpHelper.findRegionByIp(ip)
}
}
2、用来判断字符长度是否等于2,等于2进行读取解析,字符长度大于2的默认是脏数据,不进行读取;
import com.ggstar.util.ip.IpHelper
import org.apache.spark.{SparkConf,SparkContext}
object ParseIpApp {
def main(args: Array[String]):Unit = {
val sparkConf = new SparkConf().setAppName("ParseIpTest").setMaster("local[2]")
val sc = new SparkContext(sparkConf)
val lines = sc.textFile("file:///D:/iptest.log")
val result = lines.map( x => {
val splits = x.split("\t")
if (splits.length == 2) {
val ip = splits(0)
val province = IpHelper.findRegionByIp(ip)
(province, 1)
} else {
("null",1)
}
}).reduceByKey(_+_).take(5).foreach(println)
sc.stop()
}
}
//输出结果:
(鏈煡,3)
(null,1)
(江苏省,3)
(河北省,2)
3、在2的基础上进行修改,因为输入的第一个字段可能是ip也有可能是ip:port的形式,所以还需要对第一个字段进行解析:
package com.ruozedata.bigdata.SparkCore02
import com.ggstar.util.ip.IpHelper
import org.apache.spark.{SparkConf,SparkContext}
object ParseIpApp {
def main(args: Array[String]):Unit = {
val sparkConf = new SparkConf().setAppName("ParseIpTest").setMaster("local[2]")
val sc = new SparkContext(sparkConf)
//读取文件
val lines = sc.textFile("file:///D:/iptest.log")
val result = lines.map( x => {
//tab键进行分割
val splits = x.split("\t")
var ip = ""
//对长度做判断,对于长度不等于2的默认是脏数据,不进行操作
if (splits.length == 2) {
//文件中第一个字段是ip字段,但是采集过来的数据有些是纯IP,有些是ip:port
val args0 = splits(0)
//对第一列使用:进行分割
val serverport = args0.split(":")
//判断serverport拆分后的字符长度是几个,如果是长度为2,则取前面的ip部分;长度不为2,ip就等于它本身;
if (serverport.length == 2){
ip = serverport(0)
} else {
ip = args0
}
//定义省份,使用ipdatabase库中的findRegionByIp方法
val province = IpHelper.findRegionByIp(ip)
(province, 1)
//这边的(province,1)解析出省份赋上1个1,做reduceByKey操作;下面的else对应的是总长度,总长度不为2的数据,key字段赋值"null",value字段赋值1
} else {
("null",1)
}
}).reduceByKey(_+_).take(5).foreach(println)
sc.stop()
}
}
//运行出的结果:
(null,1)
(江苏省,4)
(河北省,4)
如何进行降序排序:
.reduceByKey(_+_).sortBy(_._2,false).take(10).foreach(println)
//sortBy源码的定义:
/**
* Return this RDD sorted by the given key function.
*/
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] = withScope {
this.keyBy[K](f)
.sortByKey(ascending, numPartitions)
.values
}
如上代码的作用:
1、需求的拆分,架构怎么设计合理,这些代码写出来一点意义都没有
2、仅仅了解到Spark是如何处理业务场景的;
3、掌握生产上边界值的处理 -->能够使得代码的健壮性有保障
Spark Application:a driver program + executors on the cluster
Application jar:
client:driver run local
cluster:driver run cluster
Worker node:any node that can run application node in the cluster.
运行executor进程的,对于yarn来说就是在NodeManager上运行一个Container
Executor:
a process launched for an application on a worker node,(一个应用程序的进程)
that runs tasks and keeps data in memory or a disk storage across them(把数据放在内存或磁盘上面存储,每一个executor可以跑多个tasks)
Each application has its own executors :每一个应用程序有他自己的executors
如下图所示:NodeManager上跑container,Application和App虽然是跑在一个NodeManager上的,上面三个和下面三个是相互独立的。
Task:A Unit of work that will be sent to one executor
每一个executor中运行多个task
Job:
只要spark遇到一个action就是一个job
stage
Stage: Each job gets divided into smaller sets of tasks called stages that depend on each other (similar to the map and reduce stages in MapReduce); you will see this term used in Driver’s logs
图片解析:
一个application应用程序跑在yarn上的3个executor上;另外一个application跑在另外3个executor上,虽然2个application的executor是运行在相同的NodeManager上,但是它们是独立的,不起冲突。
Spark应用程序是一组独立的进程在集群上的:有一个Driver和多个executor,通过SparkContext对象在你的主程序中,也叫作Driver Program
为了让你的作业能够运行到集群上,SparkContext需要通过Cluster Manager去到集群上申请资源,CM去申请资源,此时就会启动Executor在集群上运行;executor是一=一堆进程去运行我们的计算和存储我们的数据;Driver Program会发送你的应用程序代码到executor上去;最终SparkContext会发送task去到executor上去。
1、每一个应用程序都有多个独立的进程
2、Spark不关心底层的集群管理:
Spark is agnotic to the underlying cluster manager(底层的集群管理)
如何理解:代码都是一样的,可以跑在不同的地方,只是一个参数设置的问题:setMaster;一旦Spark拿到执行进程,进程之间就会进行通信;Spark运行在集群上相对容易,它也能运行在Mesos、Yarn上。
3、Driver和executor网路一定要通:
4、Driver调度作业要尽可能的靠近NodeManager
1、每一个应用程序都有其独立的进程
2、Spark不关心底层的集群管理
3、Driver 和executor的网络一定要通
4、driver调度作业要尽可能靠近NodeManager