spark资料收集

1.8 Spark

1.8.1 回顾MR并且比较spark

MapReduce :分而治之 map是分
Map端每一个maptask处理的是一个split的数据,一个split对应一个block,split大小和block大小不完全一样,有可能一行记录被划分到了两个block存储

默认的环形buffer是100M,比例是80%,为什么划分两份?为了并行执行,而不是阻塞执行,当达到80%时,会把80M内存锁住,然后进行分区、排序、溢写磁盘小文件,那么其它的数据此时进来就交给剩余20M内存去处理。

当maptask执行完成后,会把磁盘小文件合并成大文件,合并的过程中会进行排序、合并分区,使用的是基于磁盘的归并排序

Reduce task 拉取数据,当一部分maptask执行完成后,就开始拉取,而不是等到全部maptask执行完成。拉取的数据也会放到内存,当内存满了之后,溢写磁盘小文件,当所有的数据拉取完成后,会把这些小文件进行合并、排序、分组,每一组对应一个输出文件。
Shuffer占用了mapreduce的大部分过程,上图的MAP和reduce之间的过程都是shuffer

Spark与MR的比较:
MR将中间结果写入HDFS,怎么理解呢?假如一个任务需要进行两次运算,第一次运算的结果存放在了HDFS,第二次的运算结果也存放在了HDFS,这个是非常消耗性能的。因为在写HDFS时,假如客户端不在HDFS集群,会随机选取一个节点作为第一个副本存放位置,第二个副本的存放位置是同机架的另一个节点,第三个节点是不同机架的一个节点。
除了上面的原因导致spark快之外,spark还有DAG。

Spark运行模式:
Local 多用于测试
Standalone
YARN 最具前景
Mesos

1.8.2 RDD的五大特性

算子

Map 和flatMap的区别:map输入一条,输出一条,mapflat,先map,再flat,输入一条,输出多条。
grouByKey与reduceBykey的区别:
都会按照key先进行分组,reduceByKey会按照传递进去的函数逻辑进行聚合,而groupByKey的返回值是一组数据,相当于将数据进行取并集。
Union:并没有发生shuffer,而是将两个RDD看成一个RDD。
Join:必须作用在KV格式的RDD上面,关联的条件是key相同
Reduce和reduceByKey的区别:一个是action算子,一个是转换算子,reduce作用在整个RDD上面,而reduceBykey作用在KV格式的RDD。
看返回值可以区别是哪一类算子,也可以看源码,调用runJob方法就是action

Driver的作用:
1、 分发task到计算节点中执行
2、 负责将计算结果拉回到Driver(可以选择不拉回)

1.8.3 Wordcount

两个wordcount程序,注意java版本的,一定要自己写一遍JAVA版本的
package com.bjsxt.java.spark.core;

import java.util.Arrays;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;

public class WordCount {
@SuppressWarnings(“resource”)
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName(“WC”).setMaster(“local”);
JavaSparkContext sc = new JavaSparkContext(conf);
sc.textFile(“cs”).flatMap(new FlatMapFunction<String, String>() {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		@Override
		public Iterable<String> call(String line) throws Exception {
			// TODO Auto-generated method stub
			return Arrays.asList(line.split(" "));
		}
		
	}).mapToPair(new PairFunction<String, String, Integer>() {

		@Override
		public Tuple2<String, Integer> call(String word) throws Exception {
			// TODO Auto-generated method stub
			return new Tuple2<String, Integer>(word, 1);
		}
	}).reduceByKey(new Function2<Integer, Integer, Integer>() {
		
		@Override
		public Integer call(Integer v1, Integer v2) throws Exception {
			// TODO Auto-generated method stub
			return v1+v2;
		}
	}).foreach(new VoidFunction<Tuple2<String,Integer>>() {
		
		@Override
		public void call(Tuple2<String, Integer> arg0) throws Exception {
			// TODO Auto-generated method stub
			System.out.println(arg0._1+":count: "+arg0._2);
		}
	});
	
}

}

1.8.4 Cache、persist、checkpoint

Cache操作:将数据缓存到内存 ,如果接下来还有RDD调用这个缓存过的RDD,那么就直接在内存中获取数据,不需要重新计算数据。
Persist(StorageLevel1.MEMORY_ONLY)= Cache
Spark standalone模式安装
提交PI程序
./bin/spark-submit --master spark://node01:7077,node05:7077 --class org.apache.spark.examples.SparkPi ./lib/spark-examples-1.6.0-hadoop2.6.0.jar 10

