本文章轉載于:https://zhuanlan.zhihu.com/p/54245707
由于統一通路對象存儲(如S3)和HDFS資料的場景的出現和普及,Apache Spark結合Alluxio的大資料棧越來越受歡迎。此外,越來越流行的計算與存儲分離的架構導緻計算端查詢延遲增大。是以,Alluxio常被用作貼近計算端的熱資料存儲以提高性能。為了能夠獲得最佳性能,使用者需要像使用其他技術棧組合一樣遵循最佳的實戰經驗。本文介紹了在Alluxio上運作Spark時,對于實際工作負載性能調優的十大技巧。
常用連結
- Alluxio項目官網
- Alluxio Inc網站
- Alluxio在各大廠用例
- 關注Alluxio微信公衆号: Alluxio_China
關于資料本地性的技巧
資料本地性就是盡量将計算移到資料所在的節點上進行,避免資料在網絡上的傳輸。分布式資料并行環境下,資料的本地性非常重要。提高資料本地性能夠極大地提升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 reads
的兩個名額。此外,還可以觀察From RemoteInstance
cluster.BytesReadAlluxioThroughput
兩個名額的值進行判斷。如果資料的本地讀寫吞吐量為零或遠低于總吞吐量,那麼說明該Spark作業可能沒有本地的Alluxio worker進行資料互動。cluster.BytesReadLocalThroughput
- 方法2:利用 dstat 等工具檢測網絡流量,這樣我們就可以看到短路讀取是否發生以及實際的網絡吞吐量。YARN使用者還可以在
日志中查找如下資訊:/var/log/hadoop-yarn/userlogs
,觀察遠端讀取是否發生,或者都是短路讀取。運作Spark作業時,使用者可以通過配置YARN相關參數(例如INFO ShuffleBlockFetcherIterator: Started 0 remote fetches in 0ms
)來開啟收集這些日志。--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用戶端有一些配置參數可供調優。具體地,
-
:即資料本地性降級的等待時間,預設是3000毫秒,如果逾時就啟動下一本地優先級别的task。該配置參數同樣可以應用到不同優先級的本地性之間(spark.locality.wait
,PROCESS_LOCAL
NODE_LOCAL
RACK_LOCAL
)。ANY
- 我們也可以通過将參數
設定為特定的本地性級别,進而實作自定義每個節點的本地性等待時間。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之一。