Spark+Alluxio性能調優十大技巧


本文章轉載於:https://zhuanlan.zhihu.com/p/54245707

由於統一訪問對象存儲(如S3)和HDFS數據的場景的出現和普及,Apache Spark結合Alluxio的大數據棧越來越受歡迎。此外,越來越流行的計算與存儲分離的架構導致計算端查詢延遲增大。因此,Alluxio常被用作貼近計算端的熱數據存儲以提高性能。爲了能夠獲得最佳性能,用戶需要像使用其他技術棧組合一樣遵循最佳的實戰經驗。本文介紹了在Alluxio上運行Spark時,對於實際工作負載性能調優的十大技巧。

常用鏈接


關於數據本地性的技巧


數據本地性就是儘量將計算移到數據所在的節點上進行,避免數據在網絡上的傳輸。分佈式數據並行環境下,數據的本地性非常重要。提高數據本地性能夠極大地提升Spark作業的性能。如果需要計算的數據存儲在節點本地,那麼Spark任務可以直接以內存速度(當配置ramdisk時)從本地Alluxio worker中讀取Alluxio中的數據,而不必通過網絡進行數據傳輸。首先我們要介紹的幾個調優技巧是關於數據本地性方面的。


1. 檢查並確認Spark作業讀取Alluxio時的數據本地性

當Spark worker與Alluxio worker同置部署(co-locate)在同一節點上時,Alluxio能夠通過支持短路讀寫爲Spark計算任務提供最佳的性能。用戶有多種方法檢查I/O請求是否實際由Alluxio短路讀/寫提供數據訪問服務,具體方法如下:

  • 方法1:當運行Spark任務時,觀察監控頁面Alluxio metrics UI page上的Short-circuit readsFrom RemoteInstance的兩個指標。此外,還可以觀察cluster.BytesReadAlluxioThroughputcluster.BytesReadLocalThroughput兩個指標的值進行判斷。如果數據的本地讀寫吞吐量爲零或遠低於總吞吐量,那麼說明該Spark作業可能沒有本地的Alluxio worker進行數據交互。
  • 方法2:利用dstat等工具檢測網絡流量,這樣我們就可以看到短路讀取是否發生以及實際的網絡吞吐量。YARN用戶還可以在/var/log/hadoop-yarn/userlogs日誌中查找如下信息:INFO ShuffleBlockFetcherIterator: Started 0 remote fetches in 0ms,觀察遠程讀取是否發生,或者都是短路讀取。運行Spark作業時,用戶可以通過配置YARN相關參數(例如--master yarn--num-executors 4 --executor-cores 4)來開啓收集這些日誌。

如果用戶通過上述方法發現Alluxio對Spark作業只提供了一小部分短路讀取或寫入,請閱讀下面幾條技巧以提升數據本地性。

2. 確保Spark Executor本地性


Spark中的數據本地性分爲兩種:executor 層面的本地性(executor locality)、task 層面的本地性(task locality) 。Executor級別的本地性意味着當Spark作業由其他資源管理框架(如Mesos和YARN)部署時,Spark Executor會被分配在同時運行Alluxio worker的節點上,從而和Alluxio worker運行於統一的節點。如果Executor的本地性沒有達到,Alluxio就無法爲Spark作業提供本地的數據服務。我們經常會發現,相比於Spark Standalone模式部署,當Spark通過Spark on Yarn或Spark on Mesos等資源管理框架的模式進行部署時,executor本地性經常難以保證。


較新版本的Spark在YARN和Mesos框架調度分發executors時能夠考慮數據本地性因素,此外還有方法可以強制在所需節點上啓動executors。因此,一個簡單的策略是在每個節點上都啓動executor,這樣能夠保證在每個所需節點上都至少有一個executor運行着。
然後,由於實際情況下的資源限制,在通常生產環境中的每個節點上都部署運行Alluxio worker還比較困難。爲了能夠將Alluxioworker與計算節點同置部署(co-locate),用戶可以利用類似於資源管理框架YARN中的節點標籤功能。具體地,首先,將包含Alluxio worker的NodeManagers節點標籤標記爲alluxio(具體標籤取名不重要)。然後,用戶將Spark作業提交到該alluxio標籤指定的節點。最後,系統會在這些機器節點上啓動Spark作業的executors並與Alluxio workers進行協同工作。


