Spark葵花寶典:一小時速成Spark

Spark簡介

什麼是Spark?

Spark是一個快速、分佈式、可擴展(隨時可以進行節點的擴充)、容錯(節點宕機了。那麼它可以重新構建恢復這個數據)的集羣計算框架。低延遲的複雜分析,因爲Spark的低延遲,延遲低是因爲Spark是在內存裏面計算的。(Spark已經成爲Apache軟件基金會旗下的頂級開源項目)

爲什麼要使用 Spark 替換 MapReduce?

MapReudce不適合迭代和交互式任務,Spark主要爲交互式查詢和迭代算法設計,支持內存存儲和高效的容錯恢復。Spark擁有MapReduce具有的優點,但不同於MapReduce,Spark中間輸出結果可以保存在內存中,減少讀寫HDFS的次數。

Spark特點

  • 快速:因爲 Spark 是在內存裏面計算的
  • 易用性:Spark 支持使用Scala、Python、Java及R語言快速編寫應用。
  • 通用性:Spark可以與SQL、Streaming及複雜的分析良好結合。
  • 隨處運行

Spark的生態圈:(瞭解)

  • Spark Core:Spark核心,提供底層框架及核心支持。包含Spark的基本功能,包括任務調度、內存管理、容錯機制等。Spark Core內部定義了RDDS,並提供了很多API來創建和操作RDD
  • Tachyon:Tachyon是一個分佈式內存文件系統,可以理解爲內存中的HDFS。
  • BlinkDB:一個用於在海量數據上運行交互式SQL查詢的大規模並行查詢引擎,它允許用戶通過權衡數據精度來提升查詢響應時間,其數據的精度被控制在允許的誤差範圍內。
  • Spark SQL:可以執行SQL查詢,包括基本的SQL語法和HiveQL語法。讀取的數據源包括Hive表、Parquent文件、JSON數據、關係數據庫(如MySQL)等。
  • Spark Streaming:流式計算。比如,一個網站的流量是每時每刻都在發生的,如果需要知道過去15分鐘或一個小時的流量,則可以使用Spark Streaming來解決這個問題。
  • MLBase:MLBase是Spark生態圈的一部分,專注於機器學習,讓機器學習的門檻更低,讓一些可能並不瞭解機器學習的用戶也能方便地使用MLBase。MLBase分爲四部分:MLlib、MLI、ML Optimizer和MLRuntime。
  • MLlib:MLBase的一部分,MLlib是Spark的數據挖掘算法庫,實現了一些常見的機器學習算法和實用程序,包括分類、迴歸、聚類、協同過濾、降維以及底層優化。
  • GraphX:圖計算的應用在很多情況下處理的數據都是很龐大的,比如在移動社交上面的關係等都可以用圖相關算法來進行處理和挖掘,但是如果用戶要自行編寫相關的圖計算算法,並且要在集羣中應用,那麼難度是非常大的。而使用Spark GraphX就可以解決這個問題,它裏面內置了很多的圖相關算法。
  • SparkR:SparkR是AMPLab發佈的一個R開發包,使得R擺脫單機運行的命運,可以作爲Spark的Job運行在集羣上,極大地擴展了R的數據處理能力。

Scala編程基礎

什麼是Scala?

Scala是一門多範式的編程語言,設計初衷是要集成面向對象編程和函數式編程的各種特性。因此Scala是一種純面向對象的語言,每個值都是對象。同時Scala也是一種函數式編程語言,其函數也能當成值來使用。由於Scala整合了面嚮對象語言和函數式編程的特性,Scala相對於Java、C#、C++等其他語言更加簡潔。Scala源代碼被編譯成Java字節碼,所以它可以運行於JVM之上,並可以調用現有的Java類庫。Scala一開始就打算基於Java的生態系統發展自身,而這令Scala受益匪淺。

Scala特點

  • 面向對象:把相關的數據和方法組織爲一個整體來看待,從更高的層次來進行系統建模,更貼近事物的自然運行模式。
  • 函數式編程:函數是一等公民,允許自定義控制語句、改造語言。
  • 靜態類型:編譯時進行檢查。
  • 可擴展的
    • 隱式類: 允許給已有的類型添加擴展方法
    • 字符串插值: 可以讓用戶使用自定義的插值器進行擴展

Scala的編程模式

  • 交互式編程:REPL(Read Eval Print Loop:交互式解釋器)
    • 讀取:讀取用戶輸入,解析輸入的內容。
    • 執行:執行輸入的scala代碼
    • 打印:輸出結果
    • 循環 :循環操作以上步驟直到用戶確認退出。
  • 腳本編程:使用文本編輯器創建一個 .scala 的文件來執行代碼
    • 使用 scalac 命令編譯
    • 使用scala命令執行程序

數據類型

基本數據類型:

