spark學習部分筆記

每個 Spark 應用都由一個驅動器程序(driver program)來發起集羣上的各種並行操作。驅動器程序包含應用的 main 函數,
驅動器程序通過一個 SparkContext 對象來訪問 Spark。
調用了sc.textFile() 來創建一個代表文件中各行文本的 RDD
驅動器程序一般要管理多個執行器(executor)節點。


val conf = new SparkConf().setMaster("local").setAppName("My App")
集羣 URL:告訴 Spark 如何連接到集羣上。在這幾個例子中我們使用的是 local ,這個
特殊值可以讓 Spark 運行在單機單線程上而無需連接到集羣。
應用名:在例子中我們使用的是 My App 。當連接到一個集羣時,這個值可以幫助你在
集羣管理器的用戶界面中找到你的應用。

RDD 支持兩種類型的操作:轉化操作(transformation)和行動操作(action)
轉化操作會由一個 RDD 生成一個新的 RDD。
行動操作會對 RDD 計算出一個結果,並把結果返回到驅動器程序中,或把結果存儲到外部存儲系統(如 HDFS)


默認情況下,Spark 的 RDD 會在你每次對它們進行行動操作時重新計算。
如果想在多個行動操作中重用同一個 RDD,可以使用 RDD.persist() 讓 Spark 把這個 RDD 緩存下來。


總的來說,每個 Spark 程序或 shell 會話都按如下方式工作。
(1) 從外部數據創建出輸入 RDD。
(2) 使用諸如 filter() 這樣的轉化操作對 RDD 進行轉化,以定義新的 RDD。
(3) 告訴 Spark 對需要被重用的中間結果 RDD 執行 persist() 操作。
(4) 使用行動操作(例如 count() 和 first() 等)來觸發一次並行計算,Spark 會對計算進行優化後再執行。


cache() 與使用默認存儲級別調用 persist() 是一樣的


創建 RDD 最簡單的方式就是把程序中一個已有的集合傳給 SparkContext 的 parallelize()方法
例如 val lines = sc.parallelize(List("pandas", "i like pandas"))


更常用的方式是從外部存儲中讀取數據來創建 RDD




行動操作則是向驅動器程序返回結果或把結果寫入外部系統的操作,會觸發實際的計算,比如 count() 和 first() 。


如果對於一個特定的函數是屬於轉化操作還是行動操作感到困惑,你可以看看它的返回值類型:
轉化操作返回的是 RDD,而行動操作返回的是其他的數據類型。


val inputRDD = sc.textFile("log.txt")
val errorsRDD = inputRDD.filter(line => line.contains("error"))
注意, filter() 操作不會改變已有的 inputRDD 中的數據。


從已有的 RDD 中派生出新的 RDD,Spark 會使用譜系圖(lineage graph)來記錄這些不同 RDD 之間的依賴關係。


行動操作是第二種類型的 RDD 操作,它們會把最終求得的結果返回到驅動器程序,或者寫入外部存儲系統中。


RDD 還有一個 collect() 函數,可以用來獲取整個 RDD 中的數據。


記住,只有當你的整個數據集能在單臺機器的內存中放得下時,才能使用 collect() ,因此, collect() 不能用在大規模數據集上。
在大多數情況下,RDD 不能通過 collect() 收集到驅動器進程中,因爲它們一般都很大。
此時,我們通常要把數據寫到諸如 HDFS 或 Amazon S3 這樣的分佈式的存儲系統中。使用 saveAsTextFile() 、 saveAsSequenceFile()


不應該把 RDD 看作存放着特定數據的數據集,而最好把每個 RDD 當作我們通過轉化操作構建出來的、記錄何計算數據的指令列表


把需要的字段放到一個局部變量中,來避免傳遞包含該字段的整個對象


Scala 中出現了 NotSerializableException ,通常問題就在於我們傳遞了一個不可序列化的類中的函數或字段。
記住,傳遞局部可序列化變量或頂級對象中的函數始終是安全的。


希望對每個輸入元素生成多個輸出元素。實現該功能的操作叫作 flatMap() 。


 flatMap() 的一個簡單用途是把輸入的字符串切分爲單詞
 
需要注意, distinct() 操作的開銷很大,因爲它需要將所有數據通過網絡進行混洗(shuffle),以確保每個元素都只有一份。


 union(other) ,它會返回一個包含兩個 RDD 中所有元素的 RDD
 如果輸入的 RDD 中有重複數據,Spark 的 union() 操作也會包含這些重複數據
 intersection()在運行時也會去掉所有重複的元素(單個 RDD 內的重複元素也會一起移除)
 subtract(other) 函數接收另一個 RDD 作爲參數,返回一個由只存在於第一個 RDD 中而不存在於第二個 RDD 中的所有元素組成的 RDD。
 cartesian(other) 轉化操作會返回,所有可能的 (a, b) 對,其中 a 是源 RDD 中的元素,而 b 則來自另一個 RDD
 
