Spark中一個(gè)非常難以理解的概念皱蹦,就是在集群中分布式并行運(yùn)行時(shí)操作的算子外部的變量的生命周期
通常來說宙枷,這個(gè)問題跟在RDD的算子中操作作用域外部的變量有關(guān)
所謂RDD算子中斥季,操作作用域外部的變量搞动,指的是,類似下面的語句: val a = 0; rdd.foreach(i -> a += i)
此時(shí)李丰,對rdd執(zhí)行的foreach算子的作用域,其實(shí)僅僅是它的內(nèi)部代碼逼泣,但是這里卻操作了作用域外部的a變量
根據(jù)不同的編程語言的語法趴泌,這種功能是可以做到的,而這種現(xiàn)象就叫做閉包
閉包簡單來說拉庶,就是操作的不屬于一個(gè)作用域范圍的變量
如果使用local模式運(yùn)行spark作業(yè)嗜憔,那么實(shí)際只有一個(gè)jvm進(jìn)程在執(zhí)行這個(gè)作業(yè)
此時(shí),你所有的RDD算子的代碼執(zhí)行以及它們操作的外部變量氏仗,都是在一個(gè)進(jìn)程的內(nèi)存中吉捶,這個(gè)進(jìn)程就是driver進(jìn)程
此時(shí)是沒有任何問題的
但是在作業(yè)提交到集群執(zhí)行的模式下(無論是client或cluster模式,作業(yè)都是在集群中運(yùn)行的)
為了分布式并行執(zhí)行你的作業(yè)皆尔,spark會將你的RDD算子操作呐舔,分散成多個(gè)task,放到集群中的多個(gè)節(jié)點(diǎn)上的executor進(jìn)程中去執(zhí)行
每個(gè)task執(zhí)行的是相同的代碼慷蠕,但是卻是處理不同的數(shù)據(jù)
在提交作業(yè)的task到集群去執(zhí)行之前珊拼,spark會先在driver端處理閉包
spark中的閉包,特指那些砌们,不在算子的作用域內(nèi)部杆麸,但是在作用域外部卻被算子處理和操作了的變量
而算子代碼的執(zhí)行也需要這些變量才能順利執(zhí)行
此時(shí),這些閉包變量會被序列化成多個(gè)副本浪感,然后每個(gè)副本都發(fā)送到各個(gè)executor進(jìn)程中昔头,供那個(gè)executor進(jìn)程運(yùn)行的task執(zhí)行代碼時(shí)使用
對于上面說的閉包變量處理機(jī)制
對于local模式,沒有任何特別的影響影兽,畢竟都在一個(gè)jvm進(jìn)程中揭斧,變量發(fā)送到executor,也不過就是進(jìn)程中的一個(gè)線程而已
但是對于集群運(yùn)行模式來說峻堰,每個(gè)executor進(jìn)程讹开,都會得到一個(gè)閉包變量的副本,這個(gè)時(shí)候捐名,就會出問題
因此閉包變量發(fā)送到executor進(jìn)程中之后旦万,就變成了一個(gè)一個(gè)獨(dú)立的變量副本了,這就是最關(guān)鍵的一點(diǎn)
此時(shí)在executor進(jìn)程中镶蹋,執(zhí)行task和算子代碼時(shí)成艘,訪問的閉包變量赏半,也僅僅只是當(dāng)前executor進(jìn)程中的一個(gè)變量副本而已了
此時(shí)雖然在driver進(jìn)程中,也有一個(gè)變量副本淆两,但是卻完全跟各個(gè)executor進(jìn)程中的變量副本不是一個(gè)東西
此時(shí)断箫,各個(gè)executor進(jìn)程對于自己內(nèi)存中的變量副本進(jìn)行操作,即使改變了變量副本的值秋冰,但是對于driver端的程序仲义,是完全感知不到的
driver端的變量沒有被進(jìn)行任何操作
因此綜上所述,在你使用集群模式運(yùn)行作業(yè)的時(shí)候剑勾,切忌不要在算子內(nèi)部埃撵,對作用域外面的閉包變量進(jìn)行改變其值的操作
因?yàn)槟菦]有任何意義,算子僅僅會在executor進(jìn)程中甥材,改變變量副本的值
對于driver端的變量沒有任何影響盯另,我們也獲取不到executor端的變量副本的值
如果希望在集群模式下,對某個(gè)driver端的變量洲赵,進(jìn)行分布式并行地全局性的修改
可以使用Spark提供的Accumulator鸳惯,全局累加器
后面我們會講解一個(gè)Accumulator的高級用法,自定義Accumulator叠萍,實(shí)現(xiàn)任意機(jī)制和算法的全局計(jì)算器