數據類型 描述
Byte 8位有符號補碼整數。數值區間爲 -128 到 127
Short 16位有符號補碼整數。數值區間爲 -32768 到 32767
Int 32位有符號補碼整數。數值區間爲 -2147483648 到 2147483647
Long 64位有符號補碼整數。數值區間爲 -9223372036854775808 到 9223372036854775807
Float 32 位, IEEE 754 標準的單精度浮點數
Double 64 位 IEEE 754 標準的雙精度浮點數
Char 16位無符號Unicode字符, 區間值爲 U+0000 到 U+FFFF
String 字符序列
Boolean true或false
Unit 表示無值,和其他語言中void等同。用作不返回任何結果的方法的結果類型。Unit只有一個實例值,寫成()。
Null null 或空引用
Nothing Nothing類型在Scala的類層級的最底端;它是任何其他類型的子類型。
Any Any是所有其他類的超類
AnyRef AnyRef類是Scala裏所有引用類(reference class)的基類

常用的基本數據類型

Int:整型數值,這意味着它只能保存整數。

Float與Double:都是表示浮點數,浮點數後面有f或者F後綴時,表示這是一個Float類型,否則就是一個Double類型的。

String:字符串,其用法是用雙引號包含一系列字符。

Boolean:只能保存兩個特殊的值,ture和flase。

Unit:在Java中創建一個方法時經常用void表示該方法無返回值,而Scala中沒有void關鍵字,Scala中用Unit表示無值,等同於Java中的void。

數據類型中還有一個:

Range類型:是一種數據範圍或序列, 支持Range的類型包括Int、Long、Float、Double、Char、BigInt和BigDecimal。

變量(var)和常量(val)

var:可以在它的聲明週期中被多次賦值

val:一旦初始化了,val就不能再被賦值

注意:val 對併發或分佈式編程很有好處

判斷

if (表達式) {
	//代碼塊	
} else if(表達式){
    //代碼塊
}else{
	//代碼塊
}

數組

定長數組

定義定長數組的兩種方式:

  • val arr = new Array[Int](5)
  • val arr = Array(1,2,3,4,5)

操作定長數組:

  • arr.length:返回數組長度
  • arr.head:返回數組第一個元素
  • arr.tail:返回除了第一個元素
  • arr.isEmpty:判斷數組是否爲空
  • arr.contains(數據):判斷數組是否包含某個元素

變長數組

數組緩衝ArrayBuffer

定義變長數組之前需要先導入包:

import scala.collection.mutable.ArrayBuffer

定義變長數組:

val bufArr = new ArrayBuffer[Int]()

操作變長數組:定長數組的操作均可以使用

  • bufArr += 1:在末尾添加元素

  • bufArr.trimEnd(n):移除最後的n個元素

  • bufArr.insert(n-1,x):中間插入數組元素(第n個元素前插入x元素)。

  • bufArr.insert(n-1,x,y,z):插入多個元素

  • bufArr.remove(n-1):刪除數組第n個元素

  • bufArr.toArray:變成定長數組

數組其他常用對的方法:

需要導入包:import Array._

  • concat(arr1,arr2):合併數組
var arr1 = Array(1,2)
var arr2 = Array(3,4)
var arr3 = concat(arr1,arr2)
  • fill(len, data):返回數組,長度爲第一個參數指定,同時每個元素使用第二個參數進行填充。
val arr:Array[Int]=fill(3)(2)
  • ofDim(len):創建指定長度的數組,或創建二維數組
val arr1:Array[Int]=ofDim(3)
val arr2:Array[Array[Int]]=ofDim(3,3)  //二維數組
  • range(start,end,step):創建指定區間內的數組,step 爲每個元素間的步長
val arr3:Array[Int]=range(1,10,2)
val arr4:Array[Int]=range(1,10)

連接數組連接兩個數組既可以使用操作符++,也可以使用concat方法。但是使用concat方法需要先使用import Array._引入包。

創建多維數組

var arrInt = Array(Array(2,3),Array(5,6))

方法和函數

方法和函數:二者在語義上的區別很小。Scala 方法是類的一部分,而函數是一個對象可以賦值給一個變量。換句話來說在類中定義的函數即是方法。

使用 val 語句可以定義函數def 語句定義方法

方法

方法的定義方式

 object Test {
    //方法
    def addInt( a:Int, b:Int) :Int = {
        var sum:Int =0
        sum = a + b
        return sum
    }
}

//調用方法形式1:帶類名調用
Test.addInt(1,2)

在這裏插入圖片描述

//既不帶參數也不返回結果
def printMe() : Unit = {
     println("Hello, Scala!")
}

//調用方法形式2:直接調用
printMe()

方法的參數

  • 可變參數:Scala允許方法的最後一個參數可以是重複的,即不需要指定參數的個數,可以向方法傳入可變長度參數列表。
def printStrings( args:String*) = {
  var i :Int = 0;
  for (arg <- args){
    println("arg value[" + i + "] = "  + arg);
    i += 1;
  }
}
printStrings("abc", "cd")
  • 默認值參數:在定義方法的過程中可以給參數賦默認值。這種情況下,如果調用方法時不傳入參數則使用默認值,如果傳入參數則根據實際參數值進行計算。
def addInt ( a:Int=5, b:Int=7) :Int = {
  var sum:Int = 0
  sum = a+b
  return sum
}

