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