Checkpoint:支持将结果写入到HDFS,所以数据非常安全。什么样的RDD适合用它来做持久化呢?
这个RDD计算过程非常复杂耗时,这个RDD的结果非常重要
sc.setCheckpointDir(path) //设置hdfs结果文件位置
rdd1.checkpoint() //调用

RDD的依赖关系:例如RDD1RDD2,RDD2知道依赖于RDD1,但是RDD1不知道谁依赖于自己。

1.8.5 Client与cluster提交,以及standalone和yarn

Spark client搭建:
在生产环境中,不管是hadoop集群还是spark集群,都需要一台专门提交程序的客户端

由于下面的yarn模式,需要把hadoop相关文件分发到client

下面这两个是在standalone模式运行:
Cluster方式和client方式提交的区别:
Cluster提交,随机选择一台worker启动Driver
Client方式提交,在提交程序的客户端启动Driver
它们执行是相同的命令,只是执行命令的位置不同而已,一个在client端执行,一个在spark集群上面执行

Client方式执行流程:
1、 客户端执行提交命令,会在客户端启动一个Driver进程
2、 Driver启动完成后,向Master为当前app申请资源
3、 Master会通知资源充足的work启动excutor进程
4、 Driver会分发task到计算节点

Cluster方式执行流程:
1、 客户端执行提交命令,客户端向master申请资源启动Driver进程
2、 Master会寻找一台资源 充足的worker启动Driver进程
3、 …接下来和上面类似。

下面这两个是在yarn上运行:
只需要在client节点修改spark-env.sh
export HADOOP_CONF_DIR=/opt/sxt/hadoop-2.6.5/etc/hadoop

client方式提交在yarn上面
./bin/spark-submit --master yarn --class org.apache.spark.examples.SparkPi ./lib/spark-examples-1.6.0-hadoop2.6.0.jar 10
在http://192.168.66.63:8088 yarn资源管理界面可以看到当前任务

集群方式进行提交,结果只能在yarn的管理界面日志中查看
./bin/spark-submit --master yarn-cluster --class org.apache.spark.examples.SparkPi ./lib/spark-examples-1.6.0-hadoop2.6.0.jar 10

1.8.6 术语解释

Master(standalone):资源管理的主节点(进程)
Cluster Manager:在集群上获取资源的外部服务(例如standalone,Mesos,Yarn )
Worker Node(standalone):资源管理的从节点(进程) 或者说管理本机资源的进程
Application:基于Spark的⽤用户程序,包含了driver程序和运行在集群上的executor程序
Driver Program:用来连接工作进程(Worker)的程序
Executor:是在一个worker进程所管理的节点上为某Application启动的⼀一个进程,该进程负责运行任务,并且负责将数据存在内存或者磁盘上。每个应⽤用都有各自独⽴立的executors
Task:被送到某个executor上的工作单元
Job:包含很多任务(Task)的并行计算,可以看做和action对应
Stage:⼀个Job会被拆分很多组任务,每组任务被称为Stage(就像Mapreduce分map task和reduce task一样)

1.8.7 宽窄依赖

窄依赖:父RDD和子RDD、partition之间是一一对应关系
宽依赖:父RDD和子RDD、partition之间是一对多的关系
父RDD与子RDD是多对一,也是窄依赖
一般宽依赖于shuffer对应

Stage切分规则:遇到宽依赖就切割,注意stage3,其实是B和G在一起的圈圈

RDD不存放数据,那么它存放的是处理逻辑。
例如:RDD1.textFile,那么RDD1存放的就是读取数据的逻辑

上图的stage2的并行度是4,它有4个task,并行度由每个stage的最后一个RDD决定。

Pipeline计算模式,管道计算模式,相当于1+1+1=3,没有产生中间结果2。
而不是:int sum=1+1 ,int count=sum+1 这样就产生了中间结果进行存储。

1.8.8 任务调度

TaskScheduler默认会对失败的task重新发送三次
如果三次还是失败,taskScheduler会想DAGScheduler汇报,DAGScheduler会重新发送stage,默认是4次,那么,一个stage里面有很多task,那么哪些成功的task就不会发送到Excutor,只会发送失败的task。如果发送4次依然失败了呢?那么这个job就宣布失败。

TaskScheduler还会重试挣扎(执行时间长,慢悠悠)的task,它重试时,不会kill掉之前挣扎的task,让他们两个task去比赛,看谁先执行完。这个过程就叫推断执行。推断执行默认是关闭的。