3. 確保Spark Task本地性


Task層面的數據本地性是屬於下一層次的本地性,它是由Spark本身決定的。達到Task本地性表示一旦啓動executor,Spark能夠將task調度到滿足數據本地性的executor上執行(在與Alluxio結合的場景下,即在將task調度到通過Alluxio提供本地數據的executor上執行)。爲了達到該目標,Spark任務調度器需要首先從Alluxio收集所有數據的位置信息。該位置信息以Alluxio worker的主機名列表進行表示。然後,嘗試匹配Spark executor主機名進行調度。用戶可以從Alluxio master web UI中查看到Alluxio worker的主機名,也可以通過Spark driver web UI找到Spark executor的主機名。

然而,有時由於不同網絡環境或配置方面的原因,在同一臺機器上運行的Alluxio worker和Spark executor提供的主機名可能並不匹配。在這種情況下,Spark 的任務調度器將會混淆主機名的匹配從而無法確保數據本地性。因此,Alluxio worker的主機名與Spark executor的主機名採用相同的“主機名名稱空間”非常重要。用戶經常會遇到的一個問題是:Alluxio worker和Spark executor一個使用IP地址標示,而另一個使用主機名標示。如果發生了這種情況,用戶仍然可以在Spark中手動設置Alluxio-client屬性alluxio.user.hostname(如何設置見文檔),並將Alluxio worker中的alluxio.worker.hostname屬性設置爲相同值來解決該問題。此外,用戶也可以查閱JIRA SPARK-10149 以獲取Spark開源社區的解決方案。


4. 優先考慮Spark調度器中的本地性因素

關於本地性, Spark客戶端有一些配置參數可供調優。具體地,

  • spark.locality.wait:即數據本地性降級的等待時間,默認是3000毫秒,如果超時就啓動下一本地優先級別的task。該配置參數同樣可以應用到不同優先級的本地性之間(PROCESS_LOCALNODE_LOCALRACK_LOCALANY)。
  • 我們也可以通過將參數spark.locality.wait.node設置爲特定的本地性級別,從而實現自定義每個節點的本地性等待時間。

負載均衡

負載均衡同樣非常重要,它能夠確保Spark作業的執行能夠均勻地分佈在不同可用節點上。以下的幾個技巧介紹如何避免由於Alluxio輸入數據傾斜導致負載或者任務調度不均衡。


5. 使用DeterministicHashPolicy通過Alluxio從UFS冷讀數據

同一個Spark作業的多個task經常會讀取相同的輸入文件。當該文件沒有加載到Alluxio中時,默認情況下,不同task的本地Alluxio worker將從UFS讀取相同的文件。這可能導致如下後果:

  • 多個Alluxio worker同時競爭相同數據的Alluxio-UFS連接。如果從Alluxio到UFS的連接速度很慢,每個worker都會因爲這些不必要的競爭而造成數據讀取速度變慢。
  • 同一份數據會在Alluxio緩存空間中存儲多個副本,造成大量的空間浪費,並間接造成那些對其他查詢有用的數據被剔出出Alluxio緩存空間。

解決此問題的一種方法是將alluxio.user.ufs.block.read.location.policy設置爲DeterministicHashPolicy以協調Alluxio worker從UFS讀取數據的過程,避免產生不必要的競爭。例如,編輯Spark的conf/spark-defaults.conf以確保最多允許四個隨機Alluxio worker讀取給定數據塊。(具體數目由alluxio.user.ufs.block.read.location.policy.deterministic.hash.shards參數決定)

spark.driver.extraJavaOptions=-Dalluxio.user.ufs.block.read.location.policy=alluxio.client.block.policy.DeterministicHashPolicy
spark.executor.extraJavaOptions=-Dalluxio.user.ufs.block.read.location.policy.deterministic.hash.shards=4


注意,通過上述設置,系統將爲不同的數據塊隨機選擇一組不同的四個worker與之對應。

6. 採用更小的Alluxio數據塊獲得更高的並行度


