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日