如果两个相同的task在比赛执行的时候,数据库产生重复的数据呢?
方式一:关闭推断执行与重复执行,job全部执行成功才算成功
方式二:在数据库加主键,重复的数据不插入

推测执行的另一个问题:
如果发生了数据倾斜,数据多的task跑的慢,被冤枉成了挣扎的任务,就会启动一个task比赛,然而,它肯定更慢,又被认为是一个挣扎的任务,又启动一个task,导致越来越慢。

1.8.9 粗粒度与细粒度

1.8.10 累加器与广播变量

代码:

package com.bjsxt.java.spark.core.operator;

import java.util.Arrays;
import java.util.List;

import org.apache.spark.Accumulator;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.VoidFunction;

public class AccumulatorTest {

	public static void main(String[] args) {
		SparkConf conf = new SparkConf().setAppName("LineCount")
                .setMaster("local[3]");
        JavaSparkContext sc = new JavaSparkContext(conf);
        
        final Accumulator<Integer> accumulator = sc.accumulator(0);
        
        List<Integer> numbers = Arrays.asList(0,1,2,3,4,5);
        JavaRDD<Integer> numberRDD = sc.parallelize(numbers);
        
        // map对每个元素进行操作
        JavaRDD<Integer> results = numberRDD.map(new Function<Integer, Integer>() {

			private static final long serialVersionUID = 1L;

			@Override
			public Integer call(Integer number) throws Exception {
				System.out.println(accumulator.value());
				accumulator.add(number);
				return number;
			}
		});
        
       
        
        results.foreach(new VoidFunction<Integer>() {
			
			private static final long serialVersionUID = 1L;

			@Override
			public void call(Integer result) throws Exception {
				System.out.println(result);
				
			}
		});
        
        System.out.println(accumulator.value());
        sc.close();
	}
}

执行会报错,因为在Excutor中读数据,在Excutor是不能读累加器数据的

广播变量:每个Excutor上面有一个广播变量,一个Excutor上所有的task共享这个广播变量

代码:

package com.bjsxt.java.spark.core.operator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.spark.Accumulator;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.broadcast.Broadcast;

public class BroadcastTest {

	public static void main(String[] args) {
		SparkConf conf = new SparkConf().setAppName("LineCount")
                .setMaster("local[3]");
        JavaSparkContext sc = new JavaSparkContext(conf);
        
          final int f = 10;
        
//        List<String> broadList = new ArrayList<String>();
//        broadList.add("hello");
//        broadList.add("bjsxt");
//        broadList.add("shsxt");
//        broadList.add("laoxiao");
//        broadList.add("laogao");
//        broadList.add("xiaogao");
        
          final Broadcast<Integer> broadcast = sc.broadcast(f);
//        final Broadcast<List<String>> broadcast = sc.broadcast(broadList);
        
        
        List<Integer> numbers = Arrays.asList(0,1,2,3,4,5);
        JavaRDD<Integer> numberRDD = sc.parallelize(numbers);
        
        // map对每个元素进行操作
        JavaRDD<Integer> results = numberRDD.map(new Function<Integer, Integer>() {

			private static final long serialVersionUID = 1L;

			@Override
			public Integer call(Integer number) throws Exception {
//				return number*f;
				return broadcast.getValue()*number;
//				return broadcast.getValue().get(number).equals("hello")?1:0;
			}
		});
        
       
        
        results.foreach(new VoidFunction<Integer>() {
			
			private static final long serialVersionUID = 1L;

			@Override
			public void call(Integer result) throws Exception {
				System.out.println(result);
				
			}
		});
        
        sc.close();
	}
}

1.8.11 资源调度源码分析

Idea导入源码
关闭工程,点击import选项,找到源码位置,导入时选择maven,源码切入点是Master类的schedule()方法,ctrl+f12可以看到大纲

1.8.12 启动参数计算executor、mem、core数量

默认一个worker启动一个excutor,一个excutor使用1G内存
我的机器是3个worker,每个work 3个core、3.6G内存

./spark-shell --master spark://node01:7077,node05:7077
每个worker启动一个excutor,每个excutor占用1G内存,但是每个占了3核,所以总共启动了3个executor,使用了3G内存,使用了9核(注意:如果不指定核数,它默认会使用所有的核)

集群中启动了多少个executor呢?
Core 表示每个work管理的core,memory是每个work管理的memory
Min(core/corePerExecutor,memory/memPerExcutor)*workNum

./spark-shell --master spark://node01:7077,node05:7077 --executor-cores 1
一共启动了9个excutor,webUI观察确实是9.
min(3/1, 3.6/1)*3=9