fold() 和 reduce() 都要求函數的返回值類型需要和我們所操作的 RDD 中的元素類型相同
使用 collect() 使得 RDD 的值與預期結果之間的對比變得很容易。


take(n) 返回 RDD 中的 n 個元素,並且嘗試只訪問儘量少的分區,因此該操作會得到一個
不均衡的集合。需要注意的是,這些操作返回元素的順序與你預期的可能不一樣。


爲數據定義了順序,就可以使用 top() 從 RDD 中獲取前幾個元素。 top() 會使用數據
的默認順序,但我們也可以提供自己的比較函數,來提取前幾個元素。


 mean() 和 variance() 只能用在數值 RDD 上,而 join() 只能用在鍵值對 RDD 上。
 
 在 Scala 和 Java 中,默認情況下 persist() 會把數據以序列化的形式緩存在 JVM 的堆空間中
 
 如果要緩存的數據太多,內存中放不下,Spark 會自動利用最近最少使用(LRU)的緩存策略把最老的分區從內存中移除。
 
 RDD 還有一個方法叫作 unpersist() ,調用該方法可以手動把持久化的 RDD 從緩存中移除。
 
Spark 的 Java API 讓用戶使用 scala.Tuple2 類來創建二元組。
這個類很簡單:Java 用戶可以通過 new Tuple2(elem1, elem2) 來創建一個新的二元組,並且可以通過 ._1() 和 ._2() 方法訪問其中的元素。


reduceByKey() 與 reduce() 相當類似;它們都接收一個函數,並使用該函數對值進行合併。


combineByKey() 是最爲常用的基於鍵進行聚合的函數。大多數基於鍵聚合的函數都是用它實現的


每個 RDD 都有固定數目的分區,分區數決定了在 RDD 上執行操作時的並行度。


切記,對數據進行重新分區是代價相對比較大的操作。


cogroup() 不僅可以用於實現連接操作,還可以用來求鍵的交集。除此之外,cogroup() 還能同時應用於三個及以上的 RDD。


 sortByKey() 函數接收一個叫作 ascending 的參數,表示我們是否想要讓結果按升序排序(默認值爲 true )
 
Spark 程序可以通過控制RDD 分區方式來減少通信開銷。


分區並不是對所有應用都有好處的——比如,如果給定RDD 只需要被掃描一次,我們完全沒有必要對其預先進行分區處理


partitionBy() 是一個轉化操作,因此它的返回值總是一個新的 RDD,但它不會改變原來的 RDD


如果沒有將 partitionBy() 轉化操作的結果持久化,那麼後面每次用到這個RDD 時都會重複地對數據進行分區操作。
不進行持久化會導致整個 RDD 譜系圖重新求值。


在 Spark shell 中使用 partitioner 屬性不僅是檢驗各種 Spark 操作如何影響分區方式的一種
好辦法,還可以用來在你的程序中檢查想要使用的操作是否會生成正確的結果


轉化操作的結果並不一定會按已知的分區方式分區


會爲生成的結果 RDD 設好分區方式的操作: cogroup() 、 groupWith() 、
join() 、 leftOuterJoin() 、 rightOuterJoin() 、 groupByKey() 、 reduceByKey() 、
combineByKey() 、 partitionBy() 、 sort() 、 mapValues() (如果父 RDD 有分區方式的話)、
flatMapValues() (如果父 RDD 有分區方式的話),以及 filter() (如果父 RDD 有分區方
式的話)。其他所有的操作生成的結果都不會存在特定的分區方式。


Spark 提供的 HashPartitioner 與 RangePartitioner 已經能夠滿足大多數用例




要實現自定義的分區器,你需要繼承 org.apache.spark.Partitioner 類並實現下面三個方法。
?  numPartitions: Int :返回創建出來的分區數。
?  getPartition(key: Any): Int :返回給定鍵的分區編號(0 到 numPartitions-1 )。
?  equals() :Java 判斷相等性的標準方法。這個方法的實現非常重要,Spark 需要用這個
方法來檢查你的分區器對象是否和其他分區器實例相同,這樣 Spark 纔可以判斷兩個
RDD 的分區方式是否相同。






82頁




如果你想要對多個 RDD 使用相同的分區方式,就應該使用同一個函數對象,比如一個全局函數,而不是爲每個 RDD 創建一個新的函數對象。


