===================WORDCOUNT案例:=================
def main(args: Array[String]) { val lines=List("hadoop , spark, hbase,hive" , "hadoop,spark,hbase,hive", "", "spark,hive,spark,spark',hdfs,hadoop!") lines .filter(_.nonEmpty) .flatMap(_.split(",")) .filter(_.nonEmpty) .map(word=>(word.trim.replaceAll("!","").replaceAll("'",""),1)) .groupBy(_._1) .map{ case (word,iter)=>{ val sum=iter.map(_._2).sum (word,sum) } } .foreach(println) }===================PV&&UV统计========================
数据格式类似
2013-05-19 13:00:00 http://www.taobao.com/17/?tracker_u=1624169&type=1 B58W48U4WKZCJ5D1T3Z9ZY88RU7QA7B1 http://hao.360.cn/ 1.196.34.243 NULL -1
def main(args: Array[String]) { val conf=new SparkConf() .setMaster("local[*]") .setAppName("pv_uv") val sc=new SparkContext(conf) val time=System.currentTimeMillis() val path="hdfs://bigdata-01.yushu.com:8020/user/yushu/spark/data/page_views.data" val rdd=sc.textFile(path) //公共的计算 val mapredRdd=rdd.map(line=>{ var a=line.split("\t") a }) .filter(arr=> { var flag=arr.length>3&& arr(0).trim.length>10 flag }) .map(arr=>{ (arr(0).trim.substring(0,10),arr(1).trim,arr(2).trim) }) //pv的第一种计算方式 val pvRdd1=mapredRdd .map(t=>(t._1,t._2)) .groupByKey() .map{ case(date,urls)=>{ val pv=urls.size (date,pv) } } pvRdd1.saveAsTextFile(s"/user/yushu/spark/core/pv_uv/${time}/pv/1")
//PV的第二种计算方式 val pvRdd2=mapredRdd .map(t=>(t._1,1)) .reduceByKey(_+_) pvRdd2.saveAsTextFile(s"/user/yushu/spark/core/pv_uv/${time}/pv/2") //uv的计算 val uvRdd=mapredRdd .map(t=>(t._1,t._3)) .distinct() .map(t=>(t._1,1)) .reduceByKey(_+_) uvRdd.saveAsTextFile(s"/user/yushu/spark/core/pv_uv/${time}/uv") sc.stop() }===================分组排序TOPN案例========================
数据格式:
aa 78 bb 98 aa 80 cc 98 aa 69 cc 87 bb 97 cc 86 aa 97 bb 78 bb 34 cc 85 bb 92 cc 72 bb 32 bb 23
def main(args: Array[String]) { val conf=new SparkConf() .setMaster("local[*]") .setAppName("GroupSortedTopN2") val sc=new SparkContext(conf) val rdd=sc.textFile("datas/groupsort.txt") val result1=rdd.map(line=>{line.split(" ")}) .filter(arr=>arr.length==2) .map(arr=>(arr(0).trim,arr(1).trim.toInt)) .groupByKey() .map(t=>{ val key=t._1 val values=t._2.toList.sorted.takeRight(3).reverse (key,values) }) result1.foreachPartition(iter=>{ iter.foreach(println) }) println("re3------第二种方式:两阶段聚合------") val result3=rdd .map(line=>line.split(" ")) .filter(arr=>arr.length==2) .mapPartitions(iter=>{ val random=Random iter.map(arr=>((random.nextInt(3),arr(0).trim),arr(1).trim.toInt)) }) .groupByKey() .flatMap{ case((_,key),iter) =>{ val r=iter.toList.sorted.takeRight(3) r.map(v=>(key,v)) } } .groupByKey() .map{ case(key,iter)=>{ val r=iter.toList.sorted.takeRight(3).reverse (key,r) } } result3.foreachPartition(iter=>iter.foreach(println)) }================================================
RDD的几个API的应用:
map:数据转换,给定的f返回的数据类型是啥,最终rdd中的数据类型就是啥
flatMap: 数据转换 + 扁平化操作,给定的f返回的集合中的数据类型是啥,最终rdd中的数据类型就是啥
filter:数据过滤,对于给定函数f返回为false的数据进行过滤,保留返回结果为true的数据
reduceByKey: 对于key/value的数据进行按照key进行数据分组,然后对每组数据进行给定函数的聚合操作
====================Linux上执行spark的jar文件================== http://spark.apache.org/docs/1.6.1/submitting-applications.html 通过bin/spark-submit命令进行任务的提交操作 代码打包的jar文件要先上传上Linux上 命令: ./bin/spark-submit \ --class com.yushu.bigdata.spark.app.core.LogPvAndUvCountSpark \ --master local[*] \ /home/yushu/logs-analyzer.jar ./bin/spark-submit \ --class com.yushu.bigdata.spark.app.core.LogPvAndUvCountSpark \ --master spark://bigdata-01.yushu.com.com:7070 \ /home/yushu/logs-analyzer.jar ./bin/spark-submit \ --class com.yushu.bigdata.spark.app.core.LogPvAndUvCountSpark \ --master spark://bigdata-01.yushu.com:7070 \ --deploy-mode cluster \ /home/yushu/logs-analyzer.jar ### 当提交访问为standalone+cluster的时候,最好选择rest url ./bin/spark-submit \ --class com.yushu.bigdata.spark.app.core.LogPvAndUvCountSpark \ --master spark://bigdata-01.yushu.com:6066 \ --deploy-mode cluster \ /home/yushu/logs-analyzer.jar ./bin/spark-submit \ --class com.yushu.bigdata.spark.app.core.LogPvAndUvCountSpark \ --master spark://bigdata-01.yushu.com:6066 \ --deploy-mode cluster \ --executor-cores 1 \ --total-executor-cores 2 \ --driver-cores 1\ --executor-memory 1g \ --driver-memory 512m \ /home/yushu/logs-analyzer.jar =============================================================
=============================================================
Windows上执行spark的应用
异常信息
-1. Exception in thread "main" org.apache.spark.SparkException: A master URL must be set in your configuration
解决方案:在SparkkConf中指定master的信息
-2. Exception in thread "main" org.apache.spark.SparkException: An application name must be set in your configuration
解决方案:在SparkConf中指定应用名称的信息
-3. null/bin/winutil.exe
原因:windows上没有配置HADOOP环境导致的
解决方案:将hadoop的安装包解药到本机上的任意路径,路径要求不能有空格,不能包含中文;然后将winutils放入hadoop根目录的bin文件夹中,最后配置HADOOP_HOME环境变量
操作完后,重启IDEA,如果重启IDEA无效,重启机器
-4. (windows上可能出现)有可能遇到其它异常,NullPointerException, 这种异常有可能需要修改源码(Hadoop源码)
================================================================
================================================================
Spark应用的参数配置
可以在三个地方进行配置,分别的优先级如下:
1: ${SPARK_HOME}/conf/spark-defaults.conf
2: bin/spark-submit 参数
3:代码中通过SparkConf来给定
优先级:3 > 2 > 1
==================================================================
================================================================== Spark应用的结构 Driver + Executors Driver:运行SparkContext上下文的地方,进行RDD初始化的地方,资源申请、RDD Job调度地方(JVM中) 其实就是main函数运行的地方 Executor:运行具体Task的地方,一个Executor中可以并行的运行多个Task任务 一个Application包含多个Job 一个Job包含多个Stage 一个Stage包含多个Task Task是最小运行单位,是运行在executor中的线程 deploy-mode: 指定driver在哪儿运行 client:默认,driver运行的机器就是执行spark-submit脚本命令的机器 cluster:在集群中任选一台机器作为节点来执行driver进程 ===================Spark的资源调优=========================
主要来讲:调整CPU、内存、executor的数量;通过spark的参数来调整的
http://spark.apache.org/docs/1.6.1/configuration.html
spark-submit的脚步参数资源调优:(相关参数)
--master:给定spark应用运行在哪个地方(eg: local、standalone、yarn、mesos)
--deploy-mode:指定driver在哪儿运行(cluster、client)
--class:指定spark应用的类全称是那个(包名 + 类名),如果该参数不给定,那么应用默认是jar文件中默认的main class
--conf PROP=VALUE: 给定spark应用的参数信息,eg: --conf "spark.ui.port=5050"
--properties-file: 给定应用读取spark的配置信息,默认是conf/spark-defaults.conf
--driver-memory:给定driver的内存,默认1024M
--executor-memory:给定每个executor的内存,默认1024M
--proxy-user:给定运行spark应用的代理用户名称,默认为空
standalone + cluster:
--driver-cores:指定driver的数量,默认1
standalone/mesos + cluster:
--supervise:指定当driver宕机的时候,会自动进行恢复操作
standalone/mesos:
--total-executor-cores NUM: 指定所有的executor的总core的数量,默认情况下是所有core的数量
standalone/yarn:
--executor-cores NUM: 给定每个executor的core的数量,如果是yarn上,默认值为;standalone上默认值为all
yarn:
--driver-cores NUM: 指定cluster的情况下,driver需要的core数量,默认为1
--queue QUEUE_NAME:指定提交到yanr上的时候,提交到那个队列,默认是default
--num-executors NUM:指定executor的数量,默认2个
==========================================================
==========================================================
Spark的内存管理模型(Executor的内存管理)
老模型和新模型比较而言,老模型的资源没有动态分配而已,有些情况下,存在资源利用不充分的问题
spark1.6之前的版本
storage:
指定的是数据缓存所应用到的内存,比如: rdd.cache、rdd.persist、广播变量、共享变量等
由参数:spark.storage.memoryFraction(0.6)控制,默认表示60%的executor的内存用于storage
shuffle:
指定的是在shuffle过程中应用到的内存大小,比如:上一个阶段的数据会先写入内存、shuffle的数据排序等
由参数:spark.shuffle.memoryFraction(0.2)控制,默认表示20%的executor的内存用于shuffle过程
user:
代码中用到的集合、对象等应用到内存,默认占比是(0.2):
1 - spark.storage.memoryFraction - spark.shuffle.memoryFraction
spark1.6+版本
使用老的内存管理模型(1.6之前的):
spark.memory.useLegacyMode:将该参数设置为true即可,默认为false
使用新的内存模型(1.6+)<内存自动分配模型>:
Reserved Memory:
固定内存部分,主要是spark框架使用,默认大小:300M,不可修改
storage&shuffle Memory:
大小由参数:spark.memory.fraction(默认0.75)
由两部分构造:storage memory 和 execution memory(shuffle memory)
动态调整的含义:
当storage部分的内存不够的情况下会自动从execution中获取内存,返之也行
参数:spark.memory.storageFraction(默认0.5),指定storage部分最少内存的占比
动态调整过程:
-a. 如果storage memory满了,execution有多的内存
Storage的操作就会将数据存储到Execution内存部分中
-b. 如果Execution Mmemory满了,同时之前Storage占用了execution部分的内存
将内存中storage部分进行删除,空出内存存储execution部分操作所产生的数据;但是Storage部分内存至少会保留spark.memory.storageFraction给定大小的内存
User Mempry:
用户代码使用到的内存,大小是:1 - spark.memory.fraction(0.25)
eg: 默认情况下一个executor的内存是1024M,使用新的模型来讲:
Reserved:300M
User: (1024 - 300) * (1 - 0.75) = 181M
storage&shuffle:
(1024 - 300) * 0.75 = 534M
最少storage占比:
534 * 0.5 = 267M
====> 动态可分配大小为(534 - 267) = 267M
=======================================================
=======================================================
Spark的动态分配
指的根据业务的需要/应用的情况动态的指定executor的数量
应用场景:streaming或者一致运行的spark程序适应访问高峰和低谷不同情况下的资源利用的问题
相关参数:(一般需要给定四个参数)
http://spark.apache.org/docs/1.6.1/configuration.html#dynamic-allocation
spark.dynamicAllocation.enabled:开启动态资源分配,默认false
spark.dynamicAllocation.initialExecutors:初始化的时候,初始化多少个executor的数量,默认是spark.dynamicAllocation.minExecutors的参数值
spark.dynamicAllocation.minExecutors:给定应用中保持的最小exeuctor的数量,默认0
spark.dynamicAllocation.maxExecutors:给定该应用最多允许持有的executor的数量,默认无,需要给定
====================================================
====================================================
Spark on yarn
http://spark.apache.org/docs/1.6.1/running-on-yarn.html
前提:
1. yarn的配置信息(yarn-site.xml)在spark的classpath中
==> 配置HADOOP_CONF_DIR在spark-env.sh中<配置本地运行环境>
2. 启动yarn
./bin/spark-submit \
--class com.yushu.bigdata.spark.app.core.LogPvAndUvCountSpark \
--master yarn \
--deploy-mode cluster \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 1 \
--num-executors 3 \
/home/yushu/logs-analyzer.jar
./bin/spark-submit \
--class com.yushu.bigdata.spark.app.core.LogPvAndUvCountSpark \
--master yarn \
--deploy-mode client \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 1 \
--num-executors 3 \
/home/yushu/logs-analyzer.jar
==============================================================
==============================================================
Spark应用的构建及提交流程:
-1. Driver中构建RDD的DAG图
-2. RDD的job被触发(需要将具体的rdd执行过程提交到executor上执行,此时可以从4040页面看到执行的内容)
-3. Driver中的DAGScheduler将RDD划分为不同的Stage阶段
-4. Driver中的TaskScheduler将一个一个的stage对应的task提交到executor上执行
Spark应用通过spark-submit命令提交的执行过程:
-1. client向资源管理器申请资源(Yarn、Standalone等), 运行driver程序;如果是client模式,driver的运行资源不需要申请
-a. 当运行模式为spark on yarn cluster的情况下,此时driver和ApplicationMaster功能合并
-b. 当运行模式为spark on yarn client的情况下,driver不需要申请资源,此时申请ApplicationMaster运行的资源
driver:DAG的构建、Job的调度
ApplicationMaster: 负责executors的资源申请与管理
-2. 启动Driver(ApplicationMaster)
-3. Driver向资源管理器申请executors的资源
-a. 一台机器上可以运行多个executor ==> 一个NodeManager上可以运行多个executor
-4. executors执行启动
-5. RDD的构建
-6. RDD对应Job的执行
==================================================
==================================================
spark on yarn job history配置
http://spark.apache.org/docs/1.6.1/running-on-yarn.html ===> Debugging your Application
-1. 配置并启动spark的job history相关信息
http://bigdata-01.yushu.com:18080/
-2. 配置yarn-site.xml文件,然后重启yarn
-3. 修改spark-defaults.conf内容
spark.yarn.historyServer.address http://bigdata-01.yushu.com:18080
===============================================================
RDD
Resilient Distributed Datasets ==> 弹性分布式数据集
Resilient:可以存在给定不同数目的分区、数据的缓存的时候可以缓存一部分分区的数据也可以缓存整个rdd的数据、分区的数量可以由业务来定
Distributed:分区的数据可以分布在不同的节点上,并且在不同的executor上执行
Dataset:内部存储的数据类似是一个集合数据
RDD中的数据是不可变的、是分区存在;也就是说每次调用一个api对数据进行操作的时候,实际上是产生一个新的RDD
RDD的底层构建原理:
-1. RDD的分区:
其实是一个逻辑结构,只是一个映射到数据源的结构(怎么读取数据)
分区的数量由:InputFormat的getSplit方法返回集合中的split数量决定,一个split就是一个分区
RDD中其实存储的就是分区的数据,也就是存储的各个分区的位置信息(怎么读数据)
===> RDD中其实是不包含数据的
RDD的构建
-1. RDD的构建底层调用的是MapReduce的InputFormat
-2. sc.textFile模式使用TextInputFormat读取数据
-3. 默认情况下SparkContext中的API,在读取数据的时候使用的是MapReduce的旧API
RDD的创建
-1. 外部数据(非内存数据): 基于MapReduce的InputFormat进行rdd的创建
sc.textFile ==> 底层使用TextInputFormat读取数据形成RDD,内部使用旧MR的API
sc.newAPIHadoopFile ==> 底层使用TextInputFormat读取数据形成RDD,内部使用新的MR的API
sc.newAPIHadoopRDD ==> API明确指定使用哪个InputFormat读取数据
-2. 内存中数据:直接将集合中的数据转换为RDD
val seq = List(1,3,5,6,7,9)
val rdd = sc.parallelize(seq)
RDD的方法类型(API类型)
-1. transformation(transformation算子):转换操作
功能:由一个RDD产生一个新的RDD的API,不会触发job的执行
在这类API调用的过程中,实质是在driver中构建了一个RDD的依赖图,也称为构建RDD的执行逻辑图(DAG图)
是一个lazy的过程
-2. action(action算子):动作/操作
功能:触发rdd的job提交过程,并将rdd对应的job的dag图进行划分后提交到executor上执行
该类型API的调用,会触发job的执行,执行结果会输出到其它文件系统中或者返回给driver中
-3. pesist:(RDD的缓存/RDD的持久化)
功能:将rdd的数据缓存到指定级别的存储系统中,或者将缓存的rdd进行清除缓存操作
rdd.cache() 数据缓存到内存中
rdd.persist(xx) 将rdd的数据缓存到指定级别的存储系统中(内存、磁盘)
rdd.unpersist() 清除该rdd的缓存
注意:rdd的缓存是一个lazy的过程,清空缓存操作是一个立即执行的操作
RDD的输出输出内部其实调用的是:MapReduce的OutputFormat类
=================================================================
=================================================================
Spark优化
-1. 代码优化
-a. 如果一个RDD只使用一次,可以不进行赋值操作,直接调用后续的API ===> 链式编程
-b. 如果一个RDD被多次使用,记住一定要cache
-c. 对于重复操作的API,将其提出并形成一个新的RDD,并将RDD进行缓存
-d. 有限选择reduceByKey和aggregateByKey的API,尽量减少使用groupByKey(groupByKey容易出现OOM和数据倾斜的问题)
-e. 尽量少用可能会产生shuffle的API
-f. 优先使用foreachPartition而不是用foreach API
-2. 资源优化
-3. 数据倾斜优化
导致原因:数据重复分配不均匀导致的,可能会导致某些task执行时间过长或者OOM异常
-a. 更改分区的策略(增加分区的数量 + 根据数据定制开发Partitioner)
-b. 两阶段聚合
===================================================================
===================================================================
RDD依赖
窄依赖
子RDD的每个分区的数据来自常数个父RDD的分区;父RDD的每个分区的数据到子RDD的时候一定在一个分区中
常用方法:map、flatMap、filter、mapPartitions、union、join(要求两个父RDD具有相同的partitioner,同时两个父RDD的分区数量一致,并且子RDD的分区数量和父RDD的分区数量一致)
宽依赖:
子RDD的每个分区的数据来自父RDD的所有分区;父RDD的每个分区的数据到子RDD的时候都有可能分配到任意一个子分区中
常用方法:xxxxByKey、reparation、groupBy、sortBy、join等
==================================================================
==================================================================
Spark应用的结构
Driver + Executors
Driver:运行SparkContext上下文的地方,进行RDD初始化的地方,资源申请、RDD Job调度地方(JVM中)
其实就是main函数运行的地方
Executor:运行具体Task的地方,一个Executor中可以并行的运行多个Task任务
一个Application包含多个Job
一个Job包含多个Stage
一个Stage包含多个Task
Task是最小运行单位,是运行在executor中的线程
Stage的划分规则:
当RDD的DAG图进行提交的时候,会由DAGScheduler对DAG进行stage的划分,划分规则如下:
从后往前进行推断,如果遇到一个宽依赖,就形成一个stage,直到rdd的开头
最后一个Stage叫做ResultStage,其它的Stage叫做ShuffleMapStage
Task:
是Executor中执行的最小单位
task实质上就是分区,一个分区的数据执行代码就是一个Task
分区:从数据的分布情况来讲
task:从数据的执行逻辑情况来讲
一个Stage中的每个Task的执行逻辑(代码)是一样的,只是处理的数据不同而已,task的代码逻辑其实就是对应stage中的API组成的一个执行链
====================================================================
====================================================================
Spark Shuffle机制
Spark的shuffle只存在宽依赖中,也就是说两个stage之间一定存在这儿shuffle过程;stage内部的执行时全部在内存中完成的,不需要考虑写磁盘、不同节点的数据流通
由Spark Shuffle Manager进行管理,参数:spark.shuffle.manager:sort
http://spark.apache.org/docs/1.6.1/configuration.html#shuffle-behavior
Shuffle优化:
-1. spark.shuffle.manager:sort
当task的数量小于200的时候,会自动启用by_pass模式(没有数据排序的操作)
spark.shuffle.sort.bypassMergeThreshold:200
-2. spark.shuffle.manager:hash
当应用中不需要数据排序的情况下,可以考虑使用hash shuffle manager
当分区数量比较多的时候(超过100), 需要开启文件聚合功能:
参数: spark.shuffle.consolidateFiles:默认为false,需要设置为true
==================================================================
==================Spark的调度==================================
Spark Schdeduler
http://spark.apache.org/docs/1.6.1/configuration.html#scheduling
http://spark.apache.org/docs/1.6.1/job-scheduling.html
Spark的Job调度分为FIFO(先进先出, 默认)和FAIR(公平调度)
FIFO: 按照job的提交时间来执行
FAIR:并行的执行job任务
相关参数:
spark.scheduler.mode:FIFO, 给定调度的模式
spark.speculation:是否开启spark的推测执行,默认为false,不开启
spark.task.cpus:1,给定一个task允许需要多少core
spark.task.maxFailures:4,给定一个task最多允许失败次数
==================Spark的优化之两阶段聚合====================================
适用场景:对RDD进行分组操作的时候,某些Task处理数据过多或者产生OOM异常等情况
实现思路:第一阶段给每个key加一个随机数,然后进行局部的聚合操作;第二阶段去除每个key的前缀,然后进行全局的聚合操作
实现原理:将key添加随机前缀的方式可以让一个key变成多个key,可以让原本被一个task处理的数据分布到多个task上去进行局部的聚合,进而解决单个task处理数据太多的问题;随后去掉前缀,进行全局集合,完成功能的实现
优缺点:对于聚合类shuffle操作(groupByKey、reduceByKey等)产生的问题能够很好的解决;但是对于非聚合类shuffle操作( join等)产生的问题很难使用该方式解决
==================================================
对于Driver中的数据需要在Executor中执行的,要求数据必须是可以序列化的,实现了java.io.Serializable;或者使用SerializableWritable将hadoop的writable对象转换为可序列化对象
注意:一般情况下,如果存在调用RDD相关API将executor上的数据获取到driver中,那么要求driver内存是单个executor的1-1.5倍
共享变量
http://spark.apache.org/docs/1.6.1/programming-guide.html#shared-variables
广播变量(broadcast variables)
http://spark.apache.org/docs/1.6.1/programming-guide.html#broadcast-variables
功能:减少driver到executor的数据传输量,将原来每个task拥有一个副本的方式改成一个executor拥有一个数据副本,一个executor中的所有task共享一个数据副本
注意:
1. 广播变量是不允许被修改的,只读的
2. 广播变量在executor中存储在storage内存区
3. 当task执行的过程中发现对应的广播变量不存在了,会自动从driver中重新获取
4. 一般,如果一个广播变量不会使用了,会进行一个清空操作
累加器(Accumulators)
http://spark.apache.org/docs/1.6.1/programming-guide.html#accumulators
功能:类似MapReduce执行过程中的元数据累加操作;累加器在executor中是进行数据累加操作,在driver中进行数据的读取操作(execturo中的数据读取结果不保证<不同executor上结果读取结果不同>)
实现原理:
-1. 每个executor会分别维护一个累加器
-2. 当所有executor执行完成后,所有的累加器会合并形成一个新的累加器
-3. driver中使用的累加器就是最终形成的新累加器
========================广播变量案例=====================
def main(args: Array[String]) { val conf=new SparkConf() .setMaster("local[*]") .setAppName("BVD") val sc=new SparkContext(conf) val rdd = sc.parallelize(Array( "hadoop, yushu' spark! hadoop", "hadoop; spark. spark hbase!!", "hadoop, spark hbase hive?", "hadoop hive spark hive" )) val list = Array(",", "\\.", ";", "!", "\\?", "'") val broadcast=sc.broadcast(list) rdd .flatMap( _.split(" ") .map( w=>broadcast.value.foldLeft(w)( (a,b)=>{ a.replaceAll(b,"")} ) ) ) .map((_,1)) .reduceByKey(_+_) .foreachPartition(iter=>iter.foreach(println)) }======================== SparkCore总结=========================================