./spark-shell --master spark://node01:7077,node05:7077 --executor-cores 1 --executor-memory 2G
webUI观察结果,确实总数为3个executor
min (3/1,3.6/2)*3=3

上面的公式有些情况不准确,但是大概意思就是取最小值最好是单个分析,例如上个提交命令:如果启动了4个excutor,那么,3个excutor时,每个work占用了1core,2Gmemory,由于每一work最大分配3core,3.6G内存,这样的话每个work剩余2core,1.6Gmemory,而一个executor需要1core,2Gmemory,明显memory不够,不能再启动一个executor了

视频16又是一顿源码分析,暂时跳过

小练习:
统计排名最多的校区,然后对这些校区过滤掉,即不打印这个排名最高的校区
思路:将这个排名最高的放到广播变量,然后进行filter

package com.bjsxt.java.spark.core.operator;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.broadcast.Broadcast;

import scala.Tuple2;

public class SampleTest {
	public static void main(String[] args) {
		SparkConf conf = new SparkConf().setAppName("xiaopangzi").setMaster("local");
		JavaSparkContext context = new JavaSparkContext(conf);
		JavaRDD<String> rdd = context.textFile("log.txt");
		rdd = rdd.cache();
		JavaRDD<String> sampleRDD = rdd.sample(false, 0.9);
		
		final String key1 = sampleRDD.mapToPair(new PairFunction<String, String,Integer>() {

			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			@Override
			public Tuple2<String, Integer> call(String t) throws Exception {
				String key = t.split("\t")[1];
				return new Tuple2<String, Integer>(key,1);
			}
		}).reduceByKey(new Function2<Integer, Integer, Integer>() {
			
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			@Override
			public Integer call(Integer v1, Integer v2) throws Exception {
				return v1+v2;
			}
		}).mapToPair(new PairFunction<Tuple2<String,Integer>, Integer, String>() {

			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			@Override
			public Tuple2<Integer, String> call(Tuple2<String, Integer> tuple) throws Exception {
				// TODO Auto-generated method stub
				return new Tuple2<Integer, String>(tuple._2,tuple._1);
			}
		}).sortByKey(false).first()._2;
		
		System.out.println("key:" + key1);
		
		final Broadcast<String> broadcastKey = context.broadcast(key1);
		
		
		rdd.filter(new Function<String, Boolean>() {
			
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			@Override
			public Boolean call(String v1) throws Exception {
				String key = broadcastKey.value();
				return !v1.contains(key);
			}
		}).foreach(new VoidFunction<String>() {
			
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			@Override
			public void call(String t) throws Exception {
				System.out.println(t);
			}
		});
		 
		context.close();
		
		
	}
}

1.8.13 Spark内存管理

假设现在executor内存为2G
静态内存管理:

0.6 0.2 0.2
在0.6中,预留0.1内存防止OOM和给JVM使用 :20.60.1
0.60.90.2 用于数据反序列化 spark.storage.unrollFraction 0.2
0.60.90.8 用于存储RDD持久化数据和广播变量

0.2 shuffer聚合 spark.shuffle.memoryFraction 0.2
0.20.8 spark.shuffle.safetyFraction 0.8
0.2
0.2 预留,防止OOM
0.2 task执行 1-0.6-0.2
参数:http://spark.apache.org/docs/1.6.1/configuration.html
spark.storage.memoryFraction 默认0.6

动态内存管理:这是spark默认的内存管理方式

1.8.14 Spark shuffle

一般一个core分配2~3个Task,核与核之间可能存在性能差异,好的核可以分配多一点task
1个task一般处理1G数据,如果计算逻辑复杂,处理500M也是可以的

现在有1T的数据,计算复杂度是wordcount级别的,那么一个task处理1G数据,需要1024个task,每个core上有3个task,1024/3=342 ,需要324个core,服务器一般是24core 48core,假如是48核,324/48 =8 ,所以最好是8台服务器。当然1台服务器也能处理1T的数据,但是不是最好。

下图是最初的hashshuffle:

最初的hashshuffle:小文件数量:task*分区数量
下图是优化后的shuffle:

优化后的Hashshuffle,称为hashshuffle的合并机制,小文件数量:core*分区数量
spark.shuffle.consolidateFiles 默认为false,设置为true,就表示开启了hashshuffle的合并机制,提交的时候可通过
–conf spark.shuffle.consolidateFiles=true 指定,也可以在代码中指定,也可以在配置文件指定,推荐在提交的时候指定。
上面说的是hashShuffle机制,下面说的是sortShuffle机制,这是spark默认的机制
普通sortShuffle机制:

