回顾
Spark是一个内存计算框架
在MR基础上做一个扩展
- RDD resilient distributed dataset
- transformation:变换 lazy map() filter
- action 动作 count first take(n)
- rdd.persist() 持久化
- rdd. cache() == rdd.persist();
- SparkContext : 到Spark集群的链接 SparkConf
- local //本地模式,通过线程模拟
- spark-shell --master local[4]
- val rdd = sc.textFile(“file:///x/x/”,n);//n:并发程度
- rdd.map
- rdd.flatMap() //压扁操作
压扁操作 :把集合打开 元素释放出来
x to 5
非压扁操作
每一个都生成一个集合,一共五个集合
flatMap压扁操作
伪集合操作
1 两个集合
rdd1 = tom tom tpom tomaslee
rdd2 = tom tomaslee bob
0 reduce
def reduce(f: (T, T) ⇒ T): T
根据映射函数f,对RDD中的元素进行二元计算,返回计算结果。
scala> var rdd1 = sc.makeRDD(1 to 10,2)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[36] at makeRDD at :21
scala> rdd1.reduce(_ + _)
res18: Int = 55
scala> var rdd2 = sc.makeRDD(Array(("A",0),("A",2),("B",1),("B",2),("C",1)))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[38] at makeRDD at :21
scala> rdd2.reduce((x,y) => {
| (x._1 + y._1,x._2 + y._2)
| })
res21: (String, Int) = (CBBAA,6)
2 去除重复
rdd.distinct()//产生驱虫的新的rdd
3 collect 转换成数组
def collect(): Array[T]
collect用于将一个RDD转换成数组。
scala> var rdd1 = sc.makeRDD(1 to 10,2)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[36] at makeRDD at :21
scala> rdd1.collect
res23: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
4 union联合
5 交集 intersection
6 差集 减法
7 笛卡尔积
8 fold
def fold(zeroValue: T)(op: (T, T) ⇒ T): T
fold是aggregate的简化,将aggregate中的seqOp和combOp使用同一个函数op。
scala> rdd1.fold(1)(
| (x,y) => x + y
| )
res19: Int = 58
##结果同上面使用aggregate的第一个例子一样,即:
scala> rdd1.aggregate(1)(
| {(x,y) => x + y},
| {(a,b) => a + b}
| )
res20: Int = 58
rdd.aggregate(value)(seqOp, combOp)
刚才说到reduce()和fold(),这两个函数有一个问题,那就是它们的返回值必须与rdd的数据类型相同,啥意思呢?比如刚才那个例子,l的数据是Int,那么reduce()和flod()返回的也必须是Int。
aggregate()函数就打破了这个限制。比如我返回(Int, Int)。这很有用,比如我要计算平均值的时候。
要算平均值,我就有两个值是要求的,一个是rdd的各元素的累加和,另一个是元素计数,我初始化为(0, 0)。
那么就是
val l = List(1,2,3,4)
l.aggregate(0, 0)(seqOp, combOp)
那么seqOp和combOp怎么写呢?而combOp又是啥意思呢?
我们将seqOp写为
(x, y) => (x._1 + y, x._2 + 1)
val l = List(1,2,3,4)
l.reduce((x, y) => x + y)
对于这个x,它代指的是返回值,而y是对rdd各元素的遍历。
在aggregate()这也一样,x不是返回值吗,我返回值是(Int, Int)啊,它有两个元素啊,我可以用x._1和x._2来代指这两个元素的,y不是rdd的元素遍历吗,那我x._1 + y就是各个元素的累加和啊,x._2 + 1就是元素计数啊。遍历完成后返回的(Int, Int)就是累加和和元素计数啊。
按理说有这么一个函数就应该结束了,后边那个combOp是干嘛的?
因为我们的计算是分布式计算,这个函数是将累加器进行合并的。
例如第一个节点遍历1和2, 返回的是(3, 2),第二个节点遍历3和4, 返回的是(7, 2),那么将它们合并的话就是3 + 7, 2 + 2,用程序写就是
(x, y) => (x._1 + y._1, x._2 + y._2)
val l = List(1,2,3,4)
r = l.aggregate(0, 0)((x, y) => (x._1 + y, x._2 + 1), (x, y) => (x._1 + y._1, x._2 + y._2))
m = r._1 / r._2.toFload
action
1 count
2 collect
3 countByvalue
4 teke()
提取前n个元素,数组
5 first
提取第一个元素,一个值
6 top()
提取末尾的n个元素
rdd.top(2) ==> 3,3
7 rdd.takeOrdered(3)
1 2 3
8 rdd.takeSample
9 fold()
10 .aggreate() == > (9,4)
rdd.aggregate((0,0))((x , y ))......
11 foreach()
rdd.foreach(println)
mean () //均值
variance() //方差
持久化
persist() //
1 Spark默认持久化对象到jvm heap中没有串行化
2 如果是off-heap或者磁盘存储必须是串行化的
3 串行化级别
rdd.persist(StrongeLevel.DISK_ONLY); //存放到磁盘
操纵key-value
ETL(extract, transform, and load)
-----------抽取-------变换-------------加载---------------------------------
1 创建pairRDD
rdd.map(x=>(xxx,yyy))
2 pairRDD操作
准备
a.prdd.reduceByKey((x,y)=>x + y) // 按照key分组操作,key不变 ,value运算。
//结果:
{(1,2),(3,10)}
b.prdd.groupByKey().collect
//结果
Array((1,CompactBuffer(2)),(3,CompactBuffer(4,6)))
4 rdd.mapValues()
rdd.mapValues(x=>x + 1) ===> {(1,3),(3,5),(3,7)}
5 rdd.flatMapValues()
将值压扁,在和key组合成新的pair
6 keys
取出每个新的pair的key,形成key集合
prdd。ksys()
7 values
rdd4.values.collect
8 sortByKey()
按照value排序
转换key value
Aggregations:聚合
单词统计
1 准备txt文件
2 压扁
x:每一行通过空格压扁
3 映射 每个(单词,1)
4 Reduce
总写

java写

=============================================================================
countByValue
//直接结算个数,按照key进行累加
rdd2.countByValue() === rdd2.map(x=>(x,1)).reduceByKey((x,y)=> x + y)
combineByKey
//paste:进入复制模式
调整并发程度
线程分配
===============================================================
1 制定分区数
rdd.reduceByKey(f:op,numParitions : Int){..}
2 检查分区数
val = rdd.partitions.size
3 重新指定分区数目
rdd.reparitions(4,true)//不推荐,等价于hadoop:job.setNumReduceTasks(4);
rdd.coalesce(4) // 推荐
对数据排列
PariRDD可用的action
====================================================================
1 准备数据
val rdd = sc.parallelize(Array((1,2),(3,4),(5,6)))
2 countByKey
//对每一个key统计个数,形成tuple
rdd.countByKey(); //(1,1),(3,2)
3 collectAsMap
//将tuple转换成map<Key-value>
rdd.collectAsMap() //1-->2,3-->6
3 lookup(3)
//找出所有同key的value
rdd.lookup(3)//WrappedArry(4,6)
数据分区
数据分区:
在分布式集群里,网络通信的代价很大,减少网络传输可以极大提升性能。
mapreduce框架的性能开支主要在io和网络传输,io因为要大量读写文件,它是不可避免的,但是网络传输是可以避免的,把大文件压缩变小文件,从而减少网络传输,但是增加了cpu的计算负载。
spark里面io也是不可避免的,但是网络传输spark里面进行了优化:
spark把rdd进行分区(分片),放在集群上并行计算。
同一个rdd分片100个,10个节点,平均一个节点10个分区
当进行sum型的计算的时候,先进行每个分区的sum,然后把sum值shuffle传输到主程序进行全局sum,所以进行sum型计算对网络传输非常小。
但对于进行join型的计算的时候,需要把数据本身进行shuffle,网络开销很大。
spark是如何优化这个问题的呢?
spark把key-value rdd通过key的hashcode进行分区,而且保证相同的key存储在同一个节点上,这样对改rdd进行key聚合时,就不需要shuffle过程
我们进行mapreduce计算的时候为什么要尽兴shuffle?,就是说mapreduce里面网络传输主要在shuffle阶段,shuffle的根本原因是相同的key存在不同的节点上,按key进行聚合的时候不得不进行shuffle。shuffle是非常影响网络的,它要把所有的数据混在一起走网络,然后它才能把相同的key走到一起。要尽兴shuffle是存储决定的。
spark从这个教训中得到启发,spark会把key进行分区,也就是key的hashcode进行分区,相同的key,hashcode肯定是一样的,所以它进行分区的时候100t的数据分成10分,每部分10个t,它能确保相同的key肯定在一个分区里面,而且它能保证存储的时候相同的key能够存在同一个节点上。
比如一个rdd分成了100份,集群有10个节点,所以每个节点存10份,每一分称为每个分区,spark能保证相同的key存在同一个节点上,实际上相同的key存在同一个分区。
key的分布不均决定了有的分区大有的分区小。没法分区保证完全相等,但它会保证在一个接近的范围。
所以mapreduce里面做的某些工作里边,spark就不需要shuffle了,spark解决网络传输这块的根本原理就是这个。
进行join的时候是两个表,不可能把两个表都分区好,通常情况下是把用的频繁的大表事先进行分区,小表进行关联它的时候小表进行shuffle过程。
大表不需要shuffle。
模版是:
val userData = sc.sequenceFileUserID,UserInfo
.partitionBy(new HashPartition(100))//构造100个分区
.persist()
从分区中获益的操作:cogroup(), groupwith(),join(),leftOuterJoin(),rightOuterJoin(),groupByKey(),reduceByKey(),cobimeByKey(),lookup()
所有基于key的操作都会获益
对于诸如cogroup()和join()这样的二元操作,预先进行数据分区会让其中至少一个rdd(使用已知分区器的那个rdd)不发生数据shuffle,如果两个rdd使用同样的分区方式,并且它们还缓存在同样的机器上(比如一个rdd是通过mapvalues()从另一个rdd中创建出来的,这两个rdd就会拥有相同的key和分区方式),或者其中rdd还没有被计算出来,那么跨界点的shuffle(数据混洗)不会发生了。
mapreduce一般要求本地网卡达到20兆!即便进行了压缩!
import org.apache.hadoop.hive.ql.exec.persistence.HybridHashTableContainer.HashPartition
import org.apache.hadoop.mapred.lib
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
import org.apache.spark.storage.StorageLevel
import org.apache.spark.HashPartitioner
/**
* Created by zengxiaosen on 16/9/23.
*/
object PartitionVisitCount {
/*
大表小表关联
*/
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("useUDF").setMaster("local")
val ss = SparkSession.builder().config(sparkConf).getOrCreate()
val sc = ss.sparkContext
val fileRDD = sc.textFile("/opt/tarballs/spark_kafka/beifengspark/src/main/scala/2015082818")
.filter(line=>line.length>0)
.map{
line =>
val arr = line.split("\t")
val date = arr(17).substring(0,10)
val guid = arr(5)
val url = arr(1)
val uid = arr(18)
(uid,(guid,url)) //key-value:tuple2
}.partitionBy(new HashPartitioner(10)) //采用了hashcode分片方式,分成了10份,十个分区,每个分区10分
/*
相同的key在同一个分区,在进行任务调用时候,大表不需要任何shuffle
只需要shuffle小表
*/
.persist(StorageLevel.DISK_ONLY)
/*
parallelize有两个参数,第一个是他的list,第二个是分区数
分区数可以不给,不给的情况下默认就是它的核数
*/
//比如里面存的是我的用户id
val rdd = sc.parallelize(List(1,2,3,4,5,6,7),10)
.map(i => (i+"", i+"str"))
fileRDD.join(rdd).foreach(println)
/*
如果fileRDD后面还会关联好多个其他的rdd1,rdd2。。。rddn
就要先把大的fileRDD进行分区
这样优化了网络传输
*/
}
}
分区器决策
1 获得rdd的分区器
val par:Option[Partitioner] = rdd.partitioner //获得分区器Option
par.isDefined() //判断分区器是否定义
val par0:Partition = par.get() //获得分区对象
2 分区有益的方法
cogroup(),groupWith(),jion(),leftOuterJoin(),rightOuterJoin()
groupByKey(),reduceBykey(),combineBykey(),lookup()
Rank
PageRank
自定义分区器
1 继承Partitioner
//显示完整导入类型
val rdd = sc…
val rdd2 = rdd.partitionBy(new TwoPartitioner()) //查看打印输出
rdd.groupByKey()