函數

函數:函數是一個對象可以賦值給一個變量,使用val進行定義。函數可作爲一個參數傳入到方法中,而方法不行。

函數的定義

val f = (x:Int,y:Int) =>  { 
     x + y
     x + y + 10
}

循環

while循環

var a = 10;
while( a < 20 ){
   println( "Value of a: " + a );
   a = a + 1;
}

注意:++i和i++在Scala裏不起作用,要在得到同樣效果,必須要麼寫成i=i+1,要麼寫成i+=1

do while循環

var a = 10;
do{
   println( "Value of a: " + a );
   a = a + 1;
}while( a < 20 )

for循環

var a = 0;
for( a <- 1 to 10){
   println( "Value of a: " + a );
}

//----等同於

for( a <- 1 until 10){
   println( "Value of a: " + a );
}

注意:左箭頭 <- 用於爲變量 a 賦值。

for循環和數組

var a = 0;
val numList = Array(1,2,3,4,5,6);
for( a <- numList ){
   println( "Value of a: " + a );
}

for循環和集合

var a = 0;
val numList = List(1,2,3,4,5,6);
for( a <- numList ){
   println( "Value of a: " + a );
}

for循環和過濾

語法

for( var x <- List
      if condition1; if condition2...
   ){
   statement(s);
}

示例:

var a = 0;
val numList = List(1,2,3,4,5,6,7,8,9,10);
 
for( a <- numList
     if a != 3; if a < 8 ){
   println( "Value of a: " + a );
}

for循環和yield

語法:將 for 循環的返回值作爲一個變量存儲

var retVal = for{ var x <- List
     if condition1; if condition2...
}yield x

示例:

var a = 0;
val numList = List(1,2,3,4,5,6,7,8,9,10);
 
var retVal = for{ a <- numList 
  if a != 3; if a < 8
}yield a
 
for( a <- retVal){
   println( "Value of a: " + a );
}

foreach方法

val arr = Array(1,2,3,4)
arr.foreach((x:Int) => println(x))

//輸出結果: 1,2,3,4

集合

Scala 集合分爲可變的和不可變的集合。

可變集合:可以在適當的地方被更新或擴展。這意味着你可以修改,添加,移除一個集合的元素;

不可變集合:相比之下,永遠不會改變。不過,你仍然可以模擬添加,移除或更新操作。但是這些操作將在每一種情況下都返回一個新的集合,同時使原來的集合不發生改變;如List

Collection : List、Set、Map、Tuple

List列表

定義列表:

方式1:

val list:List[String] = List("baidu", "google")

方式2:Nil 代表一個空,最後一個必須爲Nil ,否則會報錯

val list:List[Int] = 1::2::3::Nil

鏈接列表:

  • ::: 運算符是將集合中的每一個元素加入到集合中去,並且左右兩邊都必須是集合.
val list1 = "Baidu"::("google"::("bing"::Nil))
val list2 = "Facebook"::"taoboa"::Nil
val list3 = list1:::list2
  • 使用List.concat(list1, list2)
val list1 = "Baidu"::("google"::("bing"::Nil))
val list2 = "Facebook"::"taoboa"::Nil
val list4 = List.concat(list1,list2)

操作列表

  • ::list1.::("baidu")
  • :::list1.:::(("baidu"::Nil))
  • head:獲取列表的第一個元素
  • init:返回所有元素,除了最後一個
  • drop(n):丟棄前n個元素,並返回新列表
  • filter:輸出符號指定條件的所有元素。
val num:List[Int] = List(1,2,3,4,5)
val list = num.filter(x => x%2==0)  //List(2, 4)
  • map:通過給定的方法將所有元素重新計算
val num:List[Int] = List(1,2,3,4,5)
val list = num.map(x => x*2) //List(2, 4, 6, 8, 10)
  • mkString:使用分隔符將列表所有元素作爲字符串顯示
val num:List[Int] = List(1,2,3,4,5)
val list = num.mkString(",")   //1,2,3,4,5
  • foreach:將函數應用到列表的所有元素
val num:List[Int] = List(1,2,3,4,5)
val list = num.foreach(x=>println(x)) //函數
//---代碼塊---
val list = num.foreach(x=>{
    println(x)
})

ListBuffer可變列表

使用可變列表首先要導入:

import scala.collection.mutable.ListBuffer

定義可變列表:

val list = ListBuffer[Int](1, 2, 3) 

操作可變列表:

  • 添加元素
    • +=list += 4
    • append(data)list.append(4)
  • 刪除元素
    • remove(index)list.remove(0)刪除下表爲0的元素

Set集合

默認情況下,Scala 使用的是不可變集合,如果你想使用可變集合,需要引用 scala.collection.mutable.Set 包。Set是沒有重複的對象集合,所有的元素都是唯一的
定義集合:

val set =  Set(1,2,3,3) // 元素唯一性
val set =  Set(1,2,3)
//二者結果相同

操作集合

  • head:返回集合第一個元素
  • tail:返回一個集合,包含除了第一元素之外的其他元素
  • isEmpty:判斷是否是一個空數組
  • foreach:遍歷集合
