Spark
spark和hadoop的区别:
- hadoop磁盘IO开销大,延迟高、表达能力有限(需要转换为MapReduce)、在前一个任务执行完成之前,其他任务都无法开始,map 和 reduce过程,任务之间的衔接。
- spark计算模式也属于MapReduce,但不局限与map和reduce操作,还提供了多种数据集操作类型、提供内存计算,将中间结果放在内存中,对于迭代计算效率更高。
MapReduce | Spark |
---|---|
数据存储过程:磁盘HDFS文件系统的split | 使用内存构建弹性分布式数据集RDD对数据进行运算和cache |
编程范式:Map + Reduce | DAG:Transformation + Action |
计算中间结果落到磁盘,IO及序列化、反序列化代价大 | 计算中间结果在内存中维护,存取速度比磁盘高数个数量级 |
Task以进程的方式维护,需要数秒时间才能启动任务 | Task以线程的方式维护,对于小数据及读取延迟更小 |
配置日志服务器
sc.textFile("./NOTICE").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect
spark-submit
出现以下错误的原因:找不到主机对应的ip地址,而不是端口绑定不正确引起的。
修改hosts文件,将本机IP对应写在映射中。
19/09/18 12:30:43 WARN Utils: Service 'sparkWorker' could not bind on port 0. Attempting port 1.
Exception in thread "main" java.net.BindException: Cannot assign requested address: Service 'sparkWorker' failed after 16 retries (starting from 0)! Consider explicitly setting the appropriate port for the service 'sparkWorker' (for example spark.ui.port for SparkUI) to an available port or increasing spark.port.maxRetries.
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:433)
at sun.nio.ch.Net.bind(Net.java:425)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:223)
at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:127)
at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:501)
at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1218)
at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:506)
at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:491)
at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:965)
at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:210)
at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:353)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:408)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:455)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:140)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
at java.lang.Thread.run(Thread.java:748)
出现JAVA_HOME not set异常。可以在sbin目录下的spark-config.sh文件中加入如下配置:
export JAVA_HOME=XXX
一、RDD
-
创建RDD
(1)、从集合中创建RDD;
val rdd = sc.parallelize(Array(1,2,3,4,5))
makeRDD
def parallelize[T: ClassTag]( seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] def makeRDD[T: ClassTag]( seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] def makeRDD[T: ClassTag](seq: Seq[(T, Seq[String])]): RDD[T]
可以从上面看出makeRDD有两种实现,而且第一个makeRDD函数接收的参数和parallelize完全一致。其实第一种makeRDD函数实现是依赖了parallelize函数的实现,第二种makeRDD函数,它接收的参数类型是Seq[(T, Seq[String])],这个函数还为数据提供了位置信息,提示数据的分区位置。
(2)、从外部存储创建RDD;
val files = sc.textFile("hdfs://hadoop1:9000/RELEASE")
(3)、从其他RDD创建。
二、RDD的类型
1、数值型RDD RDD[Int]、 RDD[(Int,Int)] 、 RDD[(Int,(Int,Int))] RDD.scala
2、键值对RDD RDD[(Int,Int)] RDD[(Int,(Int,Int))] PairRDDFunctions.scala
[所有键值对RDD都可以使用数据型RDD的操作]
三、Transformation(RDD转换)
1、def map[U: ClassTag](f: T => U): RDD[U] 一对一转换
2、def filter(f: T => Boolean): RDD[T] 传入一个Boolean的方法,过滤数据
3、def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U] 一对多,并将多压平
4、def mapPartitions[U: ClassTag](f: Iterator[T] => Iterator[U], preservesPartitioning: Boolean = false): RDD[U] 对于一个分区中的所有数据执行一个函数,性能比map要高
5、def mapPartitionsWithIndex[U: ClassTag](f: (Int, Iterator[T]) => Iterator[U], preservesPartitioning: Boolean = false): RDD[U]
6、def sample(withReplacement: Boolean,fraction: Double,seed: Long = Utils.random.nextLong): RDD[T] 主要用于抽样
7、def union(other: RDD[T]): RDD[T] 联合一个RDD,返回组合的RDD
8、def intersection(other: RDD[T]): RDD[T] 求交集
9、def distinct(): RDD[T] 去重
10、def partitionBy(partitioner: Partitioner): RDD[(K, V)] 用提供的分区器分区
11、def reduceByKey(func: (V, V) => V): RDD[(K, V)] 根据Key进行聚合 预聚合。
12、def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])] 将key相同的value聚集在一起。
13、def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C,
numPartitions: Int): RDD[(K, C)]
14、def aggregateByKey[U: ClassTag](zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U,combOp: (U, U) => U): RDD[(K, U)] 是CombineByKey的简化版,可以通过zeroValue直接提供一个初始值。
15、def foldByKey(zeroValue: V, partitioner: Partitioner)(func: (V, V) => V): RDD[(K, V)] 该函数为aggregateByKey的简化版,seqOp和combOp一样,相同。
16、def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length) : RDD[(K, V)] 根据Key来进行排序,如果Key目前不支持排序,需要with Ordering接口,实现compare方法,告诉spark key的大小判定。
17、def sortBy[K]( f: (T) => K, ascending: Boolean = true, numPartitions: Int = this.partitions.length) (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] 根据f函数提供可以排序的key
18、def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] 连接两个RDD的数据。
JOIN : 只留下双方都有KEY
left JOIN: 留下左边RDD所有的数据
right JOIN: 留下右边RDD所有的数据
19、def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner) : RDD[(K, (Iterable[V], Iterable[W]))] 分别将相同key的数据聚集在一起。
20、def cartesian[U: ClassTag](other: RDD[U]): RDD[(T, U)] 做笛卡尔积。 n * m
21、def pipe(command: String): RDD[String] 执行外部脚本
22、def coalesce(numPartitions: Int, shuffle: Boolean = false,partitionCoalescer: Option[PartitionCoalescer] = Option.empty)(implicit ord: Ordering[T] = null)
: RDD[T] 缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。
23、def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] 重新分区。
24、def repartitionAndSortWithinPartitions(partitioner: Partitioner): RDD[(K, V)] 如果重新分区后需要排序,那么直接用这个。
25、def glom(): RDD[Array[T]] 将各个分区的数据形成一个数组,RDD[Array[T]]
26、def mapValues[U](f: V => U): RDD[(K, U)] 对于KV结构RDD,只处理value
27、def subtract(other: RDD[T]): RDD[T] 去掉和other重复的元素
-
combineByKey()计算各个学生的平均分
val rdd = sc.makeRDD(Array(("a",90),("b",80),("c",85),("a",75),("b",98),("c",78))) def combineByKey[C]( createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C, numPartitions: Int): RDD[(K, C)] rdd.combineByKey( v=> (v,1), (c:(Int,Int),v) => (c._1+v,c._2 + 1), (c1:(Int,Int),c2:(Int,Int)) => (c1._1 + c2._1, c1._2 + c2._2) ) rdd.aggregateByKey((0,0))((a:(Int,Int),b:Int) => (a._1+b,a._2+1),(a:(Int,Int),b:(Int,Int)) => (a._1+b._1,a._2+b._2)) //上述两个方法完成相同内容
四、RDD行动
1、def reduce(f: (T, T) => T): T 如果最后不是返回RDD,那么就是行动操作,
2、 collect() 将RDD的数据返回到driver层进行输出
3、def count(): Long 计算RDD的数据数量,并输出
4、first() 返回RDD的第一个元素
5、take(n) 返回RDD的前n个元素
6、takeSample 采样
7、takeOrdered(n) 返回排序后的前几个数据
8、aggregate (zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U) 聚合操作
9、fold(zeroValue)(func) aggregate的简化操作,seqOp和combOp相同
10、saveAsTextFile(path) path为HDFS相兼容的路径
11、saveAsSequenceFile(path) 将文件存储为SequenceFile
12、saveAsObjectFile(path) 将文件存储为ObjectFile
13、def countByKey(): Map[K, Long] 返回每个Key的数据的数量。
14、foreach 对每一个元素进行处理。
五、函数传递的问题
1、如果RDD的转换操作中使用到了class中的方法或者变量,那么该class需要支持序列化
2、如果通过局部变量的方式将class中的变量赋值为局部变量,那么不需要传递对象,比如最后一个方法。
六、RDD的依赖关系
宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle
窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用
七、RDD的执行切分
1、一个jar包就是一个Application
2、一个行动操作就是一个Job, 对应于Hadoop中的一个MapReduce任务
3、一个Job有很多Stage组成,划分Stage是从后往前划分,遇到宽依赖则将前面的所有转换换分为一个Stage
4、一个Stage有很多Task组成,一个分区被一个Task所处理,所有分区数也叫并行度。
八、RDD的持久化
1、缓存 : persist cache
(1)、cache就是MEMORY_ONLY的persist
(2)、persist可以提供存储级别
(3) 、缓存后可能会由于内存不足通过类似LRU算法删除内存的备份,所以,缓存操作不会清除血统关系。
2、检查点机制
1、检查点机制是通过将RDD保存到一个非易失存储来完成RDD的保存,一般选择HDFS,
2、通过sc.setCheckPointDir来指定检查点的保存路径,一个应用指定一次。
3、通过调用RDD的checkpoint方法来主动将RDD数据保存,读取时自动的,
4、通过检查点机制,RDD的血统关系被切断。
九、RDD的分区
1、分区主要面对KV结构数据,Spark内部提供了两个比较重要的分区器,Hash分区器和Range分区器。
2、hash分区主要通过key的hashcode来对分区数求余。可能会导致数据倾斜问题,Range分区是通过水塘抽样算法来讲数据均匀的分配到每个分区中。
3、自定义分区主要通过集成partitoner抽象类来实现,需要实现两个方法
class CustomerPartitioner(numberPartition:Int) extends Partitioner{
//返回分区的总数
override def numPartitions: Int = {
numberPartition
}
//根据传入的key放回分区的索引,具体分区规则在此定义
override def getPartition(key: Any): Int = {
key.toString.toInt % numberPartition
}
}
十、RDD的累加器
自定义累加器
1、需要继承AccumulatorV2抽象类,IN就是输入的数据类型, OUT是累加器输出的数据类型。
2、怎么用?
1、创建一个累加器的实例
2、通过sc.register()注册一个累加器
3、通过 累加器实例名.add 来添加数据
4、通过 累加器实例名.value 来获取累加器的值
3、最好不要在转换操作中访问累加器,最好在行动操作中访问。
4、转换或者行动操作中不能访问累加器的值,只能添加add
class CustomerAcc extends AccumulatorV2[String,mutable.HashMap[String,Int]]{
private val _hashAcc = new mutable.HashMap[String,Int]()
//检测是否为空
override def isZero: Boolean = {
_hashAcc.isEmpty
}
//拷贝一个新的累加器
override def copy(): AccumulatorV2[String, mutable.HashMap[String, Int]] = {
val newAcc = new CustomerAcc()
_hashAcc.synchronized{
newAcc._hashAcc ++= _hashAcc
}
newAcc
}
//重置一个累加器
override def reset(): Unit = {
_hashAcc.clear()
}
//每一个分区中用于添加数据的方法
override def add(v: String): Unit = {
_hashAcc.get(v) match {
case None => _hashAcc += ((v,1))
case Some(a) => _hashAcc += ((v,a+1))
}
}
//合并每一个分区的输出
override def merge(other: AccumulatorV2[String, mutable.HashMap[String, Int]]): Unit = {
other match {
case o : AccumulatorV2[String, mutable.HashMap[String, Int]] =>{
for ((k,v)<- o.value){
_hashAcc.get(k) match {
case None => _hashAcc += ((k,v))
case Some(x) => _hashAcc += ((k,x+v))
}
}
}
}
}
//输出结果值
override def value: mutable.HashMap[String, Int] = {
_hashAcc
}
}
object CustomerAcc {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setAppName("accumulator").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
val hashAcc = new CustomerAcc()
sc.register(hashAcc,"hcx")
val rdd = sc.makeRDD(Array("a","b","c","d","a","b","c"))
rdd.foreach(hashAcc.add(_))
for ((k,v) <- hashAcc.value){
println(k+"-----"+v)
}
sc.stop()
}
}