只需要使用文件路徑作爲參數調用 SparkContext 中的 textFile() 函數,就可以讀取一個文本文件
例如val input = sc.textFile("file:///home/holden/repos/spark/README.md")


如果文件足夠小,那麼可以使用 SparkContext.
wholeTextFiles() 方法,該方法會返回一個 pair RDD,其中鍵是輸入文件的文件名。


讀取 JSON 數據的最簡單的方式是將數據
作爲文本文件讀取,然後使用 JSON 解析器來對 RDD 中的值進行映射操作。


如果你有跨行的
JSON 數據,你就只能讀入整個文件,然後對每個文件進行解析


由於 Hadoop 使用了一套自定義的序列化框架,因此 SequenceFile 是由實現 Hadoop 的 Writable
接口的元素組成


 SequenceFile 存儲的
是鍵值對,所以需要創建一個由可以寫出到 SequenceFile 的類型構成的 PairRDD 




hadoopDataset /
saveAsHadoopDataSet 和 newAPIHadoopDataset / saveAsNewAPIHadoopDataset 來訪問 Hadoop 所
支持的非文件系統的存儲格式。例如,許多像 HBase 和 MongoDB 這樣的鍵值對存儲都提
供了用來直接讀取 Hadoop 輸入格式的接口。


Spark 原生的輸入方式( textFile 和 sequenceFile )可以自動處理一些類型的壓縮。


推薦的方法是將文件先放到像 HDFS、NFS、S3 等共享文件系統上。


在 Java 和 Scala 中, Row 對象的訪問是基於下標的。每個 Row 都有一個
get() 方法,會返回一個一般類型讓我們可以進行類型轉換。


Spark 可 以 通 過
Hadoop 輸入格式訪問 HBase。這個輸入格式會返回鍵值對數據,其中鍵的類型爲 org.
apache.hadoop.hbase.io.ImmutableBytesWritable ,而值的類型爲 org.apache.hadoop.hbase.
client.Result 。




累加器(accumulator)與廣播變量(broadcast variable)。累加器用來對信息進行聚合,而
廣播變量用來高效分發較大的對象。


Spark 的兩個共享變量,累加器與廣
播變量,分別爲結果聚合與廣播這兩種常見的通信模式突破了這一限制。




總結起來,累加器的用法如下所示。
1.通過在驅動器中調用 SparkContext.accumulator(initialValue) 方法,創建出存有初
始值的累加器。返回值爲 org.apache.spark.Accumulator[T] 對象,其中 T 是初始值
initialValue 的類型。
2.Spark閉包裏的執行器代碼可以使用累加器的 += 方法(在Java中是 add )增加累加器的值。
3. 驅動器程序可以調用累加器的 value 屬性(在 Java 中使用 value() 或 setValue() )來訪
問累加器的值。
注意,工作節點上的任務不能訪問




注意,工作節點上的任務不能訪問累加器的值。
Spark 會自動重新執行失敗的或較慢的任務來應對有錯誤的或者比較慢的機器


對於要在行動操作中使用的累加器,Spark
只會把每個任務對各累加器的修改應用一次。
如果想要一個無論在失敗還是重複計
算時都絕對可靠的累加器,我們必須把它放在 foreach() 這樣的行動操作中。
對於在 RDD 轉化操作中使用的累加器,就不能保證有這種情況了。




使用廣播變量的過程很簡單。
(1) 通過對一個類型 T 的對象調用 SparkContext.broadcast 創建出一個 Broadcast[T] 對象。
任何可序列化的類型都可以這麼實現。
(2) 通過 value 屬性訪問該對象的值(在 Java 中爲 value() 方法)。
(3) 變量只會被髮到各個節點一次,應作爲只讀值處理(修改這個值不會影響到別的節點)。




基於分區對數據進行操作可以讓我們避免爲每個數據元素進行重複的配置工作。




通過 pipe() ,你可以
將 RDD 中的各元素從標準輸入流中以字符串形式讀出,並對這些元素執行任何你需要
的操作,然後把結果以字符串的形式寫入標準輸出——這個過程就是 RDD 的轉化操作過
程。


任務是 Spark 中最小的工作單
元,用戶程序通常要啓動成百上千的獨立任務。


Spark 執行器節點是一種工作進程,負責在 Spark 作業中運行任務,任務間相互獨立。


執行器進
程有兩大作用:第一,它們負責運行組成 Spark 應用的任務,並將結果返回給驅動器進程;
第二,它們通過自身的塊管理器(Block Manager)爲用戶程序中要求緩存的 RDD 提供內
存式存儲。




 Spark 依賴於集羣管理器來啓動執行器節點,而在某些特殊