它最主要有一个索引文件,可以根据索引文件找到每个磁盘小文件的位置。
下面是sortshuffle bypass运行机制:

如果不需要进行排序,可以采用它,它少了一层排序,直接将数据写入磁盘,spark.shuffle.sort.bypassMergeThreshold 默认是200M。

1.8.15 Shuffle调优

SparkConf.set(“spark.shuffle.file.buffer”,“64k”)
推荐使用方式
spark-submit --conf spark.shuffle.file.buffer=64k --conf 配置信息=配置值 …

spark.shuffle.file.buffer
默认值:32k
参数说明:该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。

spark.reducer.maxSizeInFlight
默认值:48m
参数说明:该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。

spark.shuffle.io.maxRetries
默认值:3
参数说明:shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
调优建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。
shuffle file not find taskScheduler不负责重试task,由DAGScheduler负责重试stage

spark.shuffle.io.retryWait
默认值:5s
参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。
调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。

spark.shuffle.memoryFraction
默认值:0.2
参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。

spark.shuffle.manager
默认值:sort
参数说明:该参数用于设置ShuffleManager的类型。Spark 1.5以后,有三个可选项:hash、sort和tungsten-sort。HashShuffleManager是Spark 1.2以前的默认选项,但是Spark 1.2以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。
调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。

spark.shuffle.sort.bypassMergeThreshold
默认值:200
参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高。

spark.shuffle.consolidateFiles
默认值:false
参数说明:如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。
调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。

问题:redcue oom
问题原因:
解决办法:
1、减少拉取的数据量
2、***增加shuffle聚合内存比例
3、增加Executor的内存

1.8.16 Shuffle寻址

每一个map task执行完毕之后,会向Driver汇报,reducetask会向driver请求磁盘小文件位置,获取到了位置信息之后。Reduce task会去对应的executor上面拉取数据。

1.8.17 温度topN与二次排序

