Spark RDD Scala語言編程
RDD(Resilient Distributed Dataset)是一個不可變的分佈式對象集合, 每個rdd被分爲多個分區, 這些分區運行在集羣的不同節點上。rdd支持兩種類型的操作:轉化(trainsformation)和行動(action), Spark只會惰性計算rdd, 也就是說, 轉化操作的rdd不會立即計算, 而是在其第一次遇到行動操作時纔去計算, 如果想在多個行動操作中複用一個rdd, 可以使用RDD.persist()讓Spark把這個rdd緩存下來。
0. 初始化SparkContext
import org.apache.spark.{SparkConf, SparkContext}
val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("spark-rdd-demo"))
1. 創建RDD
Spark提供了2種創建RDD的方式:
1.1 讀取外部數據集
val lines = sc.parallelize(List("Java", "Scala", "C++"))
1.2 在驅動器程序中對一個集合進行並行化
val lines = sc.textFile("hdfs://dash-dev:9000/input/test.txt")
2. RDD操作
2.1 轉化操作
RDD的轉化操作是返回新RDD的操作, 常用轉化操作總結如下:
表1: 對一個數據爲{1,2,3,3}的RDD進行基本的轉化操作
函數名 | 目的 | 示例 | 結果 |
---|---|---|---|
map() | 將函數應用於RDD中每個元素, 將返回值構成新的RDD | rdd.map(x=>x+1) | {2,3,4,5} |
flatMap() | 將函數應用於RDD中的每個元素, 將返回的迭代器的所有內容構成新的RDD, 常用來切分單詞 | rdd.flatMap(x=>x.to(2)) | {1,2,2} |
filter() | 返回一個通過傳入給filter()的函數的元素組成的RDD | rdd.filter(x=> x>2) | {3,3} |
distinct() | 去重 | rdd.distinct() | {1,2,3} |
sample(withReplacement, fraction, [seed]) | 對RDD採樣, 以及是否替換 | rdd.sample(false, 0.5) | 非確定的 |
表2: 對數據分別爲{1,2,3}和{2,3,4}RDD進行鍼對2個RDD的轉化操作
函數名 | 目的 | 示例 | 結果 |
---|---|---|---|
union() | 求2個RDD的並集 | rdd.union(other) | {1,2,3,4} |
intersection() | 求2個RDD的交集 | rdd.intersection(other) | {2,3} |
subtract() | 求2個RDD的差集 | rdd.subtract(other) | {1} |
cartesian() | 求2個RDD的笛卡爾積 | rdd.cartesian(other) | {1,2}, {1,3}, {1,4}…{3,4} |
sample(withReplacement, fraction, [seed]) | 對RDD採樣, 以及是否替換 | rdd.sample(false, 0.5) | 非確定的 |
2.2 行動操作
RDD的行動操作會把最終求得的結果返回驅動器程序, 或者寫入外部存儲系統中。
表3: 對一個數據爲{1,2,3,3}的RDD進行基本RDD的行動操作
函數名 | 目的 | 示例 | 結果 |
---|---|---|---|
redcue() | 並行整合RDD中的所有元素 | rdd.reduce((x, y) => x+y) | 9 |
collect() | 返回RDD中的所有元素 | rdd.collect() | {1,2,3,4} |
count() | 求RDD中的元素個數 | rdd.count() | 4 |
countByValue() | 各元素在RDD中出現的次數 | rdd.countByValue() | {1,1}, {2, 1}, {3,2} |
take(n) | 從RDD中返回n個元素 | rdd.take(2) | {1,2} |
top(n) | 從RDD中返回前n個元素 | rdd.top(3) | {3,3,2} |
foreach(func) | 對RDD中的每個元素使用給定的函數 | rdd.foreach(print) | 1,2,3,3 |
2.3 向Spark傳遞函數
Spark的大部分轉化和行動操作都要依賴於用戶傳遞的函數來計算, 當傳遞的對象是某個對象的成員, 或者包含了對某個對象中一個字段的引用時(如self.field), Spark就會把整個對象發送到工作節點上--這比你本意想傳遞的東西大太多了!替代的方案是,把你需要的字段從對象中拿出來放到一個局部變量中, 然後傳遞這個局部變量:
class SearchFunctions(val query: String) {
def isMatch(s: String): Boolean = {
s.contains(query)
}
def getMatchesFunctionReference(rdd: RDD[String]): RDD[String] = {
// 問題:"isMatch"表示"this.isMatch", 因此會傳遞整個this
rdd.map(isMatch)
}
def getMatchesFieldReference(rdd: RDD[String]): RDD[String] = {
// 問題: "query"表示"this.query", 因此會傳遞整個this
rdd.map(x => x.split(query))
}
def getMatchesNoReference(rdd: RDD[String]): RDD[String] = {
// 安全:只把我們需要的字段拿出來放入局部變量中
val localQuery = this.query
rdd.map(x => x.split(localQuery))
}
}
另外, 要注意的是, Spark要求我們傳入的函數及其應用的數據是可序列化的(實現了Java的Serializable接口), 否則會出現NotSerializableException。
作者 @wusuopubupt
2016年11月11日