情況下,也依賴集羣管理器來啓動驅動器節點。


從這看出,spark的driver和exctor是邏輯概念。
而master和worker是關於並行化集羣的相對具體
的概念。


Spark 文檔中始終使用 驅動器節點 和 執行器節點 的概念來描述執行 Spark
應用的進程。而 主節點 (master)和 工作節點 (worker)的概念則被用來
分別表述集羣管理器中的中心化的部分和分佈式的部分。






(1) 用戶通過 spark-submit 腳本提交應用。
(2) spark-submit 腳本啓動驅動器程序,調用用戶定義的 main() 方法。
(3) 驅動器程序與集羣管理器通信,申請資源以啓動執行器節點。
(4) 集羣管理器爲驅動器程序啓動執行器節點。
(5) 驅動器進程執行用戶應用中的操作。根據程序中所定義的對 RDD 的轉化操作和行動操
作,驅動器節點把工作以任務的形式發送到執行器進程。
(6) 任務在執行器程序中進行計算並保存結果。
(7) 如果驅動器程序的 main() 方法退出,或者調用了 SparkContext.stop() ,驅動器程序會
終止執行器進程,並且通過集羣管理器釋放資源。




事實上,常規的做法是使用構建工具,生成單個大 JAR 包,包含應用的所有的傳遞依賴。這通常被稱爲超級(uber)JAR 或者組合(assembly) JAR




通常,依賴衝突表現爲 Spark 作業執行過
程中拋出 NoSuchMethodError 、 ClassNotFoundException ,或其他與類加載相關的 JVM 異
常。對於這種問題,主要有兩種解決方式:一是修改你的應用,使其使用的依賴庫的版本
與 Spark 所使用的相同,二是使用通常被稱爲“shading”的方式打包你的應用。




阻礙應用運行的一個常見陷阱是爲執行器進程申請的內存( spark-submit 的
--executor-memory 標記傳遞的值)超過了集羣所能提供的內存總量。在這種
情況下,獨立集羣管理器始終無法爲應用分配執行器節點。請確保應用申請
的值能夠被集羣接受。




有兩種將應用連接到集羣的模式:客戶端模式以及集羣模式。在
客戶端模式下應用的驅動器程序運行在提交應用的機器上(比如你的筆記本電腦),而在
集羣模式下,驅動器程序也運行在一個 YARN 容器內部




在“細粒度”模式(默認)中,執行器進程佔用的 CPU 核心數會在它們執行任務時
動態變化,因此一臺運行了多個執行器進程的機器可以動態共享 CPU 資源。而在“粗粒
度”模式中,Spark 提前爲每個執行器進程分配固定數量的 CPU 數目,並且在應用結束前
絕不釋放這些資源,哪怕執行器進程當前不在運行任務。你可以通過向 spark-submit 傳遞
--conf spark.mesos.coarse=true 來打開粗粒度模式。








第八章






有向無環圖(DAG)


調度器爲有向圖中的每個
RDD 輸出計算步驟,步驟中包括 RDD 上需要應用於每個分區的任務。


特定的行動操作所生成的步驟的集合被稱爲一個作業。
一個物理步驟會啓動很多任務,每個任務都是在不同的數據分區上做同樣的事情。






Spark 執行時有下面所列的這些流程。
1.用戶代碼定義RDD的有向無環圖RDD 上的操作會創建出新的 RDD,並引用它們的父節點,這樣就創建出了一個圖。
2. 行動操作把有向無環圖強制轉譯爲執行計劃當你調用 RDD 的一個行動操作時,這個 RDD 就必須被計算出來。這也要求計算出該jobRDD 的父節點。Spark 調度器提交一個作業來計算所有必要的 RDD。這個作業會包含一個或多個步驟,每個步驟其實也就是一波並行執行的計算任務。一個步驟對應有向無環圖中的一個或多個 RDD,一個步驟對應多個 RDD 是因爲發生了流水線執行。
3.任務於集羣中調度並執行 步驟是按順序處理的,任務則獨立地啓動來計算出 RDD 的一部分。一旦作業的最後一個步驟結束,一個行動操作也就執行完畢了。




當看到少量的任務相對於其他任務需要花費大量時間的時候,一般就是發生了數據傾斜。


如果任務花了很少的時間讀取或輸出數據,但是總時間卻很長,這就可能表明用戶代碼本身的執行比較花時間


Spark 也會針對 RDD 直接自動推斷出合適的並行度,這對於大多數用例來說已經足夠了。輸入 RDD 一般會根據其底層的存儲系統選擇並行度。