Map 和mapPatition:
Map遍历单位是一条记录
MapPartition遍历单位是partition,效率高。注意,它会先将一个partition的数据加载到内存,数据量太大可能出现OOM。在写入数据库的时候一般会使用mapPartition,避免频繁的创建链接,它的使用:传入的参数是迭代器和返回值。
nameRDD.mapPartitions(new FlatMapFunction<Iterator, String>() {}

foreachPartition和mapPartition怎么区别?,无返回值时用foreachPartition,但是两者都有OOM的风险,需要降低每个分区的数据量,可以增加分区数来降低每个分区的数据量。使用repartition(12)进行重新分区。
nameRDD.foreachPartition(new VoidFunction<Iterator>() {}

coalesce:
当然可以使用coalesce(3,false)来减少分区,false表示不发生shuffle
mapPartitionsWithIndex:
mapPartitionsWithIndex(new Function2<Integer, Iterator, Iterator>() {})
参数分别表示分区号、输入数据、输出数据

1.9 Spark sql

Shark:完全依赖于hive,hive一更新,shark就要更新,所以出现了spark sql
Spark sql:支持在RDD中读取数据,不依赖于hive,DataFrame可以和RDD相互转换,它将sql转换为spark程序。
DataFrame:是一个分布式数据容器,更像一个传统数据库的表,它也支持嵌套类型的数据结构(struct,map,array),DataFrame也是RDD

DataFrame的创建方式:

1.9.1 读取Json文件创建

sqlContext.read().format(“json”).load(“path”)
sqlContext.read().json(“path”)

	SparkConf conf = new SparkConf().setAppName("readfromjson").setMaster("local");
	JavaSparkContext sc = new JavaSparkContext(conf);
	SQLContext sqlContext = new SQLContext(sc);
	//DataFrame df = sqlContext.read().format("json").load("people.json");
	DataFrame df = sqlContext.read().json("people.json");
	//第一种方式,不需要注册成临时表,直接用DataFrame API 操作,一般不太建议
	//df.select(df.col("name"),df.col("age")).show();
	df.registerTempTable("person");
	sqlContext.sql("select name,age from person").show();
	sc.stop();

DataFrame中的列会按照Ascii码排序,show()默认显示前20行,可以指定show(100).

将DataFrame转为RDD:
sqlContext.sql(“select name,age from person”).show();
JavaRDD javaRDD = df.javaRDD();
javaRDD.foreach(new VoidFunction() {
@Override
public void call(Row row) throws Exception {
String name=row.getAs(“name”);
Long age=row.getAs(“age”);
System.out.println("name: “+name+”, age: "+age);
}
});

1.9.2 从普通格式的文件创建

①通过反射方式创建

JavaRDD<String> source = sc.textFile("Peoples.txt");
		JavaRDD<People> peopleRDD = source.map(new Function<String, People>() {
			@Override
			public People call(String line) throws Exception {
				String[] split = line.split(",");
				People p = new People();
				p.setAge(Integer.valueOf(split[2]));
				p.setId(Integer.valueOf(split[0]));
				p.setName(split[1]);
				return p;
			}
		});
		DataFrame df = sqlContext.createDataFrame(peopleRDD, People.class);
		df.registerTempTable("person");
		sqlContext.sql("select * from person").show();

public class People implements Serializable{
	private static final long serialVersionUID = 1L;
	private int id;
	private String name;
	private int age;

注意:实体类必须是public的,并且实现序列化,字段前面不能有transient关键字和static关键字,当然static关键字序列化版本号除外。

②动态创建schema的方式,这种方式比较常用

JavaRDD<String> source = sc.textFile("Peoples.txt");
JavaRDD<Row> rowRDD = source.map(new Function<String, Row>() {
	@Override
	public Row call(String line) throws Exception {
		return RowFactory.create(
				Integer.valueOf(line.split(",")[0]),
				line.split(",")[1],
				Integer.valueOf(line.split(",")[2]));
	}
});
List<StructField> asList = Arrays.asList(
		DataTypes.createStructField("id", DataTypes.IntegerType, true),
		DataTypes.createStructField("name", DataTypes.StringType, true),
		DataTypes.createStructField("age", DataTypes.IntegerType, true));
StructType schema = DataTypes.createStructType(asList);
DataFrame df = sqlContext.createDataFrame(rowRDD, schema);
df.registerTempTable("person");
sqlContext.sql("select * from person").show();

动态创建schema的方式,DataFrame中的列不会按照Ascii码排序

1.9.3 读取parquet文件创建

DataFrame df2 = sqlContext.read().parquet("./parquet");
写parquet文件:
df.write().parquet("./parquet")默认是gz格式,我们可以指定序列化方式与压缩格式
conf.set(“spark.serializer”,“org.apache.spark.serializer.KryoSerializer”)
conf.set(“spark.sql.parquet.compression.codec”,“snappy”)

1.9.4 读取mysql创建

Map<String, String> options = new HashMap<String, String>();
options.put("url", "jdbc:mysql://hadoop1:3306/testdb");
options.put("driver", "com.mysql.jdbc.Driver"); 
options.put("user","spark");
options.put("password", "spark2016");
options.put("dbtable", "student_info"); 
DataFrame studentInfosDF = sqlContext.read().format("jdbc").options(options).load();
options.put("dbtable", "student_score"); 
DataFrame df = sqlContext.read().format("jdbc") .options(options).load();
第二种方式加载两张表为两个DataFrame:
DataFrameReader reader = sqlContext.read().format("jdbc");
reader.option("url", "jdbc:mysql://hadoop1:3306/testdb");
reader.option("dbtable", "student_info");
reader.option("driver", "com.mysql.jdbc.Driver");
reader.option("user", "spark");
reader.option("password", "spark2016");
DataFrame studentInfosDF = reader.load();

reader.option("dbtable", "student_score");
DataFrame studentScoresDF = reader.load();

将结果写入mysql(参考广告投放项目中的各省市数据量统计)
Properties properties = new Properties()
properties.setProperty("user","root")
properties.setProperty("password","admin123")
//在表尾部追加数据
reDF.write.mode(SaveMode.Append).jdbc("jdbc:mysql://node01:3306/test?chacterEncoding=utf-8","tabname",properties)
写数据的时候默认是启动200个task,如果数据量不大,我们可以设置为1个分区,让它跑1个task:
conf.set(“spark.sql.shuffle.partitions”,”1”)

1.9.5 读取hive中的数据创建

public static void main(String[] args) {
	SparkConf conf = new SparkConf()
			.setAppName("HiveDataSource")
			.setMaster("local");
	
	JavaSparkContext sc = new JavaSparkContext(conf);
	
	//是SQLContext的子类。
	SQLContext hiveContext = new HiveContext(sc);
	//删除hive中的student_infos表
	hiveContext.sql("DROP TABLE IF EXISTS student_infos");
	
	//在hive中创建student_infos表
	hiveContext.sql("CREATE TABLE IF NOT EXISTS student_infos (name STRING, age INT) row format delimited fields terminated by '\t'");
	
	hiveContext.sql("LOAD DATA "
			+ "LOCAL INPATH '/root/resource/student_infos' "
			+ "INTO TABLE student_infos");
	
	hiveContext.sql("DROP TABLE IF EXISTS student_scores"); 
	hiveContext.sql("CREATE TABLE IF NOT EXISTS student_scores (name STRING, score INT) row format delimited fields terminated by '\t'");  
	hiveContext.sql("LOAD DATA "
			+ "LOCAL INPATH '/root/resource/student_scores'"
			+ "INTO TABLE student_scores");
	
	
	DataFrame goodStudentsDF = hiveContext.sql("SELECT si.name, si.age, ss.score "
			+ "FROM student_infos si "
			+ "JOIN student_scores ss ON si.name=ss.name "
			+ "WHERE ss.score>=80");
	
	hiveContext.sql("USE result");
	
	hiveContext.sql("DROP TABLE IF EXISTS good_student_infos");  
	
	goodStudentsDF.write().saveAsTable("good_student_infos");
	
	Row[] goodStudentRows = hiveContext.table("good_student_infos").collect();  
	for(Row goodStudentRow : goodStudentRows) {
		System.out.println(goodStudentRow);  
	}
	sc.close();
}

提交spark程序的时候,需要去找hive-site.xml文件,需要将客户端的hive-site.xml复制到提交spark程序节点的spark中的conf目录下(node04复制到node05)。

Node03启动hive:hive --service metastore &
在node05启动spark-shell :./spark-shell --master spark://node01:7077
在命令行依次输入:
import org.apache.spark.sql.hive.HiveContext;
val hiveContext= new HiveContext(sc);
hiveContext.sql(“show tables”).show();

注意:通过spark on hive 比原生的在hive上面写sql查询速度要快
将上面的程序打包到node05去提交到集群中运行。

1.9.6 UDF

UDF用户租定义函数、UDAF用户自定义聚合函数
UDF:

List<String> list = new ArrayList<String>();
	list.add("yarn");
	list.add("Marry");
	list.add("Jack");
	list.add("To	m");
	list.add("Tom");
	JavaRDD<String> nameRdd = sc.parallelize(list);
	JavaRDD<Row> rowRdd = nameRdd.map(new Function<String, Row>() {
		private static final long serialVersionUID = 1L;
		@Override
		public Row call(String name) throws Exception {
			return RowFactory.create(name);
		}
	});
	ArrayList<StructField> structFields = new ArrayList<StructField>();
	structFields.add(DataTypes.createStructField("name", DataTypes.StringType, true));
	StructType structType = DataTypes.createStructType(structFields);
	DataFrame nameDF = sqlContext.createDataFrame(rowRdd, structType);
	nameDF.registerTempTable("nameTable");
	 /**
	  * 根据UDF函数参数的个数来决定是实现哪一个UDF  UDF1,UDF2。。。。UDF1xxx
	  */
	/*sqlContext.udf().register("strLen", new UDF1<String,Integer>() {
		private static final long serialVersionUID = 1L;

		@Override
		public Integer call(String str) throws Exception {
			return str.length();
		}
	}, DataTypes.IntegerType);*/
	
	sqlContext.udf().register("strLen", new UDF2<String, Integer, String>() {
		private static final long serialVersionUID = 1L;
		@Override
		public String call(String t1, Integer t2) throws Exception {
			Random random = new Random();
			return t1.length()+random.nextInt(t2)+"~";
		}
	}, DataTypes.StringType);
	sqlContext.sql("SELECT name,strLen(name,10) from nameTable").show();

1.9.7 UDAF

public static void main(String[] args) {
		SparkConf sparkConf = new SparkConf().setAppName("UDAF").setMaster("local");
		JavaSparkContext sc = new JavaSparkContext(sparkConf);
		SQLContext sqlContext = new SQLContext(sc);
		List<String> list = new ArrayList<String>();
		list.add("yarn");
		list.add("Marry");
		list.add("Jack");
		list.add("Tom");
		list.add("Tom");
		/**
		 * SELECT name,udaf(name)  FROM nameTable GROUP BY name;
		 */
		JavaRDD<String> nameRdd = sc.parallelize(list);
		JavaRDD<Row> rowRdd = nameRdd.map(new Function<String, Row>() {
			private static final long serialVersionUID = 1L;
			@Override
			public Row call(String name) throws Exception {
				return RowFactory.create(name);
			}
		});
		ArrayList<StructField> structFields = new ArrayList<StructField>();
		structFields.add(DataTypes.createStructField("name", DataTypes.StringType, true));
		StructType structType = DataTypes.createStructType(structFields);

		DataFrame nameDF = sqlContext.createDataFrame(rowRdd, structType);

		nameDF.registerTempTable("nameTable");
		sqlContext.udf().register("stringCount", new UserDefinedAggregateFunction() {
			private static final long serialVersionUID = 1L;
			// 指定输入数据的字段与类型
			@Override
			public StructType inputSchema() {
				return DataTypes.createStructType(
						Arrays.asList(DataTypes.createStructField("str", DataTypes.StringType, true)));
			}
			/**
			 * 初始化 可以认为是,你自己在内部指定一个初始的值
			 */
			@Override
			public void initialize(MutableAggregationBuffer buffer) {
				buffer.update(0, 0);
			}

			 // 最终函数返回值的类型
			@Override
			public DataType dataType() {
				return DataTypes.StringType;
			}

			// 最后返回一个最终的聚合值 要和dataType的类型一一对应
			@Override
			public Object evaluate(Row row) {
				return row.getInt(0)+"~";
			}

			@Override
			public boolean deterministic() {
				return true;
			}

			// 聚合操作时,所处理的数据的类型
			@Override
			public StructType bufferSchema() {
				return DataTypes.createStructType(
						Arrays.asList(DataTypes.createStructField("bf", DataTypes.IntegerType, true)));
			}

			/**
			 * 更新 可以认为是,一个一个地将组内的字段值传递进来 实现拼接的逻辑
			 *  buffer.getInt(0)获取的是上一次聚合后的值
			 * 相当于map端的combiner,combiner就是对每一个map task的处理结果进行一次小聚合 
			 *    大聚和发生在reduce端
			 */
			@Override
			public void update(MutableAggregationBuffer buffer, Row arg1) {
				buffer.update(0, buffer.getInt(0) + 1);
			}

			/**
			 * 合并 update操作,可能是针对一个分组内的部分数据,在某个节点上发生的 但是可能一个分组内的数据,会分布在多个节点上处理
			 * 此时就要用merge操作,将各个节点上分布式拼接好的串,合并起来
			 * buffer1.getInt(0) : 大聚和的时候 上一次聚合后的值       
			 * buffer2.getInt(0) : 这次计算传入进来的update的结果
			 */
			@Override
			public void merge(MutableAggregationBuffer buffer1, Row buffer2) {
				buffer1.update(0, buffer1.getInt(0) + buffer2.getInt(0));
			}

		});

		sqlContext.sql("SELECT name,stringCount(name) from nameTable group by name").show();

1.9.8 开窗函数

public static void main(String[] args) {
		SparkConf conf = new SparkConf()
				.setAppName("RowNumberWindowFunction");
		JavaSparkContext sc = new JavaSparkContext(conf);
		HiveContext hiveContext = new HiveContext(sc.sc());
		
		// 通过hiveContext操作hive数据库 删除已经存在的表,创建新表,并且加载数据
		hiveContext.sql("DROP TABLE IF EXISTS sales");
		hiveContext.sql("CREATE TABLE IF NOT EXISTS sales ("
				+ "product STRING,"
				+ "category STRING,"
				+ "revenue BIGINT) row format delimited fields terminated by '\t'");  
		hiveContext.sql("LOAD DATA "
				+ "LOCAL INPATH '/root/resource/sales.txt' "
				+ "INTO TABLE sales");
		/**
		 * row_number()开窗函数的作用:按照我们每一个分组的数据,按其照顺序,打上一个分组内的行号
		 * id=2016 [111,112,113]
		 * 那么对这个分组的每一行使用row_number()开窗函数后,三行数据会一次得到一个组内的行号
		 * id=2016 [111 1,112 2,113 3]
		 */
		
		DataFrame top3SalesDF = hiveContext.sql(""
				+ "SELECT product,category,revenue "
				+ "FROM ("
					+ "SELECT "
						+ "product,"
						+ "category,"
						+ "revenue,"
						+ "row_number() OVER (PARTITION BY category ORDER BY revenue DESC) rank "
					+ "FROM sales "  
				+ ") tmp_sales "
				+ "WHERE rank <= 3");
		
		// 将每组排名前3的数据,保存到一个表中
		hiveContext.sql("USE result");
		hiveContext.sql("DROP TABLE IF EXISTS top3Sales");  
		top3SalesDF.write().saveAsTable("top3Sales");
		sc.close();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值