val set =  Set(1,2,3,3)  
set.foreach(x => { println(x)} )
  • 添加元素(前提是引入了上面的包變成可變集合
    • add(data)
    • +=
  • 刪除元素(前提是引入了上面的包變成可變集合
    • remove(data)
    • -=
  • 鏈接兩個集合
    • ++
    • set1.++(set2)
val set1 = Set(1,2,3,4)
val set2 = Set(3,4,5,6)
val set3 = set1 ++ set2
val set4 = set1.++(set2)
  • 查看兩個集合的交集元素
    • set1.&(set2)
    • set.intersect(set2)
val set1 = Set(1,2,3,4)
val set2 = Set(3,4,5,6)
val set5 = set1.&(set2)
val set6 = set1.intersect(set2)
  • 查找集合中最大與最小元素
    • set.max
    • set.min

Map映射

Map(映射)是一種可迭代的鍵值對(key/value)結構。所有的值都可以通過鍵來獲取。

同樣的 Map 有兩種類型,可變與不可變,區別在於可變對象可以修改它,而不可變對象不可以。

默認情況下 Scala 使用不可變 Map。如果你需要使用可變集合,你需要顯式的引入

import scala.collection.mutable.Map

定義 Map 映射:

val map1 = Map("a" -> 1, "b" ->2, "c" ->3)  

操作 Map 映射:

  • keys

  • values

  • isEmpty

  • foreach

val map = Map("a" -> 1, "b" ->2, "c" ->3)
println(map.keys)  //Set(a, b, c)
println(map.values) //MapLike.DefaultValuesIterable(1, 2, 3)
println(map.isEmpty) //false
map.foreach(x=>println(x))
  • 添加映射
    • +=
val map1 = Map("a" -> 1, "b" ->2, "c" ->3)
map1 += ("d" -> 4)

var map = Map[String,Int]("a" -> 1,"b" -> 2) //引用可變,支持讀寫操作;
map += ("c" -> 3) //新增 
println(map) 

val map2 = Map[String,Int]("a" -> 1,"b" -> 2) //引用不可變,只能第一次寫入值,之後只能讀取;
map2 += ("c" -> 3) //此時不能加,直接報錯; 

val map3 = scala.collection.mutable.Map[String,String]() //引用不可變,支持讀寫操作;
Map3 += (“c” -> 3)
  • 鏈接兩個映射:如果 Map 中存在相同的 key,合併後的 Map 中的 value 會被最右邊的 Map 的值所代替。
    • ++
    • map.++()
val map1 = Map("a" -> 1, "b" ->2, "c" ->3)
val map2 = Map("c" -> 4, "d" ->5)
val map3 = map1 ++ map2
val map4 = map1.++(map2)

Tuple元組

與列表一樣,元組也是不可變的,但與列表不同的是元組可以包含不同類型的元素。

Scala 支持的元組最大長度爲 22。

定義 Tuple 元組:元組的值是通過將單個的值包含在圓括號中構成的。

val t = (1, 3.14, "Fred")

val t = new Tuple3(1,2,"string")

使用t._1 訪問第一個元素, t._2 訪問第二個元素:

val t = (1, 3.14, "Fred")
println(t._1)  //1
println(t._2)  //3.14
println(t._3)  //"Fred"

補充:Scala Option(選項)類型用來表示一個值是可選的(有值或無值)。

迭代器

Scala Iterator(迭代器)不是一個集合,它是一種用於訪問集合的方法。迭代器的兩個基本操作是 nexthasNext

val map = Map("a" -> 1, "b" ->2, "c" ->3)
val iterator = map.iterator
while (iterator.hasNext){
   val ele = iterator.next()
   println("key:" + ele._1 + ";value:" + ele._2)
}

函數組合器

  • map:通過一個函數重新計算列表中所有元素,並且返回一個相同數目元素的新列表。

  • foreach:foreach沒有返回值,foreach只是爲了對參數進行作用。

  • filter:過濾移除使得傳入的函數的返回值爲false的元素。

val num:List[Int] = List(1,2,3,4,5)
num.map(x => x*2)
num.foreach(x => println(x*x + "\t"))
num.filter(x => x%2 ==0)
  • flatten:把嵌套的結構展開,或者說flatten可以把一個二維的列表展開成一個一維的列表
val list = List(List(1,2,3), List(3,4,5))
var newlist = list.flatten //List(1, 2, 3, 3, 4, 5)
  • flatMap:結合了map和flatten的功能,接收一個可以處理嵌套列表的函數,然後把返回結果連接起來。
val list:List[List[Int]] = List(List(1,2,3), List(3,4,5))
var newlist = list.flatMap(x => x.map(_*2)) //List(2, 4, 6, 6, 8, 10)
  • groupBy:對集合中的元素進行分組操作,結果得到的是一個Map。
val intList:List[Int] = List(1,2,3,4,5,6)
var newlist =  intList.groupBy(x=>x%2==0)
//結果: Map(false -> List(1, 3, 5), true -> List(2, 4, 6))

訪問修飾符

沒有指定訪問修飾符,默認情況下,訪問級別都是 public

  • 私有成員(Private):在嵌套類情況下,外層類甚至不能訪問被嵌套類的私有成員。
class Outer{
    class Inner{
        private def f(){println("f")}
        class InnerMost{
            f() 
        }
    }
    (new Inner).f()  //error
}
  • 保護成員(Protected):Protected只允許保護成員在定義了該成員的的類的子類中被訪問。
  • 公共成員(Public):在任何地方都可以被訪問。
class Outer {
   class Inner {
      def f() { println("f") }
      class InnerMost {
         f() // correct
      }
   }
   (new Inner).f() // correct  
}

Scala是一種純粹的面嚮對象語言,兩個重要的概念:類和對象。類是對象的抽象,也可以把類理解爲模板,對象纔是真正的實體。Scala中的類不聲明爲public。Scala 的類定義可以有參數,稱爲類參數;類參數在整個類中都可以訪問。

類的使用:

class Point(xc: Int, yc: Int) {
   var x: Int = xc
   var y: Int = yc
   def move(dx: Int, dy: Int) {
      x = x + dx
      y = y + dy
      println ("x : " + x);
      println ("y : " + y);
   }
}

一個Scala源文件中可以有多個類。可以使用 new 來實例化對象,並訪問類中的方法和變量

//scala文件
import java.io._
class Point(xc: Int, yc: Int) {
   var x: Int = xc
   var y: Int = yc
   def move(dx: Int, dy: Int) {
      x = x + dx
      y = y + dy
      println ("x 的座標點: " + x);
      println ("y 的座標點: " + y);
   }
}
object Test {
   def main(args: Array[String]) {
      val pt = new Point(10, 20);
      // 移到一個新的位置
      pt.move(10, 10);
   }
}

類的繼承:Scala 使用 extends 關鍵字來繼承一個類,子類會繼承父類的所有屬性和方法,Scala 只允許繼承一個父類。

Scala繼承一個基類跟Java很相似, 但我們需要注意以下幾點:

1、重寫一個非抽象方法必須使用override修飾符。

2、只有主構造函數纔可以往基類的構造函數裏寫參數。

3、在子類中重寫超類的抽象方法時,你不需要使用override關鍵字

class Location(val xc: Int, val yc: Int, val zc :Int) extends Point(xc, yc){
   var z: Int = zc
   override def move(dx: Int, dy: Int) {
      x = x + dx + 100
      y = y + dy + 100
      println ("x location  : " + x);
      println ("y location : " + y);
   }
   def move(dx: Int, dy: Int, dz: Int) {
      x = x + dx
      y = y + dy
      z = z + dz
      println ("x   : " + x);
      println ("y   : " + y);
      println ("z   : " + z);
   }
}

object Test {
   def main(args: Array[String]) {
      val loc = new Location(10, 20, 15);
      loc.move(10, 10);
   }
}

子類繼承父類中已經實現的方法需要使用關鍵字override,子類繼承父類中未實現的方法可以不用override關鍵字。

//Cat Animal 父子類方法的覆蓋
//使用eat方法,使用{}包容代碼,在方法裏使用println
abstract class Animal {
    def showName(str:String)={println("animal")}
    def eat(food:String)
}
class Cat extends Animal {
    override def showName(str:String)={println("cat")} //子類繼承父類中已經實現的方法需要使用關鍵字`override`
    def eat(food:String) = {println("fish")}//子類繼承父類中未實現的方法可以不用`override`關鍵字。
}

單例對象:在整個程序中只有這麼一個實例。Scala中沒有static關鍵字,因此Scala的類中不存在靜態成員。 Scala中使用單例模式時需要使用object定義一個單例對象。object 對象與類的區別在於object 對象不能帶參數。包含 main 方法的 object 對象可以作爲程序的入口點。

伴生對象:是一種特殊的單例對象,是一種相對概念。需要兩個條件

  • 在同一個源文件中

  • 對象名和類名相同

類和伴生對象之間可以相互訪問私有的方法和屬性。

構造器

構造器分爲兩類:主構造器(只有一個),輔助構造器(有多個)

主構造器直接在類名後面定義,每個類都有主構造器,主構造器的參數直接放在類名後,與類交織在一起
如果沒有定義構造器,類會有一個默認的空參構造器

class Point(xc: Int, yc: Int) {//主構造器
   var x: Int = xc
   var y: Int = yc
   def move(dx: Int, dy: Int) {
      x = x + dx
      y = y + dy
      println ("x 的座標點: " + x);
      println ("y 的座標點: " + y);
   }
}

輔助構造器定義,使用def this,必須調用主構造器,或者其他構造器。

class Person { //空參構造器
   var name:String = "Tom"
   var sex:String = "man"
   val age:Int =18
   println("main constructor")
   
   def this(name:String){ //輔助構造器
      this()   
      println("name constructor")
   }

   def this(name:String, sex:String) {
       this(name)
       println("name sex construtor")
   }
}

注意

  • 有兩類構造器,主構造器和輔構造器

  • 輔構造器的參數不能和主構造器的參數完全一致(參數個數、類型、順序)

  • 可以定義空參的輔助構造器,但是主構造器參數必須進行初始化賦值

  • 輔構造器的作用域只在方法中,主構造器的作用域是類中除去成員屬性和成員方法外的所有範圍

IDE中的Spark程序

SparkContext 介紹

​ 任何Spark程序都是以SparkContext對象開始的,因爲SparkContext是Spark應用程序的上下文和入口,都是通過SparkContext對象的實例來創建RDD。因此在實際Spark應用程序的開發中,在main方法中需要創建SparkContext對象,作爲Spark應用程序的入口,並在Spark程序結束時關閉SparkContext對象。

SparkContext初始化

通過SparkConf對象設置集羣配置的各種參數。

val conf = new SparkConf().setMaster("local").setAppName("appName")
var sc = new SparkContext(conf)

spark分佈式存儲

爲什麼使用分佈式存儲?| 分佈式存儲特點

  1. 數據分塊存儲在多臺機器上
  2. 每一數據塊都可以冗餘存儲在多臺機器上,以提高數據塊的高可用性

如何管理分佈式存儲

在另外一臺機器上啓動一個管理所有節點以及存儲在上面數據塊的服務

文件的概念

1: 存在於slave上的文件:表示真實存放數據的文件, 即本地磁盤文件

2: 存在於master上的文件:表示邏輯文件,它表示這個邏輯

分佈式存儲系統HDFS常見linux命令

  • 查看hdfs文件系統的方式: hadoop fs –ls /user/hadoop-twq

  • 上傳文件: hadoop fs -copyFromLocal word.txt /users/hadoop-twq

  • 下載文件: hadoop fs -get /users/hadoop-twq/word.txt

  • 設置數據塊的備份數:hadoop fs -setrep 2 /users/hadoop-twq/word.txt

  • 刪除文件的方式:hadoop fs -rm /users/hadoop-twq/word.txt

Spark分佈式計算概述

簡介:在每一個block所在的機器針對block數據進行計算,將結果彙總到計算master

原則: 移動計算而儘可能少的移動數據

含義: 其實就是將單臺機器上的計算擴展到多臺機器上進行並行計算

特點:就近計算,計算分攤到每個節點,基於性質相同的每個數據塊,同時使用相同方法來計算。

基本結構:計算主節點和資源主節點

數據的分區方式是什麼樣的?

文件的每一個block就是一個分區,當然我們也可以設置成2個block一個分區,對於key-value類型的數據的分區。我們可以根據key按照某種規則來進行分區,比如按照key的hash值來分區。

在計算伊始讀取分區數據的時候,會發生從其他機器節點通過網絡傳輸讀取數據嗎?

可能會發生,但是需要儘量避免,我們需要遵循移動計算而不移動數據的原則。每一個數據塊都包含了它所在的機器的信息,我們需要根據這個數據塊所在的機器,然後將計算任務發送到對應的機器上來執行,這個就是計算任務的本地性

每一步出現的數據都是一份存儲嗎?

不是,數據的存儲只有一份,就是一開始的數據存儲,在 shuffle 的時候會有中間臨時數據的存儲。

MapReduce是基於磁盤的?是這樣嗎?

MapReduce是基於磁盤的。

Shuffle過程spark是基於內存的?

不完全基於內存。spark的shuffle中間結果也是需要寫文件的,只是對內存的利用比較充分而已。

大數據中間結果數據的複用場景,存儲方式?

複用場景:一種是迭代式計算應用;一種是交互型數據挖掘應用

存儲方式:hadoop(磁盤)Spark(內存)

Spark分佈式內存的優點?

將中間結果放到內存中,充分發揮內存的讀寫效率,避免重複性的工作一遍遍浪費CPU。

Spark編程基礎

使用RDD的驅動力

  • 快速:可伸縮性、位置感知調度、並行轉換、數據分區

  • 準確:線性依賴、可序列化、失敗自動重建、自動容錯

注意:RDD一旦創建就不可更改。重建不是從最開始的點來重建的,可以是上一步開始重建

RDD定義: RDD叫做彈性分佈式數據集 ,是Spark中最基本的數據抽象,它代表一個不可變、可分區、裏面的元素可並行計算的集合。

RDD特點

  • 不可更改性:一旦創建就不可更改,保證數據穩定、可追溯性、容錯性
  • 數據流模型特點:自動容錯、位置感知性調度和可伸縮性。
  • 數據分區特性:可以通過數據的本地性來提高性能,這與Hadoop MapReduce是一樣的;
  • 並行轉換:並行方式來創建如(map、filter、join等);
  • 可序列化:在內存不足時可自動降級爲磁盤存儲,把RDD存儲於磁盤上
  • 數據高度重用:RDD允許用戶在執行多個查詢時顯式地將工作集緩存在內存中,後續查詢能夠重用工作集,極大地提升查詢速度。
  • 容錯性:RDD只能從持久存儲或通過Transformations操作產生,相比於分佈式共享內存(DSM)可以更高效實現容錯,對於丟失部分數據分區只需要根據它的lineage就可重新計算出來,而不需要做特定checkpoint;

RDD兩種算子

  • 轉換操作(Transformations):Transformations操作是Lazy的,也就是說從一個RDD轉換生成另一個RDD的操作不是馬上執行,spark在遇到Transformations操作時只會記錄需要這樣的操作,並不會去執行,需要等到有Actions操作的時候纔會真正啓動計算過程進行計算。如:map、Filter、groupby、join等
  • 行動操作(Actions):Actions操作會返回結果或把RDD數據寫到存儲系統中。Actions是觸發Spark啓動計劃的動因。如:count、collect、save等

RDD的寬依賴與窄依賴的概念以及各自的方法?

寬依賴:多個子RDD中的分區數據來源於同一個父RDD數據分區。例如groupByKey、reduceByKey、sortByKey等操作都會產生寬依賴

窄依賴:父RDD的每個分區最多隻被一個子RDD的一個分區使用。例如map、filter、union等操作都會產生窄依賴

RDD的創建方式?

  • 通過並行集合(數組)創建RDD:可以調用 SparkContext 的 parallelize方法,在Driver中一個已經存在的集合(數組)上創建。

  • 從一個穩定的存儲系統中創建:Spark採用textFile()方法來從文件系統中加載數據創建RDD,該方法把文件的URI作爲參數,這個URI可以是:

    • 本地文件系統的地址
    • 或者是分佈式文件系統HDFS的地址
    • 或者是Amazon S3的地址等等

RDD的轉換操作

RDD的轉換和行動操作都需要創建 SparkContext 執行上下文,下面示例中的 sc 來自這裏

val conf = new SparkConf().setMaster("local").setAppName("appName")
var sc = new SparkContext(conf)
  • map():參數是函數,函數應用於RDD每一個元素,返回值是新的RDD
val rdd_arr = sc.parallelize(Array("b", "a", "c"))
val rdd_map = arr.map(x => (x,1))
//rdd.collect(): 將RDD類型的數據轉化爲數組
println(rdd_arr.collect().mkString(", ")) // b, a, c
println(rdd_map.collect().mkString(", ")) //(b,1), (a,1), (c,1)
  • flatMap():參數是函數,函數應用於RDD每一個元素,將元素數據進行拆分,變成迭代器,返回值是新的RDD
//使用flatMap對多個集合中的每個元素進行操作再扁平化
val data = sc.parallelize(List("I am learning Spark", "I like Spark"))
val res = data.flatMap(x => x.split(" "))
println(res.collect.mkString(", ")) //I, am, learning, Spark, I, like, Spark

//對一個集合中的元素進行擴充
val arr = sc.parallelize(Array(1,2,3))
val newArr = arr.flatMap(n => Array(n, n*100, 42))
println(arr.collect().mkString(", "))  //1, 2, 3
println(newArr.collect().mkString(", ")) //1, 100, 42, 2, 200, 42, 3, 300, 42
  • filter():參數是函數,函數會過濾掉不符合條件的元素,返回值是新的RDD
val rdd_filter = sc.parallelize(Array(1,2,3)).filter(n => n>2)
println(rdd_filter.collect().mkString(", "))  //3
  • distinct():沒有參數,將RDD裏的元素進行去重操作
val arr = sc.parallelize(Array(1,2,3,3,4))
val newArr = arr.distinct()
println(newArr.collect().mkString(", ")) //4, 1, 3, 2
  • union():參數是RDD,生成包含兩個RDD所有元素的新RDD
  • intersection():參數是RDD,求出兩個RDD的共同元素
  • subtract():參數是RDD,將原RDD裏和參數RDD裏相同的元素去掉
  • cartesian():參數是RDD,求兩個RDD的笛卡兒積
  • groupBy():做一個分組。
val arr = sc.parallelize(Array("John", "Fred", "Anna", "James"))
val newArr = arr.groupBy(w => w.charAt(0))
println(newArr.collect().mkString(", ")) 
//(A,CompactBuffer(Anna)), (J,CompactBuffer(John, James)), (F,CompactBuffer(Fred))
  • coalesce:收縮合並分區,減少分區的個數

  • reduceByKey和groupByKey

val words = Array("one", "two", "two", "three", "three", "three")
val wordRDD = sc.parallelize(words).map(word => (word, 1))
val wordCountsWithReduce = wordRDD
  .reduceByKey(_ + _)
  .collect()
val wordCountsWithGroup = wordRDD
  .groupByKey()
  .map(t => (t._1, t._2.sum))
  .collect()
println(wordCountsWithReduce.mkString(", ")) //(two,2), (one,1), (three,3)
println(wordCountsWithGroup.mkString(", ")) //(two,2), (one,1), (three,3)

reduceByKey和groupByKey區別?

​ reduceByKey用於對每個key對應的多個value進行merge操作,最重要的是它能夠在本地先進行merge操作,並且merge操作可以通過函數自定義,當採用reduceByKey時,Spark可以在每個分區移動數據之前將待輸出數據與一個共用的key結合

​ groupByKey也是對每個key進行操作,但只生成一個sequence,groupByKey本身不能自定義函數,需要先用groupByKey生成RDD,然後才能對此RDD通過map進行自定義函數操作,當採用groupByKey時,由於它不接收函數,Spark只能先將所有的鍵值對(key-value pair)都移動,這樣的後果是集羣節點之間的開銷很大,導致傳輸延時。

RDD的行動操作

  • collect(): 返回RDD所有元素 ,將RDD類型轉換爲數組類型
  • count():RDD裏元素個數
  • countByValue():各元素在RDD中出現次數
  • countByKey():各鍵在RDD中出現的次數
val arr = sc.parallelize(Array(('J',"James"),('F',"Fred"),('A',"Anna"),('J',"John")))
val keyCount = arr.countByKey()
println(keyCount) //Map(A -> 1, J -> 2, F -> 1)
  • reduce():並行整合所有RDD數據,例如求和操作
val arr = sc.parallelize(Array(1,2,3,4))
val res = arr.reduce((a,b) => a+b)
println(arr.collect.mkString(", ")) //1, 2, 3, 4
println(res) //10
  • fold(0)(func):和reduce功能一樣,不過fold帶有初始值
  • aggregate(0)(seqOp,combop):和reduce功能一樣,但是返回的RDD數據類型和原RDD不一樣
  • foreach(func):對RDD每個元素都是使用特定函數
  • saveAsTextFile(pathname):保存文件
rdd.saveAsTextFile("file:///f:/output-test.txt")

RDD分區:RDD是彈性分佈式數據集,通常RDD很大,會被分成很多個分區,分別保存在不同的節點上。

使用RDD分區的優點

  1. 增加並行度
  2. 減少通信開銷

能從spark分區中獲取的操作有cogroup()groupWith()join()leftOuterJoin()rightOuterJoin()groupByKey()reduceByKey()combineByKey()以及lookup()

RDD分區原則:使得分區的個數儘量等於集羣中的CPU核心(core)數目

如何手動設置分區

  • 創建 RDD 時:在調用 textFile 和 parallelize 方法時候手動指定分區個數即可,語法格式:sc.textFile(path, partitionNum)

  • 通過轉換操作得到新 RDD 時:直接調用 repartition 方法即可

RDD惰性機制:執行“動作”類型操作時,纔是真正的計算。

RDD的持久化:爲了避免這種重複計算的開銷,可以使用persist()方法對一個RDD標記爲持久化。

鍵值對RDD

概念:鍵值對RDD由一組組的鍵值對組成,這些RDD被稱爲PairRDD。PairRDD提供了並行操作各個鍵或跨節點重新進行數據分組的操作接口,雖然大部分Spark的RDD操作都支持所有種類的單值RDD,但是有少部分特殊的操作只能作用於鍵值對類型的RDD。

創建方式:從文件中加載,通過並行集合(數組)創建RDD

鍵值對常用轉換操作的方法及其作用

(1)reduceByKey(func):使用func函數合併具有相同鍵的值

(2)groupByKey():對具有相同鍵的值進行分組

(3)Keys():keys只會把Pair RDD中的key返回形成一個新的RDD

(4)Values():只會把Pair RDD中的value返回形成一個新的RDD

(5)sortByKey():返回一個根據鍵排序的RDD

(6)mapValues(func):對鍵值對RDD中的每個value都應用一個函數,但是,key不會發生變化

(7)join:內連接

(8)combineByKey:聚合各分區的元素,而每個元素都是二元組

單詞計數程序案例:

object WordCount {
  def main(args: Array[String]) {
    //定義文件路徑
    val inputFile = "file:///F:/words.txt"
    //配置集羣參數
    val conf = new SparkConf().setAppName("WordCount").setMaster("local[2]")
    val sc = new SparkContext(conf)
    //讀取文件
    val textFile = sc.textFile(inputFile)
     //平鋪 --> (單詞, 1) --> 求和
    val wordCount = textFile
      .flatMap(line => line.split(" "))
      .map(word => (word, 1))
      .reduceByKey((a, b) => a + b)
	//輸出    
    wordCount.foreach(println)
  }
}

找出單科成績爲100的學生ID案例

object grade {
  def main(args: Array[String]): Unit = {
    //定義SparkContext上下文
    val conf = new SparkConf().setAppName("WordCount").setMaster("local[2]")
    val sc = new SparkContext(conf)
    //讀取文件
    val math = sc.textFile("file:///e:/spark/student/result_math.txt")
    val tupleMath = math.map { x => val lineData = x.split(" "); (lineData(0), lineData(1), lineData(2).toInt) }
    val bigData = sc.textFile("file:///e:/spark/student/result_bigdata.txt")
    val tupleBigdata = bigData.map { x => val lineData = x.split(" "); (lineData(0), lineData(1), lineData(2).toInt) }
    //獲取成績結果
    val studentResult = tupleMath.filter(_._3 == 100).union(tupleBigdata.filter(_._3 == 100))
    //去重
    val finalResult = studentResult.map(_._1).distinct
    finalResult.collect
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章