為了能用scala開發spark,簡單的介紹一下scala特性.scala即面向對象編程,也同時面向方法編程,也可以說是面向容器編程,在編譯階段會把scala代碼編譯成java字節碼,運行于jvm上.
在這里要介紹閉包,迭代器,隱式函數,因為這幾個特性在開發spark,或者查看源碼的時候,都會經常遇到的. 代碼列子
閉包
定義:
閉包是一個函數,返回值依賴于聲明在函數外部的一個或多個變量。
閉包通常來講可以簡單的認為是可以訪問一個函數里面局部變量的另外一個函數.
我先介紹scala單機閉包.
def main(args: Array[String]) {
closure1
println("-----------------")
closure2
}
def closure2(): Unit ={
val fs = new Array[()=>Int](4)
def set(i:Int) { fs(i) = () => i }
var j = 0
while(j < 4) {set(j); j=j+1}
fs.foreach( f => println(f()))
}
def closure1(): Unit ={
val fs = new Array[()=>Int](4)
var i = 0
while(i < 4) { fs(i) = () => i; i=i+1}
fs.foreach( f => println(f()))
}
//輸出
4
4
4
4
-----------------
0
1
2
3
可以看出閉包的綁定的是變量的引用,或者是地址. 所以說closure1輸出都是4,當fs.foreach的時候i=4. closure2中傳入set函數,是重新生成一個變量i.
scala閉包到spark分布式計算是如何展現的.我舉個例子就明白了.
val spark = SparkSession
.builder()
// .master("local[*]")
.appName("hello world")
.config("spark.some.config.option", "some-value")
.getOrCreate()
val rdd = spark.sparkContext.parallelize(Seq(1,3,4,5,6,1000,10))
var counter = 0
rdd.foreach(x => counter += x)
println("result " + counter)
結果會是多少,1033? 實際輸出0
rdd.foreach是一個分布式操作,counter+=x操作可能在多個jvm中進行,假設有兩個executor,在driver,executor1,executor2中都會有counter,初始化值都為0,相互不干擾. 因為foreach操作都是在executor上進行的,driver中并沒有操作,所以counter還是0
迭代器
scala其實也是面向集合編程的,萬物皆集合.你會發現對象都會有map,foreach,flatMap等.Iterator并不是集合,但是操作集合的方法. 了解它,你就會更加了解scala的集合和spark RDD的底層實現.
舉個例子:
val it = Iterator[String]("name1","name2","name3")
def rename(name:String): String ={
println(name)
name+"1"
}
//并未實際執行rename
val it2 = it.map(name=>rename(name))
it2.foreach(name=>{
println(name)
})
如果是存儲至mysql或者外部的數據庫,foreach可以控制每1000條處理寫入一次.
你認為輸出結果?
name1
name2
name3
name11
name21
name31
實際輸出結果:
name1
name11
name2
name21
name3
name31
以下是scala Iterator map實現原理,其實就是重新定義next函數. spark也是基于此原來來實現懶處理的
/** Creates a new iterator that maps all produced values of this iterator
* to new values using a transformation function.
*
* @param f the transformation function
* @return a new iterator which transforms every value produced by this
* iterator by applying the function `f` to it.
* @note Reuse: $consumesAndProducesIterator
*/
def map[B](f: A => B): Iterator[B] = new AbstractIterator[B] {
def hasNext = self.hasNext
def next() = f(self.next())
}
隱式函數
我們經常引入第三方庫,但當我們想要擴展新功能的時候通常是很不方便的,因為我們不能直接修改其代碼。scala提供了隱式轉換機制和隱式參數幫我們解決諸如這樣的問題。
Scala中的隱式轉換是一種非常強大的代碼查找機制。當函數、構造器調用缺少參數或者某一實例調用了其他類型的方法導致編譯不通過時,編譯器會嘗試搜索一些特定的區域,嘗試使編譯通過.
假設有這樣一個類,如果你想在它上面添加eat方法,你會怎么做?
class People{
var state:String = "walking"
def doSomething(): Unit ={
println(s"i'm $state")
}
}
如果java做起來,可能比較麻煩,當然可以把在源碼基礎上改。但是一個是違反java的設計原則,一個使用起來比較麻煩.介紹一下scala可以怎么實現:
class PeopleFunction(people: People){
def eat(): Unit ={
people.state = "eating"
people.doSomething()
}
}
package object action {
implicit def peopleImpl(people: People): PeopleFunction ={
new PeopleFunction(people)
}
}
//main函數
val people = new People
people.eat()
這樣就可以實現了,在編譯階段,會自動把eat函數的字節碼,寫入people中,這樣實現就非常簡單.