文章目錄
- Spark簡介
- Scala編程基礎
- Scala是一門怎樣的語言,具有哪些優點?
- Scala語法基礎-從Hello World說起
- Scala值和變量聲明
- Scala常用類型
- 方法的定義和使用
- 函數的定義和使用
- 函數與方法區別
- Example - 方法計時器
- 循環和高級for循環
- 常見集合使用
- 異常處理
- 類定義
- 類構造函數
- 類繼承
- 單例對象
- 函數式編程思想
- 函數式編程思想Example
- Spark體系結構和源代碼解析
- 彈性分佈式數據集RDD
- RDD 屬性和特點
- RDD.scala 解析
- RDD Example
- 寬依賴和窄依賴
- 寬依賴和窄依賴
- Stage
- Stage執行優化
- Spark執行流程
- Spark執行流程
- Yarn資源調度過程
- Spark on Yarn
- Spark編程模型
- Spark編程模型
- Spark編程模型
- Spark API文檔
- Value類型 Transformation 算子分類
- Transformation-map
- Transformation-mapPartitions
- Transformation-flatMap
- Transformation-flatMap
- Transformation-union
- Transformation-distinct
- Transformation-filter
- Transformation-intersection
- Key-Value類型 Transformation 算子分類
- Transformation-groupByKey
- Transformation-groupByKey
- Transformation-reduceByKey
- Transformation-reduceByKey
- Transformation-aggregateByKey
- Transformation-join
- Action 算子分類
- Spark內存模型
- Spark案例介紹
Spark簡介
Spark簡介
- 什麼是Spark?
- Spark是基於內存計算的通用大規模數據處理框架
- Spark已經融入了Hadoop生態系統,可支持的作業類型和應用場景比MapReduce更爲廣泛,並且具備了MapReduce所有的高容錯性和高伸縮性特點。
爲何會誕生Spark?
- 回顧MapReduce
- 並不是所有的問題都可以簡單的分解成Map和Reduce兩步模型處理
- 並不是所有的問題都可以簡單的分解成Map和Reduce兩步模型處理
- MapReduce缺點
- 延時高 ✗
- Example:不適合交互式SQL分析
- 迭代計算力不從心 ✗
- Example:斐波那契數列
- 流式數據處理 ✗
- Example:統計網站PV、UV數據
- 延時高 ✗
- Spark
- 一站式解決
- 離線批處理 ✓
- 流式計算 ✓
- 在線實時分析 ✓
- 一站式解決
Spark爲何快?
MapReduce
- MapReduce會將中間結果輸出到本地磁盤
- 例如Shuffle時Map輸出的中間結果
- 例如Shuffle時Map輸出的中間結果
- 有多個MapReduce任務串聯時,依賴HDFS存儲中間結果的輸出
- 例如執行Hive查詢
- 例如執行Hive查詢
- MapReduce在處理複雜DAG時會帶來大量的數據copy、序列化和磁盤I/O開銷
Spark
- Spark儘可能減少中間結果寫入磁盤
- 儘可能減少不必要的Sort/Shuffle
- 反覆用到的數據進行Cache
- 對於DAG進行高度優化
- 劃分不同的Stage
- 使用延遲計算技術
Spark特點
- 內存計算
- 支持複雜查詢、流式計算、機器學習、圖計算
- 融入Hadoop生態圈
- 兼容HDFS
- 兼容Yarn
- 核心代碼由Scala編寫
- 發展速度快
- 社區活躍
- 最新版本2.4.0 (截止2018年2月)
Spark多語言支持
Scala編程基礎
Scala是一門怎樣的語言,具有哪些優點?
- 快速實驗
- 快速嘗試各種語法和代碼
- 一致性
- 靜態類型系統+面向對象+函數式編程
- 面向對象
- 所有的變量和方法都封裝在對象中
- 函數式編程
- 函數可以獨立存在,可以定義一個函數作爲另外一個函數的返回值,也可以接受函數作爲函數的參數
- 異步編程
- 函數式編程提倡變量不可變,使得異步編程變得十分容易
- 基於JVM
- Scala會被編譯成爲Bytecode,所以Scala能無縫集成已有的Java類庫
- Scala會被編譯成爲Bytecode,所以Scala能無縫集成已有的Java類庫
Scala語法基礎-從Hello World說起
Scala值和變量聲明
- val變量和var變量
- val聲明的變量不可變,相當於java中的final
- val a = 1
- a = 2 // 出錯啦
- var聲明的變量可變
- var a = 1
- a = 2 // OK
- val聲明的變量不可變,相當於java中的final
- 在scala的類中,val會自動帶有getter方法,var會自動帶有getter和setter方法
Scala常用類型
- Scala沒有區分基本類型和包裝類型,統一定義爲class類。
- 1.toString() // 生成字符串1
- 7種數值類型+1種Boolean類型
- Byte -> RichByte
- Char -> RichChar
- Short -> RichShort
- Int -> RichInt
- Long -> RichLong
- Float -> RichFloat
- Double -> RichDouble
- 在基本數據類型上使用那些沒有提供的方法時,scala會嘗試“隱式轉換”轉換成增強類型
- Example
- 1.to(10) // 生成出Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
- Example
方法的定義和使用
• 方法定義
• 格式
def 方法名(參數名: 參數類型): 返回值類型 = {
return xxx // return可省略
}
• 當返回值爲unit時
def 方法名(參數名: 參數類型) {
// 方法體
}
• 無參數函數定義
def 方法名 {
// 方法體
}
• Example
def m1(a: Int, b: Int): Int = {
a + b
}
def m2() = 100
def m3 = 100
函數的定義和使用
- 函數定義
- 函數在scala中是一等公民
- val 函數名:(參數類型1, … , 參數類型n)=>返回值類型 = (T1,…, Tn) => 函數體
- val 函數名 = (參數名1: 參數類型1, … , 參數名n: 參數類型n) =>函數體
- 函數必須有參數列表,否則報錯
• val f1 = => 100 // 錯誤
• val f2 = () => 100 // 正確
函數與方法區別
- 方法不可以賦值給變量但是函數可以
- 對於一個無參數的方法是沒有參數列表的,而對於函數是有一個空參數列表。
- 函數名後必須加括號才代表函數調用,否則爲該函數本身,而方法名後不加括號爲方法調用
Example - 方法計時器
循環和高級for循環
常見集合使用
- C 操作時間爲常數
- eC 操作時間在滿足某些假設的前提下爲常數
- aC該操作的均攤運行時間爲常數。某些調用的可能耗時較長,但多次調用之下,每次調用的平均耗時是常數。
- L 操作是線性的,耗時與容器的大小成正比。
異常處理
- 如果在.map, .flatMap中遇到異常如何處理?
- Scala提供了scala.util.Try 類型更加優雅的處理異常
- 如果成功返回Success
- 如果拋出異常返回Failure並攜帶異常信息
類定義
類構造函數
類繼承
- Scala繼承類和java一樣使用extends關鍵字
- 可以將類、字段或者方法聲明爲final,確保它們不能被重寫
- 重寫一個非抽象方法必須使用override關鍵詞
- 可以將類定義爲abstract作爲抽象類,子類中重寫超類的抽象方法時不
需要使用override關鍵詞 - 調用超類與Java一致使用super關鍵詞
- 只有主構造器才能調用超類的構造器
單例對象
函數式編程思想
- 函數式編程關心的是數據的映射而命令式編程關心的是解決問題的步驟
- 函數式編程提倡
- 沒有可變的變量
- 例如無論sqrt(x),這個函數的值只取決於函數的輸入的值
- 沒有類似於命令式編程中循環元素
- 沒有可變的變量
- 好處
- 不依賴於外部的狀態,也不修改外部的狀態,使得代碼容易推理,
單元測試和調試變得十分容易 - 由於多個線程之前不共享狀態,因此不會造成資源的競爭,可以更
好的支持併發
- 不依賴於外部的狀態,也不修改外部的狀態,使得代碼容易推理,
函數式編程思想Example
Spark體系結構和源代碼解析
彈性分佈式數據集RDD
- Spark將數據緩存在分佈式內存中
- 如何實現?RDD
- Spark的核心
- 分佈式內存抽象
- 提供了一個高度受限的共享內存模型
- 邏輯上集中但是物理上是存儲在集羣的多臺機器上
RDD 屬性和特點
- 只讀
- 通過HDFS或者其它持久化系統創建RDD
- 通過transformation將父RDD轉化得到新的RDD
- RDD上保存着前後之間依賴關係
- Partition
- 基本組成單位,RDD在邏輯上按照Partition分塊
- 分佈在各個節點上
- 分片數量決定並行計算的粒度
- RDD中保存如何計算每一個分區的函數
- 容錯
- 失敗自動重建
- 如果發生部分分區數據丟失,可以通過依賴關係重新計算
RDD.scala 解析
RDD.scala是所有RDD的總得抽象
RDD Example
val lines = sc.textFile(…)
lines.filter(x => x.contains(“Error”)).count()
寬依賴和窄依賴
- 窄依賴
- 沒有數據shuffling
- 所有父RDD中的Partition均會和子RDD的Partition關係是一對一
寬依賴和窄依賴
- 寬依賴
- 有數據shuffling
- 所有父RDD中的Partition會被切分,根據key的不同劃分到子RDD的Partition中
Stage
- 什麼是Stage
- 一個Job會被拆分爲多組Task,每組Task被稱爲一個Stage
- 劃分依據
- 以shuffle操作作爲邊界,遇到一個寬依賴就分一個stage
- 以shuffle操作作爲邊界,遇到一個寬依賴就分一個stage
Stage執行優化
- 對窄依賴可以進行流水線(pipeline)優化
- 不互相依賴的Stage可以並行執行
- 存在依賴的Stage必須在依賴的Stage執行完之後才能執行
- Stage並行執行程度取決於資源數
Spark執行流程
- 用戶創建Spark程序並提交
- 每個Action會生成一個Job
- 包含了一系列RDD以及如何對其進行轉換transformation
- 對每個Job生成DAG
- Directed Acyclic Graph
- 對根據寬窄依賴對DAG進行劃分Stage
- 對每一個Stage生成一組Task
- 一個Partition對應一個Task
- Spark會以一組Task爲單位進行執行計算
Spark執行流程
Yarn資源調度過程
Spark on Yarn
- Yarn
- ResourceManager:負責整個集羣資源管理和分配
- ApplicationMaster:Yarn中每個Application對應一個AM,負責與
ResrouceManager協商獲取資源,並告知NodeManager分配啓動Container - NodeManager:每個節點的資源和任務管理器,負責啓動Container,並監視資源使用情況
- Container:資源抽象
- Spark
- Application:用戶自己編寫的Spark程序
- Driver:運行Application的main函數並創建SparkContext,和ClusterManager通信申請資源,任務分配並監控運行情況
- ClusterManager:指的是Yarn
- DAGScheduler:對DAG圖劃分Stage
- TaskScheduler:把TaskSet分配給具體的Executor
- Spark支持三種運行模式
- standalon, yarn-cluster, yarn-client
- standalon, yarn-cluster, yarn-client
Spark編程模型
核心思想:
Spark編程模型
- 對於RDD有四種類型的算子
- Create
- SparkContext.textFile()
- SparkContext.parallelize()
- Transformation
- 作用於一個或者多個RDD,輸出轉換後的RDD
- 例如:map, filter, groupBy
- Action
- 會觸發Spark提交作業,並將結果返回Driver Program
- 例如:reduce, countByKey
- Cache
- cache 緩存
- persist 持久化
- Create
Spark編程模型
- 惰性運算:遇到Action時纔會真正的執行。
- Example
- 運行Spark方式
- CDH 集羣上運行Spark-Shell
- 在Shell中輸入spark-shell --master yarn-client
- 使用Zeppelin
- sudo docker run -p 8080:8080 --rm --name zeppelin apache/zeppelin:0.7.3
- https://zeppelin.apache.org
- 使用Spark-Submit遞交作業
- CDH 集羣上運行Spark-Shell
Spark API文檔
訪問官方文檔:https://spark.apache.org/docs/latest/
Value類型 Transformation 算子分類
Transformation-map
- map
- def map[U](f: (T) ⇒ U)(implicit arg0: ClassTag[U]):RDD[U]
- 生成一個新的RDD,新的RDD中每個元素均有父RDD通過作用func函數映射變換而來
- 新的RDD叫做MappedRDD
- Example
val rd1 = sc.parallelize(List(1, 2, 3, 4, 5, 6), 2)
val rd2 = rd1.map(x => x * 2)
rd2.collect()
rd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[2] at
parallelize
rd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[3] at map
res1: Array[Int] = Array(2, 4, 6, 8, 10, 12)
Transformation-mapPartitions
- mapPartitions
- def mapPartitions[U](f: (Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]):
RDD[U] - 獲取到每個分區的迭代器
- 對每個分區中每個元素進行操作
- def mapPartitions[U](f: (Iterator[T]) => Iterator[U],
- Example
val rd1 = sc.parallelize(List("20180101", "20180102", "20180103", "20180104", "20180105",
"20180106"), 2)
val rd2 = rd1.mapPartitions(iter => {
val dateFormat = new java.text.SimpleDateFormat("yyyyMMdd")
iter.map(dateStr => dateFormat.parse(dateStr))
})
rd2.collect()
res1: Array[java.util.Date] = Array(Mon Jan 01 00:00:00 UTC 2018, Tue Jan 02 00:00:00 UTC 2018, Wed Jan 03
00:00:00 UTC 2018, Thu Jan 04 00:00:00 UTC 2018, Fri Jan 05 00:00:00 UTC 2018, Sat Jan 06 00:00:00 UTC 2018)
Transformation-flatMap
- flatMap
- def flatMap[U](f: (T) ⇒ TraversableOnce[U])(implicit arg0: ClassTag[U]): RDD[U]
- 將RDD中的每個元素通過func轉換爲新的元素
- 進行扁平化:合併所有的集合爲一個新集合
- 新的RDD叫做FlatMappedRDD
- Example
val rd1 = sc.parallelize(Seq("I have a pen",
"I have an apple",
"I have a pen",
"I have a pineapple"), 2)
val rd2 = rd1.map(s => s.split(" "))
rd2.collect()
val rd3 = rd1.flatMap(s => s.split(" "))
rd3.collect()
rd3.partitions
res136: Array[Array[String]] = Array(Array(I, have, a, pen), Array(I, have, an, apple), Array(I, have, a, pen), Array(I, have,
a, pineapple))
res137: Array[String] = Array(I, have, a, pen, I, have, an, apple, I, have, a, pen, I, have, a, pineapple)
Transformation-flatMap
Transformation-union
- union
- def union(other: RDD[T]): RDD[T]
- 合併兩個RDD
- 元素數據類型需要相同,並不進行去重操作
- Example
val rdd1 = sc.parallelize(Seq("Apple", "Banana", "Orange"))
val rdd2 = sc.parallelize(Seq("Banana", "Pineapple"))
val rdd3 = sc.parallelize(Seq("Durian"))
val rddUnion = rdd1.union(rdd2).union(rdd3)
rddUnion.collect.foreach(println)
res1: Array[String] = Array(Apple, Banana, Orange, Banana, Pineapple, Durian)
Transformation-distinct
- distinct
- def distinct(): RDD[T]
- 對RDD中的元素進行去重操作
- Example
val rdd1 = sc.parallelize(Seq("Apple", "Banana", "Orange"))
val rdd2 = sc.parallelize(Seq("Banana", "Pineapple"))
val rdd3 = sc.parallelize(Seq("Durian"))
val rddUnion = rdd1.union(rdd2).union(rdd3)
val rddDistinct = rddUnion.distinct()
rddDistinct.collect()
res1: Array[String] = Array(Orange, Apple, Banana, Pineapple, Durian)
Transformation-filter
- filter
- def filter(f: (T) ⇒ Boolean): RDD[T]
- 對RDD元素的數據進行過濾
- 當滿足f返回值爲true時保留元素,否則丟棄
- Example
val rdd1 = sc.parallelize(Seq("Apple", "Banana", "Orange"))
val filteredRDD = rdd1.filter(item => item.length() >= 6)
filteredRDD.collect()
res1: Array[String] = Array(Banana, Orange)
Transformation-intersection
- interesction
- def intersection(other: RDD[T]): RDD[T]
- def intersection(other: RDD[T], numPartitions: Int): RDD[T]
- def intersection(other: RDD[T], partitioner: Partitioner)(implicit ord: Ordering[T] = null): RDD[T]
- 對兩個RDD元素取交集
- Example
val rdd1 = sc.parallelize(Seq("Apple", "Banana", "Orange"))
val rdd2 = sc.parallelize(Seq("Banana", "Pineapple"))
val rddIntersection = rdd1.intersection(rdd2)
rddIntersection.collect()
res1: Array[String] = Array(Banana)
Key-Value類型 Transformation 算子分類
Transformation-groupByKey
- groupByKey
- def groupByKey(): RDD[(K, Iterable[V])]
- def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
- def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
- 對RDD[Key, Value]按照相同的key進行分組
- Example
val scoreDetail = sc.parallelize(List(("xiaoming","A"), ("xiaodong","B"),
("peter","B"), ("liuhua","C"), ("xiaofeng","A")), 3)
scoreDetail.map(score_info => (score_info._2, score_info._1))
.groupByKey()
.collect()
.foreach(println(_))
scoreDetail: org.apache.spark.rdd.RDD[(String, String)] = ParallelCollectionRDD[110] at parallelize
(A,CompactBuffer(xiaoming, xiaofeng))
(B,CompactBuffer(xiaodong, peter))
(C,CompactBuffer(lihua))
Transformation-groupByKey
Transformation-reduceByKey
- reduceByKey
- Example
Transformation-reduceByKey
Transformation-aggregateByKey
- 如何分組計算平均值?
[(A,110),(A,130),(A,120),(B,200),(B,206),(B,206),(C,150),(C,160),(C,170)]
Transformation-join
Action 算子分類
Spark內存模型
Yarn資源調度過程
Spark內存結構
Spark內存優化方案
- Executor最大任務並行度
- TP = N/C
- 其中N=spark.executor.cores, C=spark.task.cpus
- 任務以Thread方式執行
- 活躍線程可使用內存範圍(1/2n, 1/n) why?
- 出現Executor OOM錯誤(錯誤代碼137,143等)
- 原因:Executor Memory達到上限
- 解決辦法:
- 增加每個Task內存使用量
- 增大最大Heap值
- 降低spark.executor.cores數量
- 或者降低單個Task內存消耗量
- 每個partition對應一個任務
- 非SQL類應用 spark.default.parallism
- SQL類應用 spark.sql.shuffle.partition
- 增加每個Task內存使用量
Spark案例介紹
Spark計算PV
- 通過sc.textFile()讀入日誌文件
- 按分隔符切分每行日誌文件提取元素
- filter無法識別的URL
- 以pageId爲Key,並使用reduceByKey進行聚合操作
Spark計算UV
- 通過sc.textFile()讀入日誌文件
- 按分隔符切分每行日誌文件提取元素
- filter無法識別的URL,以pageId和uid作爲聯合Key
- 對Key進行去重操作,使用reduceByKey進行聚合操作
Spark計算頁面平均訪問時間
- 步驟1:map+filter
- 過濾無效日誌
- 解析product_id和uid
- 步驟2:mapPartitions
- 解析日誌中的時間
- Date String Format -> Long
- 步驟3:groupByKey + flatMap
- 根據uid進行分組
- 組內對於(product_id, visit_time)集合按照時間升序排序
- 依次計算時間差作爲頁面停留時間
- 步驟4:map + aggregateByKey + mapValues
- (uid, (product_id, duration)) 轉換成 (product_id, duration)
- 計算平均值
使用DataFrame寫入MySQL
- 什麼是DataFrame
- 以RDD爲基礎的分佈式數據集,類似於傳統數據庫中的表
- 在RDD基礎上引入了Schema元信息
- DataFrame所表示的二位數據集每一列都帶有名稱和類型
- 藉助DataFrame API提供了jdbc方法保存數據