數據塊(block)是Alluxio系統中最小的存儲單元。當Alluxio數據塊大小(默認爲512MB)相較於一個文件本身很大時,就意味着該文件只包含少數幾個數據塊。因此,也就只有少數幾個Alluxio worker能夠爲並行地讀取文件提供服務。具體地,爲了獲得數據NODE_LOCAL本地性,在Spark的“文件掃描”階段,task可能只分配給少數幾個服務器處理。這會導致大多數服務器閒置,從而導致集羣負載不平衡,處理併發度低下。在這種情況下,使用較小的塊來組織存儲Alluxio的文件(例如,將alluxio.user.block.size.bytes.default設置爲128MB或更小),能夠增加文件的數據塊數量,使得文件的數據分散地存儲在更多的機器中,從而提升文件數據讀取的並行度。。請注意,當UFS是HDFS時,自定義調整Alluxio塊大小的方法會不適用,因爲Alluxio的數據塊大小會被強制設置爲HDFS塊大小。

7. 調整優化executor數目以及每個executor的task數目

在一個executor中運行過多的task併發地從Alluxio中讀取數據可能會造成網絡連接、網絡帶寬等資源的爭用。基於Alluxio worker節點的數量,用戶可以通過合理地調整executor的數量以及每個executor的task數量( Spark中可配),從而更好地將task分配給更多節點(獲取更高的Alluxio帶寬),並減少資源爭用的開銷。

8. 預加載數據到Alluxio

儘管Alluxio提供了透明性的異步緩存機制,但是用戶的第一次冷讀取還是會存在性能開銷。爲了避免這個問題,用戶通過使用以下命令行將數據預加載到Alluxio存儲空間中:

$ bin/alluxio fs load /path/to/load \
--Dalluxio.user.ufs.block.read.location.policy=\
alluxio.client.file.policy.MostAvailableFirstPolicy

注意:load命令只是簡單地從底層存儲的這臺節點中中加載目標文件到Alluxio,因此數據寫入到Alluxio的速度往往會受到單個服務器節點的限制。在Alluxio 2.0中,我們計劃實現分佈式加載數據功能以提高加載的吞吐量。


容量管理

Alluxio爲用戶提供一個高層的熱點輸入數據緩存服務。事實上,正確合理的分配和管理緩存的容量配額對取得好性能同樣非常重要。

9. 使用SSD或HDD擴展Alluxio系統存儲容量

Alluxio workers也可以使用本地SSD(固態硬盤)或者HDD(硬盤)來和RAM(內存)存儲資源進行互補。爲了減少數據被剔除Alluxio存儲空間,我們建議在Alluxio存儲層中設置多級存儲目錄而非僅採用單一存儲層,詳情參見文檔。在AWS EC2上運行Alluxio workers的用戶需要注意:將EBS存儲掛載爲本地磁盤會通過網絡傳輸數據,這可能會造成數據訪問速度可能很慢。

10. 關閉被動緩存特性以防緩存抖動


Alluxio客戶端的配置參數alluxio.user.file.passive.cache.enabled可以控制是否在Alluxio中緩存額外的數據副本。這個屬性是默認開啓的(即alluxio.user.file.passive.cache.enabled=true)。因此,在客戶端請求數據時,一個Alluxio worker可能會緩存已經在其他worker上存在的數據。當該屬性爲false時,Alluxio存儲空間中數據將不會有任何額外副本。


值得注意的是,當開啓該屬性時,相同的數據塊可以在多個workers處訪問到,這樣的多副本存儲會降低Alluxio空間的總存儲容量。根據數據集的大小與Alluxio的存儲容量,關閉被動緩存對於不需要數據本地性但希望更大的Alluxio存儲容量的工作負載是有益的。未來, Alluxio 2.0將會支持更細粒度的數據副本設置,例如靈活配置Alluxio中給定文件的副本數最小值和最大值。


作者簡介:範斌, 馮濱, Gene Pang, Madan Kumar均爲Alluxio maintainer。Reid Chan 陌陌大數據平臺開發工程師,負責Alluxio, HBase, Phoenix, Kerberos等開發運維工作,支持鼓勵開源工作,是HBase Committer之一。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章