一種平衡Spark計算負載的中間數據存放策略


An intermediate data placement algorithm for load balancing in Spark computing environment

最近在研究一些Spark成本優化的東西,看了一些論文稍微總結一下思路,方便思維拓寬和希望與大家交流!

本篇博文參考自:

Future Generation Computer Systems 78 (2018) 287–301:
《An intermediate data placement algorithm for load balancing in Spark computing environment》


1 文章概述及問題描述

Spark作為基于內存迭代的云計算框架,其很容易發生數據傾斜,尤其是在Shuffle階段,reduce端所拉取的數據量很容易出現不平衡,這將導致某些reduce計算很久,使得整體計算發生延時,嚴重時會導致application失敗。本篇論文討論spark中mapreduce框架中所出現的shuffle階段的數據傾斜問題。

MR的數據格式全是K-V形式,因此數據的傾斜就是Key的傾斜,導致某些分區中的數據量過大,因此,對于reducer來說,partition的傾斜將會導致reduce的傾斜。

  • 為什么容易出現數據傾斜 *
    partition的大小取決于具有相關性的key/value的數量,由于spark中keys的分配調取由hash算法決定,因此不同的reducer之間會出現數據量不同的情況,有些reducer要處理的數據可能會非常大。

2 論文中的相關術語以及spark基礎

  1. clusters
    一個data clusters就是一組包含全部相同Key的map中間輸出結果的數據集合。
    在Spark中,所有的被相同的reduce處理的clustes組成一個分區(partition)。

  1. Bucket
    bucket是指一個序列的緩沖區,用于收集并緩存map的輸出,相對應的reduce會從相應的bucket上拉取數據。

  1. MR*
    假設reduce個數為R,map個數為M,則每一個map將會創建R個bucket,所以bucket的總數為R*M

  1. spark-shuffle(1.1.0)中的輸出分布
    Data distribution of shuffle in Spark 1.1.0.

  1. Zipf distributions
    文本數據的key一般來說是服從Zipf distributions

參考:J. Lin, et al. The curse of zipf and limits to parallelization: A look at the stragglers problem in mapreduce, in: 7th Workshop on Large-Scale Distributed Systems for Information Retrieval, 2012, pp. 2000–2009.


3 方法概述

為了解決shuffle階段出現的數據傾斜,本文提出了一種分割與合并的算法(splitting and combination algorithm for skew intermediate data blocks (SCID)),這種算法采用基于水塘抽樣的抽樣算法,來自動偵測中間數據的分布情況,并且是在spark程序運行之前就可以判斷。

大致做法就是SCID會對鍵值集合的clustes的大小進行排序,并且將會依次存放在相應的bucket中,bucket的大小是固定的,某個bucket存滿后,該clustes會被切分,剩下的clusters將進入下一次的迭代。經過這樣的幾輪分配,每一個bucket中的數據大小幾乎相同,所以此時相應的reducer拉取到的數據就是相同的。與此同時,我們可以知道,同一個bucket中可能會存放來自不同clusters的數據。

但是,上述算法所遇到的問題就是,如果map中間輸出數據的key的分布實現不知道的話,就無法在clusters上使用精確的判別機制來進行切分。而這樣的算法只有在MR執行前進行才會顯得有意義,因此本文提出動態范圍分區(dynamic range partition)的方法,對輸入數據進行以此預先的抽樣,并將抽樣結果輸入給一小部分的map tasks中,以實現分布統計,通過相應的統計值,可以預測出map階段后所產生冊所有clusters的大小,這個結果將會作為split策略的輸入。

  • 注意,所有的策略均在真正spark程序運行之前進行 *

綜上所述,本文所做的工作就是在原有spark架構基礎上添加了一個統計分布功能用于抽樣并作為split和combine算法的輸入。這是一種細粒度(fine-grained)的算法,他能夠重新進行partition,改變原有分區的大小并發送給相應的buckets,從而解決了reduce端數據傾斜問題。


4 主要貢獻