第一種方法是在數據混洗操作時,使
用參數的方式爲混洗後的 RDD 指定並行度。第二種方法是對於任何已有的 RDD,可以進行重新分區來獲取更多或者更少的分區數。


在默認情況下,Spark 會使用 60%的空間來存儲 RDD,20% 存儲數據混洗操作產生的數
據,剩下的 20% 留給用戶程序。用戶可以自行調節這些選項來追求更好的性能表現。如果
用戶代碼中分配了大量的對象,那麼降低 RDD 存儲和數據混洗存儲所佔用的空間可以有
效避免程序內存不足的情況。


切記,“越多越好”的原則在設置執行器節點內存時並不一定適用。




Spark SQL 提供了以下三大功能
(1) Spark SQL 可以從各種結構化數據源(例如 JSON、Hive、Parquet 等)中讀取數據。
(2) Spark SQL 不僅支持在 Spark 程序內使用 SQL 語句進行數據查詢,也支持從類似商業智能軟件 Tableau 這樣的外部工具中通過標準數據庫連接器(JDBC/ODBC)連接 SparkSQL 進行查詢。
(3) 當在 Spark 程序內使用 Spark SQL 時,Spark SQL 支持 SQL 與常規的 Python/Java/Scala代碼高度整合,包括連接 RDD 與 SQL 表、公開的自定義 SQL 函數接口等。這樣一來,許多工作都更容易實現了。


 1 SchemaRDD是存放 Row 對象的 RDD,每個 Row 對象代表一行記錄。
 
 Spark SQL 編譯時可以包含 Hive 支持,也可以不包含。
 
 需要強調的一點是,如果要在 Spark SQL 中包含 Hive 的庫,並不需要事先安裝 Hive。
 
 如果你是從代碼編譯Spark,你應該使用 sbt/sbt -Phive assembly 編譯,以打開 Hive 支持。 
 
 如果你不能引入 Hive 依賴,那就應該使用工件 spark-sql_2.10 來代替 spark-hive_2.10 。
 
 
  HiveContext ,它可以提供 HiveQL 以及其他依賴於 Hive 的功能的支持。更爲基礎的 SQLContext 則支持 Spark SQL 功能的一個子集,子集中去掉了需要依賴於 Hive 的功能。
  
  即使沒有部署好 Hive,Spark SQL 也可以運行。
  
這裏沒有用類似在導入 SparkContext 時的方法那樣導入HiveContext._ 來訪問隱式轉換。隱式轉換被用來把帶有類型信息的 RDD 轉變爲專門用於Spark SQL 查詢的 RDD(也就是 SchemaRDD)。


SchemaRDD 是一個由 Row 對象組成的 RDD,附帶包含每列數據類型的結構信息。 Row 對象只是對基本數據類型(如整型和字符串型等)的數組的封裝。


今後的 Spark 版本中(1.3 及以後),SchemaRDD 這個名字可能會被改爲 DataFrame。


 hiveCtx.cacheTable("tableName") 方法
 
Spark Streaming 使用離散化流(discretized stream)作爲抽象表示,叫作 DStream。


DStream 可以從各種輸入源創建,比如 Flume、Kafka 或者 HDFS。創
建出來的 DStream 支持兩種操作,一種是轉化操作(transformation),會生成一個新的
DStream,另一種是輸出操作(output operation),可以把數據寫入外部系統中。




Spark Streaming 應用需要進行額外配置來保證 24/7 不間斷工作 (24/7 是一天24小時,每週7天)


Spark Streaming 程序最好以使用 Maven 或者 sbt 編譯出來的獨立應用的形式運行。


必須顯式調用 StreamingContext 的 start() 方法


請注意,一個 Streaming context 只能啓動一次,所以只有在配置好所有 DStream 以及所需要的輸出操作之後才能啓動。


時間區間的大小是由批次間隔這個參數決定的。批次間隔一般設在 500 毫秒到幾秒之間,由應用開發者配置。


DStream它是一個 RDD 序列,每個 RDD 代表數據流中一個時間片內的數據


Spark Streaming 爲每個輸入源啓動對應的接收器


般來說,你需要每處理 5-10 個批次的數據就保存一次。在恢復數據時,Spark Streaming 只需要回溯到上一個檢查點即可


每個 DStream 在內部是由許多 RDD(批次)組成,且無狀態轉化操作是分別應用到每個 RDD 上的。例如,reduceByKey()
 會歸約每個時間區間中的數據,但不會歸約不同區間之間的數據。
 
 
K-means 中最重要的參數是生成的聚類中心的目標數量 K。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章