Spark 是專門為大數據處理設計的通用計算引擎,是一個實現快速通用的集群計算平臺。它是由加州大學伯克利分校 AMP 實驗室開發的通用內存并行計算框架,用來構建大型的、低延遲的數據分析應用程序。它擴展了廣泛使用的 MapReduce 計算模型。高效的支撐更多計算模式,包括交互式查詢和流處理。Spark 的一個主要特點是能夠在內存中進行計算,即使依賴磁盤進行復雜的運算,Spark 依然比 MapReduce 更加高效。(2.3.3)
Spark 的四大特性:speed,easy to use,Generality,Runs Everywhere
Spark 生態包含了:Spark Core、Spark Streaming、Structured Streaming、Spark SQL、Graphx 和機器學習相關的庫等。
學習 Spark 我們應該掌握:
(1)Spark Core:
Spark的集群搭建和集群架構(Spark 集群中的角色)
spark集群的web管理界面: http://master主機名:8080
spark-shell --master local[2]
Scala:
//1、構建sparkConf對象 設置application名稱和master地址
val sparkConf: SparkConf = new SparkConf().setAppName("WordCount").setMaster("local[2]")
//2、構建sparkContext對象,該對象非常重要,它是所有spark程序的執行入口
// 它內部會構建 DAGScheduler和 TaskScheduler 對象
val sc = new SparkContext(sparkConf)
//設置日志輸出級別
sc.setLogLevel("warn")
sc.textFile("file:///home/hadoop/words.txt").flatMap(x=>x.split(" ")).map(x=>(x,1)).reduceByKey((x,y)=>x+y).collect
spark--->List(1,1,1)
sc.textFile("file:///home/hadoop/words.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect
val rdd1 = sc.parallelize(List(1,1,2,3,3,4,5,6,7))
Java:
//1、創建SparkConf對象
SparkConf sparkConf = new SparkConf().setAppName("JavaWordCount").setMaster("local[2]");
//2、構建JavaSparkContext對象
JavaSparkContext jsc = new JavaSparkContext(sparkConf);
Spark Cluster 和 Client 模式的區別
yarn-cluster模式下提交任務示例:
spark-submit --class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 1 \
/kkb/install/spark/examples/jars/spark-examples_2.11-2.3.3.jar \
10
如果運行出現錯誤,可能是虛擬內存不足,可以添加參數
- vim yarn-site.xml
<!--容器是否會執行物理內存限制默認為True-->
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<!--容器是否會執行虛擬內存限制 默認為True-->
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
yarn-client模式下提交任務示例:
spark-submit --class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode client \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 1 \
/kkb/install/spark/examples/jars/spark-examples_2.11-2.3.3.jar \
10
最大的區別就是Driver端的位置不一樣。
yarn-cluster: Driver端運行在yarn集群中,與ApplicationMaster進程在一起。
yarn-client: Driver端運行在提交任務的客戶端,與ApplicationMaster進程沒關系,經常用于進行測試
Spark 的彈性分布式數據集 RDD(Resilient Distributed Dataset)
1、分區的列表
2、函數在每個分區上計算
3、一個RDD會依賴其他多個RDD
4、RDD的分區函數(hashPartitioner,RangerPartitioner)Option(Some,Node)
5、為了提高效率,存儲每個partition位置可選,計算減少數據IO
- 掌握 Spark RDD 編程的算子 API(Transformation 和 Action 算子)
1、transformation算子
根據已經存在的rdd轉換生成一個新的rdd, 它是延遲加載,它不會立即執行
轉換 | 含義 |
---|---|
map(func) | 返回一個新的RDD,該RDD由每一個輸入元素經過func函數轉換后組成 |
filter(func) | 返回一個新的RDD,該RDD由經過func函數計算后返回值為true的輸入元素組成 |
flatMap(func) | 類似于map,但是每一個輸入元素可以被映射為0或多個輸出元素(所以func應該返回一個序列,而不是單一元素) |
mapPartitions(func) | 類似于map,但獨立地在RDD的每一個分片上運行,因此在類型為T的RDD上運行時,func的函數類型必須是Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) | 類似于mapPartitions,但func帶有一個整數參數表示分片的索引值,因此在類型為T的RDD上運行時,func的函數類型必須是(Int, Interator[T]) => Iterator[U] |
union(otherDataset) | 對源RDD和參數RDD求并集后返回一個新的RDD |
intersection(otherDataset) | 對源RDD和參數RDD求交集后返回一個新的RDD |
distinct([numTasks])) | 對源RDD進行去重后返回一個新的RDD |
groupByKey([numTasks]) | 在一個(K,V)的RDD上調用,返回一個(K, Iterator[V])的RDD |
reduceByKey(func, [numTasks]) | 在一個(K,V)的RDD上調用,返回一個(K,V)的RDD,使用指定的reduce函數,將相同key的值聚合到一起,與groupByKey類似,reduce任務的個數可以通過第二個可選的參數來設置 |
sortByKey([ascending], [numTasks]) | 在一個(K,V)的RDD上調用,K必須實現Ordered接口,返回一個按照key進行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) | 與sortByKey類似,但是更靈活 |
join(otherDataset, [numTasks]) | 在類型為(K,V)和(K,W)的RDD上調用,返回一個相同key對應的所有元素對在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) | 在類型為(K,V)和(K,W)的RDD上調用,返回一個(K,(Iterable<V>,Iterable<W>))類型的RDD |
coalesce(numPartitions) | 減少 RDD 的分區數到指定值。 |
repartition(numPartitions) | 重新給 RDD 分區 |
repartitionAndSortWithinPartitions(partitioner) | 重新給 RDD 分區,并且每個分區內以記錄的 key 排序 |
2、 action算子
它會真正觸發任務的運行, 將rdd的計算的結果數據返回給Driver端,或者是保存結果數據到外部存儲介質中
動作 | 含義 |
---|---|
reduce(func) | reduce將RDD中元素前兩個傳給輸入函數,產生一個新的return值,新產生的return值與RDD中下一個元素(第三個元素)組成兩個元素,再被傳給輸入函數,直到最后只有一個值為止。 |
collect() | 在驅動程序中,以數組的形式返回數據集的所有元素, 比如說rdd的數據量達到了10G, rdd.collect這個操作非常危險,很有可能出現driver端的內存不足 |
count() | 返回RDD的元素個數 |
first() | 返回RDD的第一個元素(類似于take(1)) |
take(n) | 返回一個由數據集的前n個元素組成的數組 |
takeOrdered(n, [ordering]) | 返回自然順序或者自定義順序的前 n 個元素 |
saveAsTextFile(path) | 將數據集的元素以textfile的形式保存到HDFS文件系統或者其他支持的文件系統,對于每個元素,Spark將會調用toString方法,將它裝換為文件中的文本 |
saveAsSequenceFile(path) | 將數據集中的元素以Hadoop sequencefile的格式保存到指定的目錄下,可以使HDFS或者其他Hadoop支持的文件系統。 |
saveAsObjectFile(path) | 將數據集的元素,以 Java 序列化的方式保存到指定的目錄下 |
countByKey() | 針對(K,V)類型的RDD,返回一個(K,Int)的map,表示每一個key對應的元素個數。 |
foreach(func) | 在數據集的每一個元素上,運行函數func |
foreachPartition(func) | 在數據集的每一個分區上,運行函數func |
spark connection to mysql
personRDD.foreachPartition( iter =>{
//把數據插入到mysql表操作
//1、獲取連接
val connection: Connection = DriverManager.getConnection("jdbc:mysql://node03:3306/spark","root","123456")
//2、定義插入數據的sql語句
val sql="insert into person(id,name,age) values(?,?,?)"
//3、獲取PreParedStatement
try {
val ps: PreparedStatement = connection.prepareStatement(sql)
//4、獲取數據,給?號 賦值
iter.foreach(line =>{
ps.setString(1, line._1)
ps.setString(2, line._2)
ps.setInt(3, line._3)
//設置批量提交
ps.addBatch()
})
//執行批量提交
ps.executeBatch()
} catch {
case e:Exception => e.printStackTrace()
} finally {
if(connection !=null){
connection.close()
}
spark connection to Hbase
usersRDD.foreachPartition(iter =>{
//4.1 獲取hbase的數據庫連接
val configuration: Configuration = HBaseConfiguration.create()
//指定zk集群的地址
configuration.set("hbase.zookeeper.quorum","node01:2181,node02:2181,node03:2181")
val connection: Connection = ConnectionFactory.createConnection(configuration)
//4.2 對于hbase表進行操作這里需要一個Table對象
val table: Table = connection.getTable(TableName.valueOf("person"))
//4.3 把數據保存在表中
try {
iter.foreach(x => {
val put = new Put(x(0).getBytes)
val puts = new util.ArrayList[Put]()
//構建數據
val put1: Put = put.addColumn("f1".getBytes, "gender".getBytes, x(1).getBytes)
val put2: Put = put.addColumn("f1".getBytes, "age".getBytes, x(2).getBytes)
val put3: Put = put.addColumn("f2".getBytes, "position".getBytes, x(3).getBytes)
val put4: Put = put.addColumn("f2".getBytes, "code".getBytes, x(4).getBytes)
puts.add(put1)
puts.add(put2)
puts.add(put3)
puts.add(put4)
//提交數據
table.put(puts)
})
} catch {
case e:Exception =>e.printStackTrace()
} finally {
if(connection !=null){
connection.close()
}
Spark DAG(有向無環圖)
一個Job會被拆分為多組Task,每組任務被稱為一個stage
stage表示不同的調度階段,一個spark job會對應產生很多個stage(ShuffleMapStage,ResultStage)
由于劃分完stage之后,在同一個stage中只有窄依賴,沒有寬依賴,可以實現流水線計算,
stage中的每一個分區對應一個task,在同一個stage中就有很多可以并行運行的task。
劃分完stage之后,每一個stage中有很多可以并行運行的task,后期把每一個stage中的task封裝在一個taskSet集合中,最后把一個一個的taskSet集合提交到worker節點上的executor進程中運行。
rdd與rdd之間存在依賴關系,stage與stage之前也存在依賴關系,前面stage中的task先運行,運行完成了再運行后面stage中的task,也就是說后面stage中的task輸入數據是前面stage中task的輸出結果數據。
RDD 的依賴關系,什么是寬依賴和窄依賴
窄依賴(narrow dependency)和寬依賴(wide dependency)
所有的窄依賴不會產生shuffle
所有的寬依賴會產生shuffle
RDD 的血緣機制
RDD只支持粗粒度轉換,即只記錄單個塊上執行的單個操作。將創建RDD的一系列Lineage(即血統)記錄下來,以便恢復丟失的分區
RDD的Lineage會記錄RDD的元數據信息和轉換行為,lineage保存了RDD的依賴關系,當該RDD的部分分區數據丟失時,它可以根據這些信息來重新運算和恢復丟失的數據分區。
RDD 緩存機制
RDD通過persist方法或cache方法可以將前面的計算結果緩存, 程序運行完成后對應的緩存數據就自動消失
Spark 的任務調度和資源調度
任務調度: (1) Driver端運行客戶端的main方法,構建SparkContext對象,在SparkContext對象內部依次構建DAGScheduler和TaskScheduler
(2) 按照rdd的一系列操作順序,來生成DAG有向無環圖
(3) DAGScheduler拿到DAG有向無環圖之后,按照寬依賴進行stage的劃分。每一個stage內部有很多可以并行運行的task,最后封裝在一個一個的taskSet集合中,然后把taskSet發送給TaskScheduler
(4)TaskScheduler得到taskSet集合之后,依次遍歷取出每一個task提交到worker節點上的executor進程中運行。
(5)所有task運行完成,整個任務也就結束了
資源調度: (1) Driver端向資源管理器Master發送注冊和申請計算資源的請求
(2) Master通知對應的worker節點啟動executor進程(計算資源)
(3) executor進程向Driver端發送注冊并且申請task請求
(4) Driver端運行客戶端的main方法,構建SparkContext對象,在SparkContext對象內部依次構建DAGScheduler和TaskScheduler
(5) 按照客戶端代碼洪rdd的一系列操作順序,生成DAG有向無環圖
(6) DAGScheduler拿到DAG有向無環圖之后,按照寬依賴進行stage的劃分。每一個stage內部有很多可以并行運行的task,最后封裝在一個一個的taskSet集合中,然后把taskSet發送給TaskScheduler
(7) TaskScheduler得到taskSet集合之后,依次遍歷取出每一個task提交到worker節點上的executor進程中運行
(8) 所有task運行完成,Driver端向Master發送注銷請求,Master通知Worker關閉executor進程,Worker上的計算資源得到釋放,最后整個任務也就結束了。
Spark 任務分析
參數:
==--executor-memory== : 表示每一個executor進程需要的內存大小,它決定了后期操作數據的速度
total-executor-cores: 表示任務運行需要總的cpu核數,它決定了任務并行運行的粒度
調度模式: FIFO,FAIR -> 根據權重不同來決定誰先執行
Spark 的 CheckPoint 和容錯
可以把數據持久化寫入到hdfs上, 程序運行完成后對應的checkpoint數據就不會消失
sc.setCheckpointDir("hdfs://node01:8020/checkpoint")
val rdd1=sc.textFile("/words.txt")
rdd1.checkpoint
val rdd2=rdd1.flatMap(_.split(" "))
rdd2.collect
- Spark 的通信機制
自定義分區: 繼承==org.apache.spark.Partitioner==, 重寫==numPartitions==方法, 重寫==getPartition==方法
廣播變量: ==傳遞到各個Executor的Task上運行==, 該Executor上的各個Task再從所在節點的BlockManager獲取變量,而不是從Driver獲取變量,以減少通信的成本,減少內存的占用,從而提升了效率.
SparkContext.accumulator(initialValue)
==累加器的一個常見用途是在調試時對作業執行過程中的事件進行計數。可以使用累加器來進行全局的計數==
“==org.apache.spark.SparkException: Task not serializable==”
spark的任務序列化異常 : 在編寫spark程序中,由于在map,foreachPartition等算子==內部使用了外部定義的變量和函數==,從而引發Task未序列化問題.
Spark shuffle 原理分析
hashshuffle
sortShuffle
bypass-sortShuffle
(2)Spark Streaming:
- 原理剖析(源碼級別)和運行機制
- Spark Dstream 及其 API 操作
- Spark Streaming 消費 Kafka 的兩種方式
- Spark 消費 Kafka 消息的 Offset 處理
- 數據傾斜的處理方案
- Spark Streaming 的算子調優
- 并行度和廣播變量
- Shuffle 調優
(3)Spark SQL:
- Spark SQL 的原理和運行機制
- Catalyst 的整體架構
- Spark SQL 的 DataFrame
- Spark SQL 的優化策略:內存列式存儲和內存緩存表、列存儲壓縮、邏輯查詢優化、Join 的優化
(4)Structured Streaming
Spark 從 2.3.0 版本開始支持 Structured Streaming,它是一個建立在 Spark SQL 引擎之上可擴展且容錯的流處理引擎,統一了批處理和流處理。正是 Structured Streaming 的加入使得 Spark 在統一流、批處理方面能和 Flink 分庭抗禮。
我們需要掌握:
- Structured Streaming 的模型
- Structured Streaming 的結果輸出模式
- 事件時間(Event-time)和延遲數據(Late Data)
- 窗口操作
- 水印
- 容錯和數據恢復
- Spark Mlib:
本部分是 Spark 對機器學習支持的部分,我們學有余力的同學可以了解一下 Spark 對常用的分類、回歸、聚類、協同過濾、降維以及底層的優化原語等算法和工具。可以嘗試自己使用 Spark Mlib 做一些簡單的算法應用。