官方定义:spark是一个基于内存的分布式计算框架
它会使得计算速度以及开发速度快!
特点:
One stack rule them all !
一站解决所有问题
热查询(Hive)
批处理(MapReduce)
实时流计算(Storm)
回顾MapReduce 的 Shuffle过程 见图
![[Spark进阶]-- spark-1.6.x-小结_spark](https://s2.51cto.com/images/blog/202211/03141221_63635bc5d0a6683597.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
![[Spark进阶]-- spark-1.6.x-小结_Boo_02](https://s2.51cto.com/images/blog/202211/03141221_63635bc5f098187911.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
hadoop慢的原因:
DISK IO 输入输出DISK IO,Shuffle阶段也是DISK IO
spark快的原因:
1、基于内存
2、DAG的优化
运行模式:
1、Local
(1)Standalone (Master Worker) HA(zookeeper)
(2)client模式: Driver和Client在一台物理节点上面运行
(3)cluster模式:Driver会被资源调度的Master选一台空闲的物理节点去运行
2、Yarn (ResourceManager NodeManager) HA(zookeeper)
(1)Yarn-client
Driver和Client在一台物理节点上面运行
Driver会被资源调度的ResourceManager选一台空闲的物理节点去运行
(2)Yarn-cluster 如果代码里写有setMaster("local"),会报错!应该是setMaster("yarn-cluster")
3、 Mesos
RDD:Resilient Distributed Dataset
中文:弹性分布式数据集
就是数据一开始加载过来到内存,从RDD到RDD的转换其实是个抽象的概念,或者换句话说就是瞬时转变的状态
1,a list of partitions (partition是个物理概念,就是在一台物理节点里面的一片数据)
2,A computing function of each Split (我们说Split是读入数据的时候的概念,正常情况下从HDFS来读取,Split就相当于上面的partition,也就是 Block)
3,a list of dependencies 一组依赖
4,可选项,如果RDD里面的元素是元组key value格式,我们可以自己传入Partitioner
pairs.partitionBy(new HashPartitioner(3));
5,可选项,默认情况数据分片是有prefered locations(优先计算的位置),但是我们也可以指定
lines.repartition(3);
我们说举例,譬如说filter过后,一个partition剩下的数据量比较少了,那可以应用这个repartition让它合并
spark的操作算子详解和举例:http://homepage.cs.latrobe.edu.au/zhe/ZhenHeSparkRDDAPIExamples.html
action和transform操作:
伯克利大学spark论文内容介绍:
![[Spark进阶]-- spark-1.6.x-小结_spark_03](https://s2.51cto.com/images/blog/202211/03141222_63635bc61d4f414246.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
1、transformation 延迟操作:从RDD 到 RDD
2、Action立即执行:从RDD 到 结果或者存储,碰到Action就会封装一个Job sc.runJob(rdd)
缓存策略12种:http://tech.meituan.com/spark-tuning-basic.html
源码如下:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.spark.storage
import java.io.{Externalizable, IOException, ObjectInput, ObjectOutput}
import java.util.concurrent.ConcurrentHashMap
import org.apache.spark.annotation.DeveloperApi
import org.apache.spark.util.Utils
/**
* :: DeveloperApi ::
* Flags for controlling the storage of an RDD. Each StorageLevel records whether to use memory,
* or ExternalBlockStore, whether to drop the RDD to disk if it falls out of memory or
* ExternalBlockStore, whether to keep the data in memory in a serialized format, and whether
* to replicate the RDD partitions on multiple nodes.
*
* The [[org.apache.spark.storage.StorageLevel$]] singleton object contains some static constants
* for commonly useful storage levels. To create your own storage level object, use the
* factory method of the singleton object (`StorageLevel(...)`).
*/
@DeveloperApi
class StorageLevel private(
private var _useDisk: Boolean, //使用磁盘
private var _useMemory: Boolean, //使用内存
private var _useOffHeap: Boolean, //使用堆内存
private var _deserialized: Boolean, //反序列化
private var _replication: Int = 1) //副本数量,默认是1
extends Externalizable {
// TODO: Also add fields for caching priority, dataset ID, and flushing.
private def this(flags: Int, replication: Int) {
this((flags & 8) != 0, (flags & 4) != 0, (flags & 2) != 0, (flags & 1) != 0, replication)
}
def this() = this(false, true, false, false) // For deserialization
def useDisk: Boolean = _useDisk
def useMemory: Boolean = _useMemory
def useOffHeap: Boolean = _useOffHeap
def deserialized: Boolean = _deserialized
def replication: Int = _replication
assert(replication < 40, "Replication restricted to be less than 40 for calculating hash codes")
if (useOffHeap) {
require(!useDisk, "Off-heap storage level does not support using disk")
require(!useMemory, "Off-heap storage level does not support using heap memory")
require(!deserialized, "Off-heap storage level does not support deserialized storage")
require(replication == 1, "Off-heap storage level does not support multiple replication")
}
override def clone(): StorageLevel = {
new StorageLevel(useDisk, useMemory, useOffHeap, deserialized, replication)
}
override def equals(other: Any): Boolean = other match {
case s: StorageLevel =>
s.useDisk == useDisk &&
s.useMemory == useMemory &&
s.useOffHeap == useOffHeap &&
s.deserialized == deserialized &&
s.replication == replication
case _ =>
false
}
def isValid: Boolean = (useMemory || useDisk || useOffHeap) && (replication > 0)
def toInt: Int = {
var ret = 0
if (_useDisk) {
ret |= 8
}
if (_useMemory) {
ret |= 4
}
if (_useOffHeap) {
ret |= 2
}
if (_deserialized) {
ret |= 1
}
ret
}
override def writeExternal(out: ObjectOutput): Unit = Utils.tryOrIOException {
out.writeByte(toInt)
out.writeByte(_replication)
}
override def readExternal(in: ObjectInput): Unit = Utils.tryOrIOException {
val flags = in.readByte()
_useDisk = (flags & 8) != 0
_useMemory = (flags & 4) != 0
_useOffHeap = (flags & 2) != 0
_deserialized = (flags & 1) != 0
_replication = in.readByte()
}
@throws(classOf[IOException])
private def readResolve(): Object = StorageLevel.getCachedStorageLevel(this)
override def toString: String = {
s"StorageLevel($useDisk, $useMemory, $useOffHeap, $deserialized, $replication)"
}
override def hashCode(): Int = toInt * 41 + replication
def description: String = {
var result = ""
result += (if (useDisk) "Disk " else "")
result += (if (useMemory) "Memory " else "")
result += (if (useOffHeap) "ExternalBlockStore " else "")
result += (if (deserialized) "Deserialized " else "Serialized ")
result += s"${replication}x Replicated"
result
}
}
/**
* Various [[org.apache.spark.storage.StorageLevel]] defined and utility functions for creating
* new storage levels.
*/
object StorageLevel {
val NONE = new StorageLevel(false, false, false, false)
/**使用未序列化的Java对象格式,将数据全部写入磁盘文件中*/
val DISK_ONLY = new StorageLevel(true, false, false, false)
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
/**使用未序列化的Java对象格式,将数据保存在内存中。如果内存不够存放所有的数据,则数据可能就不会进行持久化。
那么下次对这个RDD执行算子操作时,那些没有被持久化的数据,需要从源头处重新计算一遍。
这是默认的持久化策略,使用cache()方法时,实际就是使用的这种持久化策略**/
val MEMORY_ONLY = new StorageLevel(false, true, false, true)
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
/**基本含义同MEMORY_ONLY。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。
这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC**/
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
/**使用未序列化的Java对象格式,优先尝试将数据保存在内存中。如果内存不够存放所有的数据,会将数据写入磁盘文件中,
下次对这个RDD执行算子时,持久化在磁盘文件中的数据会被读取出来使用。**/
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
/**基本含义同MEMORY_AND_DISK。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。
这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。**/
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
/**关闭堆内存:使用Tachyon分布式内存*/
val OFF_HEAP = new StorageLevel(false, false, true, false)
/**
* :: DeveloperApi ::
* Return the StorageLevel object with the specified name.
*/
@DeveloperApi
def fromString(s: String): StorageLevel = s match {
case "NONE" => NONE
case "DISK_ONLY" => DISK_ONLY
case "DISK_ONLY_2" => DISK_ONLY_2
case "MEMORY_ONLY" => MEMORY_ONLY
case "MEMORY_ONLY_2" => MEMORY_ONLY_2
case "MEMORY_ONLY_SER" => MEMORY_ONLY_SER
case "MEMORY_ONLY_SER_2" => MEMORY_ONLY_SER_2
case "MEMORY_AND_DISK" => MEMORY_AND_DISK
case "MEMORY_AND_DISK_2" => MEMORY_AND_DISK_2
case "MEMORY_AND_DISK_SER" => MEMORY_AND_DISK_SER
case "MEMORY_AND_DISK_SER_2" => MEMORY_AND_DISK_SER_2
case "OFF_HEAP" => OFF_HEAP
case _ => throw new IllegalArgumentException(s"Invalid StorageLevel: $s")
}
/**
* :: DeveloperApi ::
* Create a new StorageLevel object without setting useOffHeap.
*/
@DeveloperApi
def apply(
useDisk: Boolean,
useMemory: Boolean,
useOffHeap: Boolean,
deserialized: Boolean,
replication: Int): StorageLevel = {
getCachedStorageLevel(
new StorageLevel(useDisk, useMemory, useOffHeap, deserialized, replication))
}
/**
* :: DeveloperApi ::
* Create a new StorageLevel object.
*/
@DeveloperApi
def apply(
useDisk: Boolean,
useMemory: Boolean,
deserialized: Boolean,
replication: Int = 1): StorageLevel = {
getCachedStorageLevel(new StorageLevel(useDisk, useMemory, false, deserialized, replication))
}
/**
* :: DeveloperApi ::
* Create a new StorageLevel object from its integer representation.
*/
@DeveloperApi
def apply(flags: Int, replication: Int): StorageLevel = {
getCachedStorageLevel(new StorageLevel(flags, replication))
}
/**
* :: DeveloperApi ::
* Read StorageLevel object from ObjectInput stream.
*/
@DeveloperApi
def apply(in: ObjectInput): StorageLevel = {
val obj = new StorageLevel()
obj.readExternal(in)
getCachedStorageLevel(obj)
}
private[spark] val storageLevelCache = new ConcurrentHashMap[StorageLevel, StorageLevel]()
private[spark] def getCachedStorageLevel(level: StorageLevel): StorageLevel = {
storageLevelCache.putIfAbsent(level, level)
storageLevelCache.get(level)
}
}
StorageLevel.NONE
StorageLevel.MEMORY_ONLY (默认的缓存策略)
...
cache() = persist() = persist(StorageLevel.MEMORY_ONLY)
StorageLevel.MEMORY_ONLY 它是只存在内存,而且如果内存不够,直接不存了,那读取呢?一部分从内存读,一部分从原始文件读
StorageLevel.MEMORY_AND_DISK 首先尽量往内存存储,如果内存不够,存在数据所在的本地磁盘!
_ser 做下序列化
_2 有个副本
如何选择RDD的持久化策略
1. cache() MEMORY_ONLY
2. MEMORY_ONLY_SER
3. _2
4. 能内存不使用磁盘
如果RDD是成本很高很耗时才算出来的,我们可以StorageLevel.MEMORY_AND_DISK,当然更多选择的doCheckpoint()
但是一旦checkpoint则会封装Job!!!!
容错机制:
1, 默认的容错重新计算,那这个地方,从新计算RDD,它并不是说所有的Partition都参与,哪个计算错,哪个就重新计算
2, Lineage(是相对RDD来说的)如果很长,可以用persist() 内存
3, doCheckpoint 磁盘 SparkContext.setCheckPointDir("hdfs://...")
宽窄依赖为什么要有?------》切分stage
什么是宽依赖?
宽依赖就是父RDD里面的partition会去向子RDD里面的多个partitions,多机到多机的数据传输(shuffle),我们就称之为宽依赖,除外都是窄依赖
注意:宽窄依赖spark内核引擎主要是根据我们的代码里面的算子来定的
map
filter
union
join
groupBy
任 务调度
Application <--> Driver program (main) <--> new SparkContext <--> new DAGScheduler , new TaskSheduler ( SparkDeployBackend)
Job (根据Action来划分)
Stage (根据宽窄依赖来划分)
Task (根据stage内部的partition的数量来决定)
伯克利大学spark论文原图如下:
![[Spark进阶]-- spark-1.6.x-小结_数据_04](https://s2.51cto.com/images/blog/202211/03141222_63635bc641d6a70303.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
![[Spark进阶]-- spark-1.6.x-小结_Boo_05](https://s2.51cto.com/images/blog/202211/03141222_63635bc66a59135440.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
![[Spark进阶]-- spark-1.6.x-小结_spark_06](https://s2.51cto.com/images/blog/202211/03141222_63635bc68c7ae57490.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
Cluster
Worker Node
Executor 进程 <--> Port <--> JVM 里面有HEAP堆内存
Thread Pool <--> 每个Thread 跑一个或多个Task
如图:
![[Spark进阶]-- spark-1.6.x-小结_Boo_07](https://s2.51cto.com/images/blog/202211/03141222_63635bc6affbf88062.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
任务的调度详解:
1、最开始的时候得把集群启动好,换句话说就是worker node哪些节点注册到Master上面来
我们开发好程序打个JAR上去运行,spark-submit提交任务!
2、开始运行,Driver先会运行,启动DAGScheduler 启动TaskScheduler
当我们taskScheduler.start()运行的时候,它会去找前面的Master资源的主节点要资源
3、Master就会去集群上面找空闲的Worker让Worker启动起来需要的Executor,申请的所谓的资源就是Executors,接着Executors会反向注册到TaskScheduler,这是TaskScheduler手头上就有了可用的资源列表
4、接下来DAGScheduler开始画DAG图,切分Stage,封装Task,封装TaskSet,发给TaskScheduler
5、TaskScheduler把里面的Task拿出来找个手头的executor去运行,如果Task发到了Executor上面去,会到里面被封装为TaskRunner,会从线程池里面拿一个Thread去执行
6、如果执行完毕,Results结果会被返回到TaskScheduler上面来,所以我们的Driver在哪里,我们的结果就在哪里!
那我们说如果这个Task任务失败了,TaskScheduler重新发送,重新去计算,如果出现卡了,我们叫struggling task,如果要解决这类问题,我们可以事先开启竞争模式,所谓的竞争模式就是找个备胎,speculation设置为True就OK(在spark配置文件中设置"spark.speculation=true"。这主要和Akka的故障探测器有关)
如果Task反复重试都失败,就认为TaskSet失败也就是对应着一个Stage失败,就会反馈给DAGScheduler,那接下来就还是重试Stage,如果stage多次重试
失败,那么就认为JOB失败了,那重试Job,如果Job反复失败了,就认为Application运行出错了
什么是DAG的优化?
1、就是首先根据宽窄依赖切分出了stages,如果是窄依赖就不再划分stage,这个是优化的前提
2、根据一个partition会对应一个Task,做出来优化,这种在一个stage内部一个partition对应一个Task我们叫做pipeline
而这种pipeline的效果就是1+1+...+1 = N
反过来说如果没有这种优化,就好比 1+1=2; 2+1=3; 每次都是封装一个Task,Task本身的信息占网络传输
3、其次每次Task的输出都要记录位置,下次的时候还得再来读
Spark Core---------------->代码举例
本质上就是在操作一个个RDD
Join操作和Cogroup操作
JoinAndCogroup.java
package com.day.java;
import java.util.ArrayList;
import java.util.List;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;
/**
*
* @author root
* java测试join和cogroup区别
* join:相当于排列组合
* cogroup:合并相同key的value
*/
public class JoinAndCoGroupjav {
public static void main(String []args){
//创建Conf
SparkConf conf=new SparkConf().setAppName("JoingAndCoGroup").setMaster("local");
JavaSparkContext sc=new JavaSparkContext(conf);
//创建2个元组List集合
List> stu=new ArrayList>(); //姓名和id
List> core=new ArrayList>();//id和分数
//存放学生数据
stu.add(new Tuple2(1,"tom"));
stu.add(new Tuple2(2,"jim"));
stu.add(new Tuple2(3,"cassie"));
//存放分数数据
core.add(new Tuple2(1,80));
core.add(new Tuple2(1,30));
core.add(new Tuple2(2,90));
core.add(new Tuple2(2,92));
core.add(new Tuple2(2,90));
core.add(new Tuple2(3,80));
core.add(new Tuple2(3,86));
core.add(new Tuple2(3,87));
//进行paris
JavaPairRDD stuRDD=sc.parallelizePairs(stu);
JavaPairRDD coreRDD=sc.parallelizePairs(core);
//测试使用JavaPairRDD接收并按join方式合并
// JavaPairRDD> stuTupl=stuRDD.join(coreRDD);
//遍历合并结果
/*stuTupl.foreach(new VoidFunction>>() {
private static final long serialVersionUID = 1L;
@Override
public void call(Tuple2> t)
throws Exception {
System.out.println("id is "+t._1);
System.out.println("name is "+t._2._1);
System.out.println("score is "+t._2._2);
}
});*/
//测试使用JavaPairRDD接收并按cogroup方式合并
JavaPairRDD,Iterable>> coreTupl=stuRDD.cogroup(coreRDD);
//遍历并且获取结果
coreTupl.foreach(new VoidFunction,Iterable>>>(){
@Override
public void call(
Tuple2, Iterable>> t)
throws Exception {
System.out.println("============");
System.out.println("id is "+t._1());
System.out.println("name is "+t._2._1);
System.out.println("score is "+t._2._2);
}
});
sc.close();
}
}
Join
(1,"cassie") (2,"jack")
(1,100) (2,90) (1,101) (2,91)
(1,"cassie",100) (1,"cassie",101) (2,"jack",90) (2,"jack",91)
Cogroup
(1,"cassie") (2,"jack")
(1,100) (2,90) (1,101) (2,91)
(1,("cassie"),(100,101)) (2,("jack"),(90,91))
广播变量
本质上是把这个变量给广播到所有的Worker上,这就避免了同一份数据被多次拷贝到Tasks里面去,
如果是final int f=3也就罢了,但如果是一个词表,那最好就广播出去
BroadCastValue.java
package com.day.java;
import java.util.Arrays;
import java.util.List;
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;
/**
*
* @author root
* java测试第一类广播变量:不可改变值,可读的广播变量
*/
public class BroadLCastValueJava {
public static void main(String []args){
SparkConf conf=new SparkConf().setAppName("BroadCast").setMaster("local[1]");
//创建conf
JavaSparkContext sc=new JavaSparkContext(conf);
//创建第一类广播变量
final Broadcast broadcast=sc.broadcast(1);
//创建list
List list=Arrays.asList(1,2,3,6);
//转换为JavaRDD
JavaRDD broadRDD=sc.parallelize(list);
//遍历
JavaRDD results = broadRDD.map(new Function() {
@Override
public Integer call(Integer v1) throws Exception {
return v1*2;
}
});
results.foreach(new VoidFunction(){
@Override
public void call(Integer t) throws Exception {
System.out.println("结果:"+t);
}});
sc.close();
}
}
final Broadcast<Integer> broadCastFactor = sc.broadcast(f);
broadCastFactor.value()
值得注意的一点就是,这个广播变量是只读的,不能更改
累加器
Accumulator
AccumulatorValue.java
package com.day.java;
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.VoidFunction;
/**
*
* @author root
*广播变量的第二种模式:可以修改值,即累加器
*/
public class AccumulatorValue {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("AccumulatorValue").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);
//定义广播变量
final Accumulator sum = sc.accumulator(0);
List list = Arrays.asList(1,2,3,4,5);
JavaRDD listRDD = sc.parallelize(list);
listRDD.foreach(new VoidFunction() {
private static final long serialVersionUID = 1L;
@Override
public void call(Integer value) throws Exception {
//累加变量
sum.add(value);
System.out.println(sum.value());
}
});
System.out.println(sum.value());
sc.close();
}
}
final Accumulator<Integer> sum = sc.accumulator(0);
使用的时候
sum.add(value);
值得注意的一点是,在task里面对代码来说就是在重写的Call方法里面是获取不到的
只能最后在Driver程序中查看
sum.value()
TopN的操作
TopN.java
package com.day.java;
import java.util.List;
import org.apache.spark.HashPartitioner;
import org.apache.spark.Partitioner;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
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.PairFunction;
import scala.Tuple2;
/**
*
* @author root
* 获取文件top.txt中的前三个最高分数
*
* 9
7
6
*
*/
public class TopN {
public static void main(String[] args) {
SparkConf conf = new SparkConf().setAppName("TopN").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD lines = sc.textFile("top.txt");
//也可以直接重新分区,合并分区
lines.repartition(3);
//将单列变为2列的元组()
JavaPairRDD pairs = lines.mapToPair(new PairFunction() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2 call(String line) throws Exception {
return new Tuple2(Integer.valueOf(line),line);
}
});
//如果使用rdd的第4大特性,需要RDD是k-v格式
// pairs.partitionBy(new HashPartitioner(3));
JavaPairRDD sorted = pairs.sortByKey(false);
//获取结果值
JavaRDD results = sorted.map(new Function, String>() {
private static final long serialVersionUID = 1L;
@Override
public String call(Tuple2 tuple) throws Exception {
return tuple._2;
}
});
//通过take方法获取前3个数
List list = results.take(3);
for(String s : list){
System.out.println(s);
}
sc.close();
}
}
top.txt
首先将文件读入为JavaRDD<String>
其次是把单列值变成了Tuple元组JavaPairRDD<Integer, String>
接着排序sortByKey(false)
取topN无非就是用一下take()方法
分组取TopN
GroupTopN.java
score.txt
把数据文件读入成为JavaRDD<String>
因为我们后面要去做分组,所以呢我们先变成键值对JavaPairRDD<String, Integer>
利用groupByKey()把相同key的给凑一堆儿去JavaPairRDD<String, Iterable<Integer>>
在接下来的mapToPair方法里面我们并没有改变它的值,也没有改变它的格式以及类型,
我们只是在里面用冒泡算法给做了小排序取里面的top3
最后foreach把它打印出来
二次排序
SecondSortKey.java
sort.txt
首先定义需要排序的列,为需要排序的列提供getter和setter和hashcode和equals方法
同时需要一个构造器,因为外部会去new这个东西
重新$greater, $greater$eq, $less, $less$eq, compare, compareTo
当定义好二次排序所需要的键之后呢,我们就是需要通过transformation转换new出来我们的自定义key,
然后通过JavaPairRDD<SecondSortKey, String> sortedPairs = pairs.sortByKey(false);
排完序之后,key不是我们关系,我们需要把值再抽取出来
【自定义的key需要去实现一些个接口(extends Ordered[SecondSortKey] with Serializable)】
Spark SQL
可以兼容Hive,读取Hive里面的表数据,可以从Hive/JDBC/JSON数据源读取数据过来直接变成DataFrame
里面的最佳拍档是DataFrame,数据框,"表"
传入一条SQL语句过去-->首先通过sql parser分析SQL语句-->接着会有Analyzer来生成逻辑执行计划,Logical Plan-->会通过optimizer来优化这个逻辑执行
计划,spark SQL牛的地方就是这个optimizer做的很不错,join where --> spark planner这个组件去生成物理执行计划
另外呢还有个钨丝计划,它会在底层使用内存的上面进行优化
我们如何理解DataFrame的存储呢?如果是一个RDD,里面存的是一个个的Person对象,相反DataFrame是列存储,相同列的数据会被存储在一起,当然还是
指的内存中,所以DataFrame的性能呢要比这个RDD好很多倍!DataSet会未来Spark SQL小组去重点研究的集合,会有这种数据集的API出现,性能会比DF更好
创建DataFrame
DataFrameCreate.java
students.json
我们通过sqlContext.read().json()读进来
show();方法可以打印出来
DataFrame DSL 操作
DataFrameOperation.java
students.json
打印元数据
根据列查询
还可以查询同时并计算
过滤
还可以根据某列分组再count计数
这个地方讲过之后可以把之前wc改写通过DataFrame来实现
读取HDFS过来,生成的是RDD,之后构建DF
DataFrame两种方式构建
在使用Spark SQL之前我们肯定需要SQLContext(sc)
RDD2DataFrameReflection.java
students.txt
通过反射,java的版本需要java bean,scala的版本需要一个case class
把JavaRDD<T> 和Java Bean传进来就可以了,sqlContext.createDataFrame(students, T.class);
RDD2DataFrameDynamic.java
通过动态构建StructType来动态生成这个DataFrame,StructType里面需要的StructField
可以通过RowFactory.create来创建Row,把JavaRDD<Row> 和 structType传进来对不对,sqlContext.createDataFrame(rows, structType);
我们可以把获取到DataFrame注册为一张临时表registerTempTable
最后就可以通过sqlContext.sql()
读取默认数据源
JSONDataSource.java
students.json
读入sqlContext.read().json文件进来,因为是默认格式,所以直接就是DataFrame,然后就可以注册临时表registerTempTable
然后就可以进行过去,这里的案例是把学生分数大于80的选出来,接着把DataFrame转化为了RDD
下面呢就是读入了另外一个json文件然后注册临时表,这里最重要的就是拼写了一个SQL语句,在SQL语句里面就用到了in关键字,去过滤好学生的数据
sqlContext.sql执行后就把好学生的info信息就查询出来
一个studentScoreRDD和studentInfoRDD直接的join操作,得到好学生的分数以及信息,这个时候要想存回为一个JOSN文件,我们还要转回为DataFrame
JavaRDD<> --> JavaRDD<Row> --> DataFrame
最终存储df.write().format("json").save("goodStudentJson");
JDBCDataSource.java
这里JDBC重要的是Map<String,String> options = new HashMap<String,String>();里面需要传数据库的url,还要表名dbtable,
sqlContext.read().format("jdbc").options(options).load();
读进来时两个DF,DF转为RDD,一个studentScoreRDD和studentInfoRDD直接的join操作,还是一个RDD,转为JavaRDD<Row>,用RDD里面的filter进行过滤,
然后可以foreach可以把每个元素进行存储,当然这是需要DriverManager.getConnection("jdbc:mysql://spark001:3306/test", "root", "123123");
./bin/spark-submit --master local --class com.spark.study.sql.JDBCDataSource --driver-class-path ./lib/mysql-connector-java-5.1.32-bin.jar sparksql.jar
HiveDataSource.java
需要HiveContext
需要首先在HIVE里面准备两张表的数据
hiveContext.sql("LOAD DATA LOCAL INPATH '/usr/hadoopsoft/spark-1.6.1-bin-hadoop2.6/student_infos.txt' "
+ "INTO TABLE student_infos");
hiveContext.sql("LOAD DATA LOCAL INPATH '/usr/hadoopsoft/spark-1.6.1-bin-hadoop2.6/student_scores.txt' "
+ "INTO TABLE student_scores");
hiveContext.sql传入SQL语句关联两张表,查询成绩大于80分的学生,返回的是一个DataFrame
接着就是saveAsTable存回HIVE里面去,Bingo!!
SaveMode存储的时候可以选择不同的策略
开窗函数
这里我们通过HiveContext去取数据
hiveContext.sql("LOAD DATA "
+ "LOCAL INPATH '/usr/hadoopsoft/spark-1.5.0-bin-hadoop2.4/sales.txt' "
+ "INTO TABLE sales");
iphone7 cellphone 1000000
...
在hiveContext.sql里面首先有个子查询,row_number() OVER (PARTITION BY category ORDER BY revenue DESC) rank
row_number()开窗函数的作用是把分组后,里面每一组的数据从开头到结尾编号,从1开始到N,如果像案例里面的DESC,那就是最大的那个值为编号1
编号在哪里?就在rank里!
在外面的查询就可以根据子查询的数据来进行过滤,根据刚刚的rank一列进行过滤,顺便product,category,revenue也都有了
(分组取TopN可以在这里用开窗函数实现)
Spark SQL内建的函数
DailySale.scala
按日期分组,把总销售额给统计出来
加载文件数据,是一个RDD,filter方法过滤脏数据,变为RDD<Row>,构建StructType,sqlContext.createDataFrame构建DataFrame,
// 这里着重说明一下!!!
// 要使用Spark SQL的内置函数,就必须在这里导入SQLContext下的隐式转换
import sqlContext.implicits._
groupBy 分组
agg 聚合
聚合函数里面可以传内建的计算函数比如sum
DailyUV.scala
按日期分组,把user view给统计出来,user view你会了,page view会不会?user view无法就是需要一个distinct()去重步骤
groupBy
agg
countDistinct
上面我们wc可以通过Spark sql来重写,这里我们可以通过Spark SQL里面的内建函数来重写
UDF
文件 --> RDD --> RDD[Row] --> DataFrame --> registerTempTable
sqlContext.udf.register("strLen", (str : String)=> str.length())
使用刚刚注册的UDF函数,无法就是sqlContext.sql("select name, strLen(name) from names")
UDAF
定义了一个类StringCount去继承了UserDefinedAggregateFunction
sqlContext.udf.register("strCount", new StringCount)
sqlContext.sql("select name, strCount(name) from names group by name")