spark RDD持久化

spark RDD持久化

簡介

spark是分佈式基於內存的數據處理引擎,它的一個基本功能是將RDD持久化到內存中。巧妙使用RDD持久化,甚至在某些場景下,可以將spark應用程序的性能提升10倍。對於迭代式算法和快速交互式應用來說,RDD持久化,是非常重要的。

cache和persist

spark有cache和persist兩種方持久化方法。

# RDD.scala部分源碼
  /**
   * Set this RDD's storage level to persist its values across operations after the first time
   * it is computed. This can only be used to assign a new storage level if the RDD does not
   * have a storage level set yet. Local checkpointing is an exception.
   */
  def persist(newLevel: StorageLevel): this.type = {
    if (isLocallyCheckpointed) {
      // This means the user previously called localCheckpoint(), which should have already
      // marked this RDD for persisting. Here we should override the old storage level with
      // one that is explicitly requested by the user (after adapting it to use disk).
      persist(LocalRDDCheckpointData.transformStorageLevel(newLevel), allowOverride = true)
    } else {
      persist(newLevel, allowOverride = false)
    }
  }

  /**
   * Persist this RDD with the default storage level (`MEMORY_ONLY`).
   */
  def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)

  /**
   * Persist this RDD with the default storage level (`MEMORY_ONLY`).
   */
  def cache(): this.type = persist()

  /**
   * Mark the RDD as non-persistent, and remove all blocks for it from memory and disk.
   *
   * @param blocking Whether to block until all blocks are deleted.
   * @return This RDD.
   */
  def unpersist(blocking: Boolean = true): this.type = {
    logInfo("Removing RDD " + id + " from persistence list")
    sc.unpersistRDD(id, blocking)
    storageLevel = StorageLevel.NONE
    this
  }

從spark的源碼中可以看出,cache()和persist()的區別在於,cache()是persist()的一種簡化方式,cache()的底層就是調用的persist()的無參版本,同時就是調用persist(MEMORY_ONLY),將數據持久化到內存中,persist方法可以手工設定StorageLevel來滿足工程需要的存儲級別。如果需要從內存中清楚緩存,都使用unpersist()方法。cache或者persist並不是action。

持久化級別

從StorageLevel的源碼中我們可以將持久化級別分爲以下幾種:

# StorageLevel.scala部分源碼
object StorageLevel {
  val NONE = new StorageLevel(false, false, false, false)
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
  .
  .
  .

可以看到這裏列出了12種緩存級別,但這些有什麼區別呢?可以看到每個緩存級別後面都跟了一個StorageLevel的構造函數,裏面包含了4個或5個參數,如下

val MEMORY_ONLY = new StorageLevel(false, true, false, true)
# StorageLevel.scala部分源碼
/**
 * :: 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)
  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
  .
  .
  .

可以看到StorageLevel類的主構造器包含了5個參數:

  • useDisk:使用硬盤(外存)
  • useMemory:使用內存
  • useOffHeap:使用堆外內存,這是Java虛擬機裏面的概念,堆外內存意味着把內存對象分配在Java虛擬機的堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機)。這樣做的結果就是能保持一個較小的堆,以減少垃圾收集對應用的影響。
  • deserialized:反序列化,其逆過程序列化(Serialization)是java提供的一種機制,將對象表示成一連串的字節;而反序列化就表示將字節恢復爲對象的過程。序列化是對象永久化的一種機制,可以將對象及其屬性保存起來,並能在反序列化後直接恢復這個對象
  • replication:備份數(在多個節點上備份)

理解了這5個參數,StorageLevel 的12種緩存級別就不難理解了。

val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)

就表示使用這種緩存級別的RDD將存儲在硬盤以及內存中,使用序列化(在硬盤中),並且在多個節點上備份2份(正常的RDD只有一份)

持久化級別總結

持久化級別 含義
MEMORY_ONLY 以非序列化的Java對象的方式持久化在JVM內存中。如果內存無法完全存儲RDD所有的partition,那麼那些沒有持久化的partition就會在下一次需要使用它的時候,重新被計算。
MEMORY_AND_DISK 同上,但是當某些partition無法存儲在內存中時,會持久化到磁盤中。下次需要使用這些partition時,需要從磁盤上讀取。
MEMORY_ONLY_SER 同MEMORY_ONLY,但是會使用Java序列化方式,將Java對象序列化後進行持久化。可以減少內存開銷,但是需要進行反序列化,因此會加大CPU開銷。
MEMORY_AND_DSK_SER 同MEMORY_AND_DSK。但是使用序列化方式持久化Java對象
DISK_ONLY 使用非序列化Java對象的方式持久化,完全存儲到磁盤上。
MEMORY_ONLY_2 MEMORY_AND_DISK_2等等 如果是尾部加了2的持久化級別,表示會將持久化數據複用一份,保存到其他節點,從而在數據丟失時,不需要再次計算,只需要使用備份數據即可。
OFF_HEAP(experimental) RDD的數據序例化之後存儲至Tachyon。相比於MEMORY_ONLY_SER,OFF_HEAP能夠減少垃圾回收開銷、使得Spark Executor更“小”更“輕”的同時可以共享內存;而且數據存儲於Tachyon中,Spark集羣節點故障並不會造成數據丟失,因此這種方式在“大”內存或多併發應用的場景下是很有吸引力的。需要注意的是,Tachyon並不直接包含於Spark的體系之內,需要選擇合適的版本進行部署;它的數據是以“塊”爲單位進行管理的,這些塊可以根據一定的算法被丟棄,且不會被重建。

如何選擇RDD持久化策略?

Spark提供的多種持久化級別,主要是爲了在CPU和內存消耗之間進行取捨。下面是一些通用的持久化級別的選擇建議:

1、優先使用MEMORY_ONLY,如果可以緩存所有數據的話,那麼就使用這種策略。因爲純內存速度最快,而且沒有序列化,不需要消耗CPU進行反序列化操作。
2、如果MEMORY_ONLY策略,無法存儲的下所有數據的話,那麼使用MEMORY_ONLY_SER,將數據進行序列化進行存儲,純內存操作還是非常快,只是要消耗CPU進行反序列化。
3、如果需要進行快速的失敗恢復,那麼就選擇帶後綴爲_2的策略,進行數據的備份,這樣在失敗時,就不需要重新計算了。
4、能不使用DISK相關的策略,就不用使用,有的時候,從磁盤讀取數據,還不如重新計算一次。

RDD持久化的例子

object sparkStudy {
  def main(args: Array[String]) {
    /*
     *RDD持久化
     * cache()或者persist()的使用,是有規則的
     * 必須在transformation或者textFile等創建了一個RDD之後,直接連續調用cache()或persist()纔可以
     * 如果你先創建一個RDD,然後單獨另起一行執行cache()或persist()方法,是沒有用的
     * 而且,會報錯,大量的文件會丟失
     */
    val conf = new SparkConf().setAppName("persist").setMaster("local")

    val sc = new SparkContext(conf)
    val lines = sc.textFile("F:\\spark\\JWCC_20180402_AcctSnapshot_9917_200105_012712.dat")
    //val lines = sc.textFile("F:\\spark\\JWCC_20180402_AcctSnapshot_9917_200105_012712.dat").cache()
    val beg = System.currentTimeMillis()
    val counts = lines.count()
    val end = System.currentTimeMillis()
    println("用時:" + (end - beg) + "ms")

    val begt = System.currentTimeMillis()
    val count = lines.count()
    val endt = System.currentTimeMillis()
    println("用時:" + (endt - begt) + "ms")

  }

}

不是用cache()
這裏寫圖片描述
這裏寫圖片描述

使用cache()
這裏寫圖片描述
這裏寫圖片描述

參考資料

Spark的cache和persist
Spark中cache和persist的作用以及存儲級別

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