使用水塘抽樣進行輸入數據的采樣,并提出一種驗證模型來選擇合適的采樣率。這樣的模型在運行中考慮到了成本、效果以及方差的重要性
提出了一種切分以及合并鍵值對clusters的算法。通過以相同大小的clusters的組合來填充固定數量的buckets從而達到reduce端具有更好的負載均衡。
文章基于spark1.1.0驗證了本文算法模型帶來的性能提升和數據傾斜問題的減緩。


5 系統整體介紹

由前面的術語介紹以及mapreduce的原理可知:每一個split會由一個map來處理,并生成一中間結果,中間結果通常是被partitioned的,而且被分到相應的bucket中去。本文使用如下表達式來來自 m map Tasks的中間結果:



根據bucket的概念,我們可以用下面的公式來表達一個cluster所分成的buckets:



所有屬于相同cluster的鍵值集合被放置在同一個partition中,所以根據key可以將所有的K進行n個partition的劃分,用如下表示:

下圖流程是一個被改善過的Spark工作流程。


其中最重的組件就是 load balancing module。spark運行之前,這個組件會生成一個balanced partitioning策略,這個策略會指導該如何split clusters以及如何combine分割后的clusters。balance策略能夠被使用在shuffle階段。
關于load balancing module,在本文共包含以下兩個階段:

  1. Data samling:抽樣是在map之前進行的,所謂的抽樣就是從全部的輸入split采樣小樣本,進而通過對key的統計方法來預測map輸出結果中每一個cluster的大小。
  2. Splitting and combination:抽樣結束后,會產生一個clusters的切分和combine方法。一些過大的clusters可能會被切分多個以填充到不同的buckets中去。切分的主要還是要看固定的bucket的容量大小。

6 抽樣模型

6.1 數據偏移模型

模型的本質就是對clusters、bucket等概念進行結構化,通過對每一個組件進行結構化的分析,最終引出偏移與均值和方差的條件關系,推導出FoS這樣一個偏移程度的指標。因此,文中定義以下幾個公式:

1)因為一個cluster中是包含相同key的鍵值對數據,因此,所有的clusters能夠被形式化如下:
C={C1,C2....Ci...,Cm}, 1<=i<=m
m是cluster的編號,Ci被定義為一種結構體:Ci ={order,SC},其中order記錄了這個cluster的順序,而SC被定義為一個序列:SC = {SC1,SC2....,SCi...SCm} 1<=i<=m.其中SCi是整形變量,表示對應的cluster的數據集的大小。
2)bucket可以形式化為:B = {B1,B2,....Bk,...Bn} 1<=k<=n 其中n為bucket的編號

3)前面說到,文本數據的key一般服從Zipf分布,這個模型里面設置了一個變量a(0.1<=a<=1.2)來控制偏移量,a越大代表偏移越大,a僅僅影響當前輸入數據的偏移大小。本文使用矩陣P來表示這樣一種分布,pki表示從cluster Ci所獲得的鍵值對的數量,這個數量后面將會被bucket Bk所拉取(說到這,大家還是要搞清楚文中這幾個術語之間的關系的:map的輸出數據相當于一堆鍵值對,擁有相同key的數據<tuples>組成一個cluster,而同時被相同reduce處理的clusters組成一個partition,partition最后會被放入bucket中),因此SCi可以被看作是在所有buckets中來自cluster Ci的總大小,可以用如下公式來定義:


4)bucket k中所包含的鍵值對的個數由BC(k) 來表示,對于數據的分布pki,外加上之前給的變量a,則分布可以表示為:


那么BC(k)的值可以表示為如下公式:

5)由上式,我們可以計算在當前偏移參數a的前提下的所有buckets的鍵值對的平均數量如下公式所示,其中,n代表的是全部bucket的數量。

6)一般而言,被bucket所處理的中間數據能夠被看作是具有數據偏移標準差的,那么,當滿足以下條件時,被bucket所處理的clusters將被視作具有數據偏移,std是所有buckets中key-value對個數的標準差:

7)所有clusters中的數據的標準差可以用以下公示表達:

8)最后,為了歸一化表達數據偏移的程度,定義了FoS指標(factor of skew)來評測所有buckets負載均衡的程度,FoS指標值越小,則代表負載越均衡,則偏移也相應的越小:

6.2 水塘抽樣算法

為何要使用水塘抽樣?
在一般的編程語言中,常規的抽樣是使用偽隨機數,對于大規模的數據,特別是隨著采樣空間的增加,這樣簡單的偽隨機數不能保證所有樣本完全隨機化,不可避免地會產生一些重復樣本。而水塘抽樣則能夠有效避免這一問題,他將使得樣本出現的概率均相同,保證樣本的隨機性,特別是從某些序列流數據中抽取數據時,水塘抽樣可以保證原始key的分布更加接近于整體真實情況。
水塘抽樣是為了解決未知大小數據集的隨機數抽取問題,要求從一個未知大小的數據集中等概率地拿出k個數。尤其是在大數據背景下的采樣問題,對于大規模數據,我們無法將其全部加載到內存,此時需要根據內存大小k來等概率地從全部數據中抽取大小為k的數。

水塘抽樣基本思想:
1、簡單場景:如果我們已知這個數據集只有3個數字,那么我們在拿取第一個數的時候,其出現在水池中的概率為1,拿取第二個數的時候,其出現在水池中的概率為1/2,在拿取最后一個數的時候,我們為了等概率地返回1個數,分為兩種情況:1)返回第三個數:顯然如果要保留第三個數則齊概率為1/3。2)如果返回前兩個數的其中一個,則其概率為(1-1/3)1/2=1/3,即不選擇第三個數的概率選擇前兩個數任意一個的概率,因此水塘中每個數字出現的概率均相同。
2、復雜場景:文中的抽樣方法是需要返回k個數,因此這里直接修改1中的返回一個數為k個數即可,即:每個數字在水池中出現的概率為k/n。
其算法流程如下:
1)初始化時,我們依次將前k個數加載到水池中。
2)隨后考慮第k+1個數的生死。此時分兩種情況:
a.水池中全部元素沒有被替換
b.水池中某個元素被第k+1個替換
先來看情況b:對于第k個元素,此時生成一個0i(i=k+1)的隨機數p,如果p<k(相當于生成01的隨機數),則第k+1個數被選中,并且用這個數去替換水池中的某一個數,此時第k+1個數在水庫中出現的概率為k/k+1,接下來我們要看看水庫中每個元素被替換的概率:條件概率,首先要第k+1個數被選中,其次是k個數中隨機選出一個來替換,則k個數中被替換的概率為(k/k+1)*(1/k)=1/k+1.那么水庫中原先的k個數每個數還能繼續出現的概率就等于1-1/(k+1)=k/k+1,可以看出,不管是新來的元素還是以前的元素的出現概率均為k/k+1。
對于情況a:如果所有元素都沒有被替換,就說明第k+1個元素沒有被選中,則此時水池中每個元素出現的概率就為1-第k+1個元素被選中的概率=1-k/(k+1)=1/k+1
這樣的一個規律可以用數學歸納法,直到證明完第i+1個數時仍然成立即可。
下面是水塘抽樣算法的偽代碼部分:

//stream代表數據流  
//reservoir代表返回長度為k的池塘  
//從stream中取前k個放入reservoir;  
for ( int i = 1; i < k; i++)  
   reservoir[i] = stream[i];  
