RDD(彈性分佈式數據集)介紹---Spark的核心

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()
  }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章