1 Spark核心編程
Spark計算框架爲了能對數據進行高併發和搞吞吐的處理,封裝了三大數據結構,分別是:
- RDD:彈性分佈式數據集
- 累加器:分佈式共享只寫變量
- 廣播變量:分佈式共享只讀變量
1.1 RDD
代表是一個彈性的,不可變,可分區,裏面的元素可並行計算的集合
彈性:
存儲的彈性:內存與磁盤的自動切換
容錯的彈性:數據丟失可以自動修復
計算的彈性:計算出錯重試機制
分片的彈性:可根據需要重新分片
分佈式: 數據存儲在大數據集羣不同節點上
數據集: RDD封裝了計算邏輯,並不保存數據
數據抽象: RDD是一個抽象類,需要子類實現
不可變: RDD封裝了計算邏輯,是不可以改變的,想要改變,只能產生新的RDD,在新的RDD裏面封裝計算邏輯
可分區,並行計算
1.2 核心屬性
1.2.1 分區列表
RDD數據結構中存在分區列表,用於執行任務時並行計算,是實現分佈式計算的重要屬性
1.2.2 分區計算函數
計算時,使用分區計算函數對每個分區進行計算
1.2.3 RDD之間的依賴關係
RDD是計算模型的封裝,計算模型進行組合時,需要建立依賴關係
1.2.4 分區器(可選)
只有數據爲KV類型數據時,可以通過設定分區器自定義數據的分區,簡單說就是給任務分配不同的分區
1.2.5 首選位置(可選)
可以根據計算節點的狀態選擇不同的節點位置進行計算
1.3 實現原理
RDD體現了裝飾者設計模式,一層一層進行裝飾,與IO基本類似
RDD執行是延遲加載的
只有當collect進行採集的時候,纔會執行。
1.4 執行原理
執行時,先申請資源,然後將應用程序的數據處理邏輯分解成一個一個計算任務,將任務分配給資源不同節點,然後按照計算模型進行數據計算,最後得到計算結果
1)啓動Yarn集羣環境
2)Spark通過申請資源創建調度節點和計算節點
3)Spark框架根據需求將計算邏輯按分區劃分成不同的任務
4)調度節點將任務根據計算節點狀態發送到對應的計算節點進行計算
1.5 基礎編程
1.5.1 RDD創建
1)從集合(內存)中創建RDD
Spark提供了兩種方法:parallelize和makeRDD
object SparkCollect {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[Int] = sparkContext.parallelize(
List(1, 2, 4, 3)
)
val rdd2: RDD[Int] = sparkContext.makeRDD(
List(1, 2, 3, 4)
)
rdd1.collect().foreach(println)
rdd2.collect().foreach(println)
sparkContext.stop()
}
}
從底層代碼來看,makeRDD其實就是parallelize方法
2)從外部存儲(文件)創建RDD
外部數據集創建RDD:本地的文件系統,所有Hadoop支持的數據集,比如HDFS,HBase等
object SparkCollect {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)
val fileRDD: RDD[String] = sparkContext.textFile("D:\\1input\\word")
fileRDD.collect().foreach(println)
sparkContext.stop()
}
}
3)從其他RDD創建
通過一個RDD運算完後,再產生新的RDD。
4)直接創建RDD(new)
1.5.2 RDD並行度與分區
讀取內存數據時
對於:val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4,5,6), 3)
底層進行數據分區的時候關鍵源碼:
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
(0 until numSlices).iterator.map { i =>
val start = ((i * length) / numSlices).toInt
val end = (((i + 1) * length) / numSlices).toInt
(start, end)
}
}
case _ =>
val array = seq.toArray // To prevent O(n^2) operations for List etc
positions(array.length, numSlices).map { case (start, end) =>
array.slice(start, end).toSeq
}.toSeq
通過元組(start,end)進行操作,左閉右開截取數組數據,放入分區文件
讀取文件數據的分區和讀取內存中集合數據的分區是不一樣的
讀取文件時:
bytesRemaining:剩餘的字節
splitSize:它總共的字節
goalSize:每個分區的數據的大小
按照hadoop的行讀取規則,依照偏移量進行讀取,不足一行讀取一行:
詳細的過程:根據自定義的分區數,或者默認的2個分區,然後(long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);)計算得出目標分區的每個分區的數據,bytesRemaining)/splitSize > SPLIT_SLOP 這個條件成立,則進去一個分區,與hadoop的行讀取類似
* RDD轉換算子
1)map
函數說明:將處理的數據逐條進行映射轉換,這裏的轉換可以是類型的轉換,也可以是值的轉換
注意:分區不變,
mapValues操作的是隻有value,不改變key
val dataRDD: RDD[Int] = rdd.map(
num => {
num * 2
}
)
2)mapPartitions
函數說明:以分區爲單位發送到計算節點進行處理,這裏的處理是指可以進行任意的處理,哪怕是過濾數據
返回的值需要可以迭代,可變可不變
注意:分區數據沒有處理完就不能釋放,容易造成內存溢出
3)mapPartitionsWithIndex
函數說明:處理的同時可以獲取當前分區索引
val dataRDD1 = dataRDD.mapPartitionsWithIndex(
(index, datas) => {
datas.map(index, _)
}
)
4)flatMap
函數說明:將數據先進行扁平化,然後再進行映射處理,所以稱作扁平映射
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Any] = sparkContext.makeRDD(
List(List(1, 2), 3, List(4, 5)), 1
)
val value: RDD[Any] = rdd.flatMap(
data => {
data match {
case list: List[_] => list
case d => List(d)
}
}
)
value.collect().foreach(println)
sparkContext.stop()
}
}
5)glom
函數說明:將同一個分區的數據轉換爲相同類型的數據處理
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4,2,1, 3, 44, 55), 2
)
val value: RDD[Array[Int]] = rdd.glom()
value.collect().foreach(
data =>{
println("--------------------")
for(i <- data){
println(i)
}
}
)
sparkContext.stop()
}
}
6)groupBy
函數說明:數據根據指定規則進行分組,分區默認不變,數據會被打亂,此過程爲shuffle
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4,2,1, 3, 44, 55), 2
)
val value: RDD[(Int, Iterable[Int])] = rdd.groupBy(
data => {
data % 10
}
)
value.collect().foreach(
data => {
println("\nkey is :" + data._1)
for(word <- data._2){
print(word + " ")
}
}
)
sparkContext.stop()
}
}
7)filter
函數說明:按照指定的規則將數據進行過濾,有可能出現數據傾斜
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4,2,1, 3, 44, 55), 2
)
val value: RDD[Int] = rdd.filter(_ % 2 == 0)
value.collect().foreach(println)
sparkContext.stop()
}
}
8)sample
函數說明:根據規則從數據集中抽取數據,第一個參數爲是否放回,第二個參數是概率(放回爲大於1,不放回取0~1之間),第三個參數是隨機種子
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 3, 44, 55), 2
)
val value: RDD[Int] = rdd.sample(true, 2,1)
value.collect().foreach(
data =>{
print( data +" ")
}
)
println()
val value1: RDD[Int] = rdd.sample(true, 2,1)
value1.collect().foreach(
data =>{
print(data + " ")
}
)
}
}
9)distinct
函數說明:將數據集中的重複數據去重
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),1)
val dataRDD1 = dataRDD.distinct()
10)coalesce
函數說明:縮減分區,提高小數據集的執行效率
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 3, 44, 55), 3
)
val value1: RDD[Int] = rdd.coalesce(2)
val value: RDD[Int] = value1.mapPartitionsWithIndex(
(par, data) => {
println("分區號:" + par)
for (num <- data) {
print(num + " ")
}
println()
data
}
)
value.collect().foreach(println)
}
}
11)repartition
函數說明:內部執行的就是coalesce,只是將coalesce默認的參數shuffle爲false改成了true,一個縮減分區用coalesce,擴大分區採用repartition
12)sortBy
函數說明:按照指定的規則,第一個參數指定規則,第二個參數爲是否正序排列,第三個參數是分成多少個分區
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 9, 44, 55), 3
)
val value: RDD[Int] = rdd.sortBy(num => num%10, true, 6)
println(value.getNumPartitions)
value.collect().foreach(println)
}
}
13)pipe
函數說明:管道,針對每個分區,都調用一次shell腳本,返回輸出的RDD
14)intersection
函數說明:對源RDD和參數RDD求交集後返回一個新的RDD
val rdd: RDD[Int] = sparkContext.makeRDD(
List(4, 2, 1, 9, 44, 55), 3
)
val rdd2: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
val rdd3: RDD[Int] = rdd.intersection(rdd2)
rdd3.collect().foreach(println)
15)union
函數說明:求並集
16)substract
函數說明:求差集,rdd1-rdd2
注意:數據類型不一致會出錯
17)zip
函數說明:rdd1和rdd2兩兩打包形成元組
注意:類型可以不一致
18)partitionBy
函數說明:將數據按照指定的partitioner進行分區,Spark默認的分區器是HashPartitioner
默認分區器:
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
List((1,"aaa"),(2,"bbb"),(3,"ccc")), 3
)
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(3))
val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
(num, data) => {
data.map(
d => {
(num, d)
}
)
}
)
rdd3.collect().foreach(println)
sparkContext.stop()
}
}
自定義分區器:
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
List((1,"aaa"),(20,"bbb"),(30,"ccc")), 3
)
val rdd2: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(3))
val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
(num, data) => {
data.map(
d => {
(num, d)
}
)
}
)
rdd3.saveAsTextFile("output")
rdd3.collect().foreach(println)
sparkContext.stop()
}
class MyPartitioner(num:Int) extends Partitioner{
override def numPartitions: Int = num
override def getPartition(key: Any): Int = {
val zzuli: Int = key.toString.toInt
if(zzuli < 10)
0
else if(zzuli == 10)
1
else
2
}
}
}
19)reduceByKey
函數說明:將數據按照相同的key對value進行聚合操作
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 1),("a",22), ("b", 2), ("c", 3)))
val value: RDD[(String, Int)] = rdd.reduceByKey((a:Int,b:Int)=>{
a + b
})
value.collect().foreach(println)
sparkContext.stop()
}
}
20)groupByKey
函數說明:按照key來進行分組
val dataRDD1 =
sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.groupByKey()
21)aggregateByKey
函數說明:將數據根據不同的規則進行分區計算和分區間計算
案例:取每個分區內相同key的最大值然後分區間相加
此算子是函數柯里化,存在兩個參數列表
第一個參數列表中的參數表示初始值
第二個參數列表含有兩個參數,一個爲分區內的計算規則,一個分區間的計算規則
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Aggregate {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setAppName("zzuli").setMaster("local[*]")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(
("a", 1), ("a", 2), ("c", 3),
("b", 4), ("c", 5), ("c", 6))
, 2
)
val result: RDD[(String, Int)] = rdd.aggregateByKey(0)(
(x, y) => math.max(x, y), (x, y) => x + y
)
result.collect().foreach(println)
}
}
22)foldByKey
當分區內計算規則和分區間計算規則相同時,直接一個參數就能達到aggregate算子的效果
23)combineByKey
函數說明:對k-v型rdd進行聚集操作的聚集函數,類似於aggregate,combineByKey允許用戶返回值的類型與輸入不一致
練習:求每個key的平均值
第一個參數:改變數據格式
第二個參數:分區內計算
第三個參數:分區間計算
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val list = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
val sparkConf = new SparkConf().setAppName("zzuli").setMaster("local")
val sparkContext = new SparkContext(sparkConf)
val rdd: RDD[(String, Int)] = sparkContext.makeRDD(list, 2)
val combineRdd: RDD[(String, (Int, Int))] = rdd.combineByKey(
(a: Int) => ((a, 1)),
(acc: (Int, Int), v: Int) => (acc._1 + v, acc._2 + 1),
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
)
val value: RDD[(String, Int)] = combineRdd.mapValues(
data => {
(data._1 / data._2)
}
)
value.collect().foreach(println)
sparkContext.stop()
}
}
24)sortByKey
函數說明:按照(k,v)中的k進行排序,如果是自定義的k值,需要實現Ordered接口
val value: RDD[(String, Int)] = rdd1.sortByKey(true)
25)join
函數說明:相當於sql中的內連接
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("a", 9), ("a", 8)))
val value: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
value.collect().foreach(println)
}
}
26)leftOuterJoin
函數說明:類似於sql中的左外連接,遍歷每個(k,v),返回存在的值
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("s", 8)))
val value: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)
value.collect().foreach(println)
}
}
27)cogroup
函數說明:在(k,v)的RDD上調用,返回(K,(iterable,Iterable))的RDD
package com.baidu.exer
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Cogroup1 {
def main(args: Array[String]): Unit = {
val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
val sparkContext = new SparkContext(sparkConf)
val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("c", 5)))
val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("c", 8)))
val value: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
value.collect().foreach(println)
}
}