for (i = k; stream != null; i++) {  
   p = random(0, i);  
   if (p < k) reservoir[p] = stream[i];  
return reservoir;

6.3 cluster容量大小的預測

建立在抽樣算法的輸出結果上,同時,基于抽樣算法中的假設:抽樣得到的數據的key的分布與整體數據的key的分布是相同的,因此,能夠大致預測出每個map節點上每個cluster的數據大小。
步驟:

  1. 輸入數據是抽樣算法的輸出結果,用BS表示;同時MRjob用來表示處理BS的spark作業。
  2. 基于部署在map節點上的監控工具,可以得到clusters個數的記錄,同時獲得每個cluster中k/v的個數{SCi}.
  3. 由于抽樣數據中的每一個key的數量被認為是與原始數據中key的數量成比例的,因此可以通過擴大每一個clusters的個數來來近似估計cluster的真實大小。

7 Cluster的切分和重組模型(5. Cluster splitting and combining)

  1. 算法流程概述:
    clusters的切分和重組算法實際就是想法設法將全部的clusters打包并分配到大小相近的buckets中去。整體流程可參考下圖:


算法的輸入是前面我們所定義的{Ci}:clusters tuples的集合;{SCi}:每一個cluster在{Ci}中集合的個數;B:當前buckets的序列;RB:當前bucket剩余容量。算法的輸出是矩陣P,代表著clusters的存放策略。
整體思想是先對map的中間結果產生的clusters進行降序排序,然后從最大的cluster(SCm)開始判斷其與固定bucket(RB)的大小關系,如果SCm>=RB,則對SCm進行切分,只將滿足bucke大小的那一部分放進第一個bucket中,剩余的一部分作為一個segment將進入第二輪判斷(注意,被spilt的剩余部分在第二輪迭代前會與其他的clusters進行重新排序),以此類推;如果SCm<=RB,則直接將其放入該bucket中然后判斷下一個SCm-1是否滿足剩余空間大小,以此類推。整個流程分輪次進行迭代判斷,也就是說,每一次只會填充一個bucket。其中RB是表示當前bucket剩余空間大小,隨著clusters的填充,RB的值會不斷更新,某一輪的停止條件為尋找到一個SC的大小大于RB,則表示當前輪的bucket即將被填滿。

那么問題來了:
在spark-1.1.0中,當map的中間輸出達到20%時,就會有shuffle階段啟動,那么說明我們是無法等到全部clusters都生成后才去得到切分和重組策略!!!
對于這個問題,其實我們需要知道,切分和重組算法其實相當于一個模擬的過程,我們在作業執行前通過采樣得到了一組采樣數據,這組采樣數據能夠真實反映整體數據在map輸出時的大致分布情況,因此,在進行切分和重組算法時,模型只需輸出在已知clusters分布的情況下的一個模擬bucket分配方式即可。

切分和重組算法的輸出矩陣P如下圖所示:Pij的定義回顧一下:第i個bucket從第j個cluster所拉取的k/v的數量。

matrixP.png

這個矩陣可以換一種形式來表示,下面的形式將直接表示出每個cluster中被每個bucket所處理的k/v的數量。

matrixP02.png

2.真實作業時的Cluster分配算法
真實作業時,map達到20%輸出時就會進行shuffle,此算法就是根據先前的模擬放置策略來動態決定當前輸出數據的存放。算法以矩陣P以及{Ci}(每一個clusters的大小)為輸入,以{CBij}來表示每一個bucket的當前負載情況,即代表當前第i個bucket所拉取的第j個cluster的數據大小。
算法流程:
該算法用于真實作業情況下中間數據的放置。有兩種基本情況
1)首先對C進行遍歷獲得Ki的位置,如果此時在C中找不到Ki,則按照默認的hash來放置Ki。
2)如果找得到,則代表可以在矩陣P中獲取到該Ki。算法中實現這一過程其實是去遍歷相關列向量pj,在矩陣P中,我們知道列序號代表著key在clusters集合C中的位置,因此,對于向量pj,每一個元素都表示著該key在每個bucket中所被允許的最大的容量。解釋完pj,則這個流程可以概括為:在遍歷pj的同時,來判斷當前每一個bucket的負載情況BCij是否小于pij的極限,也就是為了判斷Ki的位置,算法將會遍歷所有的bucket。


8 實驗環境

1、16節點
2、spark-1.1.0 backend:HDFS
3、用例:
1)sort,該用例的數據應該是使用的谷歌全球排序比賽的數據
排序數據分為兩種:Daytona (stock car) 和 Indy (formula 1) 。
值得一提的是目前最快紀錄是騰訊。
2)Text Search 數據:(English Wikipedia
archive data set)[https://en.wikipedia.org/wiki/Archive]
3)WordCount,該用例的數據未在文中看到...........

我的博客 : https://NingSM.github.io

轉載請注明原址,謝謝

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容