2min速覽:從設計、實現和優化角度淺談Alluxio元數據同步

語言: CN / TW / HK

目錄

內容速覽:

01. Alluxio簡介

02. Alluxio數據掛載

1. Alluxio統一的數據命名空間

2. Alluxio掛載點

3. Alluxio策略化數據管理

03. Alluxio底層存儲一致性

1. Alluxio寫文件流程

2. Must-Catch寫模式

3. Through寫模式

4. CACHE_THROUGH寫模式

5. ASYNC_THROUGH寫模式

6. 讀文件流程

04. Alluxio和UFS元數據和數據同步

1. 檢查Alluxio元數據/數據一致性

2. 保證Alluxio元數據/數據一致性

3. 數據同步機制

4. Active Sync性能取捨

05. 元數據同步實現與優化

1. 元數據同步原理

2. 性能優化-緩存

3. 算法優化-鎖優化

4. 性能優化-調整並行度

06. 對不同場景的推薦配置

1. 場景1

2. 場景2

3. 場景3

07. 答疑

分享嘉賓


 

內容速覽:

本期分享的題目是Alluxio元數據和數據的同步,從設計實現和優化的角度進行討論,包括以下6個方面內容:

01. Alluxio簡介

Alluxio是雲原生的數據編排平台,通過解耦計算和存儲層,在中間產生了一個數據編排層,負責對上層計算應用隱藏底層的時間細節。

02. Alluxio的數據掛載

掛載操作有一個進階版操作,所做的事情就是讓用户可以把兩個存儲掛載到同一個路徑下,可以互相覆蓋。同時通過配置讀寫策略,定義讀寫文件到哪個存儲裏,並給出操作的先後順序。

03. Alluxio和底層存儲的一致性

Alluxio和底層存儲的一致性,要從Alluxio命名空間中文件的來源説起。

文件的操作分為兩類:

(1)上層應用通過Alluxio創建一個文件,通過Alluxio寫入UFS;

(2)上層應用通過Alluxio讀一個文件,當發現自己沒有這個文件的時候,Alluxio從UFS進行加載。

一致性可以分為兩個部分:

(1)Alluxio UFS元數據的一致性;

(2)Alluxio UFS數據的一致性。

04. Alluxio和UFS的元數據/數據同步

Alluxio提供兩種同步機制,一個是時間戳機制,另一個是基於消息的同步機制。

05. 元數據同步的實現原理和優化

目前元數據的同步粗略分為以下幾個步驟:

(1)RPC線程向UFS讀取文件的元信息。

(2)如果在處理遞歸的時候,發現下面還有其他目錄,整棵樹都需要進行一次搜索,此時把遞歸文件/文件夾提交到同步線程池(sync thread pool)。

(3)預取線程池負責從讀取文件或者文件夾信息,把結果交給這個同步線程池,來加速這個具體的同步過程。

優化主要包含3種方式:

(1)性能優化-緩存;

(2)算法優化-鎖優化;

(3)性能優化-調整並行度;

06. 對不同場景的推薦配置

場景一:文件全部由Alluxio寫入,之後從Alluxio讀取。

場景二:大部分的操作經過Alluxio,但不排除有一些場景會繞過Alluxio直接改動原來裏面的文件。

場景三:大部分或全部更新都不經過Alluxio,而更新又非常頻繁。

 

以上僅為大咖演講概覽,完整內容可點擊觀看


附件:大咖分享文字版完整內容可見下文

01. Alluxio簡介

Alluxio是雲原生的數據編排平台,通過解耦計算和存儲層,在中間產生了一個數據編排層,負責對上層計算應用隱藏底層的時間細節。Alluxio提供了統一的存儲命名空間,在中間層提供了緩存和其他數據管理功能。在下圖可以看到有Spark、Hive、Map reduce這一類傳統的Hadoop大數據計算應用、Presto 這種OLAP類型的數據分析,還有像Tensorflow、Pytorch這樣的AI應用。存儲層比較豐富,包括各種各樣的存儲。

(圖1 Alluxio簡介)

 

下面是Alluxio用户列表,這些公司都公開展示了Alluxio的使用場景。通過粗略分類,看到非常多的行業,包括互聯網、金融、電子商務、娛樂、電信等。感興趣的同學可以關注公眾號,上面有相關文章的彙總。

(圖2 Alluxio的用户展示)

 

02. Alluxio數據掛載

這部分將首先回顧Alluxio如何通過數據掛載實現統一編排層;之後討論Alluxio如何和底層存儲保持一致;介紹元數據和數據同步功能;Alluxio的時間原理和優化;最後對不同場景的推薦配置給出建議。

1. Alluxio統一的數據命名空間

首先介紹數據掛載這個功能。Alluxio通過把底層存儲掛載到Alluxio層上,實現了統一的數據命名空間。

(圖3 Alluxio統一命名空間)

 

上圖的例子中Alluxio掛載了HDFS和對象存儲。Alluxio的文件系統樹就是由左右兩棵樹合成,形成了一個虛擬文件系統的文件系統樹。它可以支持非常多的底層存儲系統,統一把它們稱作Under File System。稱為Under是因為它們都處於Alluxio的抽象層下。Alluxio支持各種各樣不同的底層存儲系統,比如不同版本的HDFS,支持NFS, Ceph, Amazon S3, Google Cloud之類不同的對象存儲。除此之外還支持非常多其他類型的對象存儲,比如微軟Azure、阿里、華為、騰訊,也包括國內其他供應商,如七牛對象存儲。左下圖中的例子是在自己的電腦上運行Alluxio,可以掛載不同的存儲,比如掛載HDFS,另外還可以掛載不同版本的HDFS,掛載對象存儲,掛載網盤。

2. Alluxio掛載點

Alluxio的統一命名空間,實際就是把掛載合成了一個Alluxio的虛擬層。Alluxio的掛載點可以粗略分成兩種:

(1)根掛載點

(2)嵌套掛載點

(圖4 Alluxio掛載點)

 

根掛載點直接掛在根節點上,組成了Alluxio的根節點。如果沒有根節點,無法產生,繼續形成下面的結構。所以要求在配置文件裏面定義根掛載點,系統啟動的時候就進行掛載,不掛載就沒有辦法啟動。

嵌套掛載點比較靈活,可以通過指令進行掛載。通過這個命令行,發出通知,做掛載的操作。同樣地,可以掛載,也可以卸載,就是把Mount換成Unmount。嵌套掛載點是嵌套在目錄的下面,可以掛在某個部分下面,不一定掛載在根節點下面。這裏有個要求,即兩個嵌套點的樹不能互相覆蓋,這樣帶來的好處是比較靈活。如果根掛載點將來需要更換,為了避免需要改配置和重啟服務,可以使用一個dummy的根掛載點,比如就掛載在本地路徑下面,不使用它,且不在它下面創建任何文件,它存在的唯一目的就是可以啟動Alluxio服務。然後在此基礎上,把所有要管理的存儲,都以嵌套掛載點的方式掛載上去。之後如果要改變,就直接卸載更換為其它掛載點,這樣就很靈活。所有掛載和掛載操作,都會記錄在日誌裏,重啟系統,並重啟服務之後,無需再手動操作。

3. Alluxio策略化數據管理

(圖5 Alluxio策略化數據管理)

 

掛載操作有一個進階版操作,目前只包含在商業版本里面。所做的事情就是讓用户可以把兩個存儲掛載到同一個路徑下,可以互相覆蓋。同時通過配置讀寫策略,定義讀寫文件到哪個存儲裏,並給出操作的先後順序。同時Alluxio有一個遷移策略,讓文件可以自動在Alluxio的管理下,在多個存儲之間進行遷移。例如,把HDFS和對象存儲同時掛載到同一路徑下,上層用户只能看到這樣一棵樹,但是實際上背後有兩個不同的存儲。通過配置,讓Alluxio把HDFS的數據,根據一些規則,定期遷移進S3,例如規定將超過七天的數據,認定是不常用到的冷數據之後,把它從HDFS的集羣拿出來,遷移到S3,節省HDFS的存儲空間。

03. Alluxio底層存儲一致性

在把底層存儲掛載到Alluxio的統一命名空間上之後,如何保持Alluxio和底層存儲的一致性?我們在這一部分進行分析。

(圖6 Alluxio與一致性)

 

Alluxio和底層存儲的一致性,要從Alluxio命名空間中文件的來源説起,文件的操作分為兩類:

一類是寫,上層應用通過Alluxio創建一個文件,通過Alluxio寫入UFS;

一類是讀,上層應用通過Alluxio讀一個文件,當發現自己沒有這個文件的時候,Alluxio從UFS進行加載。

一致性可以分為兩個部分:

(1)Alluxio UFS元數據的一致性

(2)Alluxio UFS數據的一致性

 

下面先看寫數據的一致性:

1. Alluxio寫文件流程

首先Alluxio寫文件的流程可以把它抽象成兩步:

第一步是客户端到Alluxio;

第二步是Alluxio到UFS。

(圖7 Alluxio寫文件流程)

 

其中的每一步都可以抽象成下面的三個步驟:

(1)創建文件

(2)寫數據

(3)提交文件

同樣Alluxio到存儲系統也可以同樣地抽象提取。

客户端和Alluxio之間,主要流程分三步:

(1)客户端向發請求創建一個文件;

(2)找到Alluxio Worker寫具體對應的數據;

(3)在寫完數據之後,提交這個文件。

同樣Alluxio到存儲系統也抽象成三步。不同存儲系統的抽象和具備的一致性,都不同,此處進行抽象只是為了便於理解。比如要求強一致性保證,但是很多對象存儲,給的一致性保證會弱很多,比如寫進去之後不能馬上讀到這個數據。在這裏,不考慮這種本身的不一致性的問題。假設Alluxio向存儲提交了之後,就能保證存儲端的文件就是需要的樣子。

Alluxio為了滿足不同的需求,設計了幾種不同的寫策略,下面逐一分析寫策略的流程以及帶來的數據一致性保證。

2. Must-Catch寫模式

(圖8 Alluxio:MUST_CACHE寫模式)

 

首先是常用的MUST_CACHE模式。在這種模式下,只會寫Alluxio緩存不寫UFS。這個模式分為三步:

首先客户端會向Alluxio 發出創建文件請求,創建的文件只是一個空文件,作為一個佔位;

之後Alluxio Worker實現具體數據的寫操作,具體數據會被分割成多個數據塊,作為Block存在於Alluxio Storage裏面。

在緩存寫之後,客户端對Master做提交文件的請求,告訴Master寫了這些數據塊,寫到Worker,然後更新對應的元數據,也知道了這些數據塊和Worker所對應的位置。

在這樣的流程之後,Alluxio不會向UFS創建這個文件,也不會寫這個文件,所以Alluxio和UFS之間的元數據和數據都不一致。

3. Through寫模式

(圖9 Alluxio:Through寫模式)

 

THROUGH的寫模式有所不同,這裏同樣的 createfile() 發出一個請求,然後找Worker寫數據。Worker會做三件事:

(1)創建文件

(2)寫文件

(3)提交文件

在第二步結束後,客户端會向Alluxio 提交這個文件。因為Alluxio 的提交是發生在文件寫完了之後,所以,Alluxio和UFS此時的元數據是一致的。因為沒有數據緩存,所以也不存在數據一致性的問題。

Alluxio的緩存是在需要讀之後才會產生,而這種THROUGH模式是比較適合用來寫已知不再會被Alluxio讀取的數據。所以在這種情況下,元數據是一致的,也不存在數據不一致的問題。

4. CACHE_THROUGH寫模式

下面的CACHE_THROUGH模式就是前面兩種模式的結合。

(圖10 Alluxio:CATCH_THROUGH寫模式)

 

唯一的不同點是在第二步,寫緩存的同時又寫了UFS。在這兩個都成功之後,第二步才會成功,之後客户端才會做提交操作。同樣的道理,因為Alluxio在UFS更新之後才更新,所以兩者的元數據和數據都是一致的。

5. ASYNC_THROUGH寫模式

最後是ASYNC_THROUGH異步寫模式,和前面的模式唯一的區別是第二步中的UFS寫變成了異步,放在了第四步。

(圖11 Alluxio:ASYNC_THROUGH寫模式)

 

在Alluxio寫緩存之後,首先創建了文件之後,在第二步寫了Alluxio緩存;在第二步緩存寫完之後,Worker就向客户端返回成功;然後由客户端向Master提交文件。注意在這個時候,Worker還沒有去UFS創建這個文件,也沒有向UFS寫文件。在Alluxio向客户端返回請求成功之後,在之後的某個時間,由Job Service把這個文件創建到UFS裏面,並且持久化。

需要注意:在異步的模式下,持久化由於某些原因失敗了,比如Alluxio成功之後,突然有人直接向裏面創建了一個同名的文件,在第四步的時候,由於緩存和之間產生了不一致,導致這個文件無法創建、無法寫入。這個時候,Alluxio會有不一致的問題,此時需要人工介入來解決這個衝突。

6. 讀文件流程

前文介紹以上四種不同的寫模式以及一致性保證,現在來看Alluxio的讀文件流程。讀文件也可以粗略分成兩種:冷讀和熱讀。

(圖12 Alluxio:讀文件流程)

 

簡單來説,冷讀情況下Alluxio不知道這個文件,需要從加載元數據和數據。熱讀的時候,緩存命中,不需要加載元數據和數據。

① 冷讀文件

(圖13 Alluxio:冷讀文件)

 

在冷讀流程裏,客户端向Alluxio請求元數據,此時Master還沒有這個元數據,所以會向UFS發出一個請求,並從UFS加載這個元數據,這也稱之為元數據同步。

在客户端具體讀數據的時候,客户端找到Worker,Worker此時還沒有緩存,於是Worker會向UFS做緩存的加載,這就是常説的緩存冷讀。在做完這兩個步驟之後,緩存是和元數據一致的。

② 熱讀文件

(圖14 Alluxio:熱讀文件 )

 

在熱讀的情況下,元數據可以在緩存裏面找到,數據可以在Worker裏面找到;此時不會有對UFS的讀請求。

在緩存命中的時候,如何保證緩存與和是一致的?這裏包括元數據的一致和數據的一致。這個簡單的來説,就是通過Alluxio的元數據和數據的同步機制,也就是下一部分的內容。

04. Alluxio和UFS元數據和數據同步

1. 檢查Alluxio元數據/數據一致性

首先考慮這個問題:在什麼時候需要檢查Alluxio的元數據和數據的一致性?

首先在寫數據的時候需要檢查。如果這個文件在已經存在了,作為緩存,除了放棄這個操作之外,也沒有其他的選項。因為不能在用户不知情的情況下,覆蓋掉用户在裏面的數據。

在讀數據的時候,同樣需要考慮如果文件在裏面已經更新了,那緩存也需要對應進行更新,需要UFS考慮裏面的文件是否發生了變化。

(圖15 檢查Alluxio:元數據/數據一致性)

 

2. 保證Alluxio元數據/數據一致性

元數據和數據的一致性分成兩步來逐個討論。首先討論如何保證Alluxio元數據和一致。Alluxio通過兩種方式來保證:

(圖16 保證Alluxio:元數據/數據一致性)

 

① 通過基於時間的假設

第一種是通過基於時間的假設,在裏面的文件,在一段時間內它是不變的。判斷方法是在每一次文件源信息、元數據請求的時候,檢查Alluxio的元數據是否足夠新。

這個判斷分成了兩個不同的部分:

(1)在每一次請求的時候

(2)檢查這個元數據是否足夠新

這種元數據的同步機制是惰性的,只有在請求的時候才會進行檢查。這樣設計的理念是儘量避免訪問慢的操作、昂貴的操作。這樣的事情越少越好、越懶越好。如果Alluxio所知的文件信息足夠新,就假設Alluxio和UFS是一致的。如果不夠新,就放棄這個假設再做一次同步;同時更新Alluxio裏面的元數據。

② 基於通知

另外一個思路就是拋棄基於時間的假設,基於通知,依賴文件更改的告知。這個不是假設,是一個確定的信息。如果沒有通知文件有變化,就確定Alluxio和現在的文件是一致的。

其次是如何保證Alluxio和UFS的數據保持一致。思路也非常簡單:保持數據的一致,只需要確定元數據及是否一致。這裏做出的假設是:如果UFS的數據,文件內容有所改變,那這個改變一定會反映在文件的元數據上。要麼是文件的長度改變,要麼是這個文件的Hash,也就是哈希值會發生改變。通過觀察這個Alluxio和的元數據,可以發現這些變化點。

如果基於這個假設,Alluxio的元數據和UFS保持一致時緩存和UFS也會一致。如果觀察元數據發現內容有變化,那麼就更新元數據並拋棄已有緩存。在下一次讀的時候,重新加載緩存。如果發現文件的內容沒有變化,只做必要的元數據更新,不拋棄數據緩存。

3. 數據同步機制

Alluxio提供兩種同步機制,這裏先介紹時間戳機制,再介紹基於消息的同步機制。

① 基於元數據時間戳的同步機制

下面先看一下第一種機制,基於元數據時間戳的同步機制。

(圖17 基於元數據時間戳同步)

 

時間戳主要是通過配置項alluxio.user.file.metadata.sync.interval,通常稱之為sync.interval或者interval。比較同步數據上次同步的時間戳和配置項。配置項中有幾種不同的配置方式:

(1)配置為-1,就只在第一次加載的時候進行同步,之後就永遠不再和這個底層存儲去做同步了;

(2)配置為0,每一次訪問的時候,都會進行同步,拋棄所有知識,不做任何假設。

(3)比較常用的一種是用户指定一個配置值,做一個假設,如果上一次同步的時間還沒有超出這個時間,就假設原信息是新的。這個假設是基於對這個文件的瞭解,比如:知道文件的來源,知道文件由哪些業務流程產生的。在此基礎上,可以做一個合理的推斷:只需每隔這樣一段時間去檢查的就足夠。如何進行配置,請看下圖。

(圖18 元數據同步的開銷)

 

(4)這張圖展示了不同的配置方式帶來什麼樣的行為。這張圖的縱軸是一個RPC完成所需時間。不同的環境、不同測試方式都會得到不一樣的結果。主要看相互之間的大小關係。如果把interval設成零,就是每一次都同步,那每一個RPC都會有元數據同步的開銷,延遲會比較高;

(5)如果interval設為-1,完全關掉同步,只要文件存在於Alluxio裏面,完成第一次加載,之後的每一次,RPC都會有一個低的延遲,因為沒有做UFS的操作。

(6)最常用的是設置成一個適中的值,實際上帶來這樣一個結果,就是如果還在interval之內,RPC能得到快速的返回;如果達到了interval,觸發一次同步,那在下一次RPC就會有一個比較高的開銷。通過這樣的方式,實際上把這個元數據同步的開銷,平均到每一次的RPC請求中。這也是為什麼大家比較喜歡這種方式,因為取得了一個平衡。

② 同步時間間隔配置

(圖19 同步時間間隔配置)

 

這個時間間隔具體配置有三個,優先級是由低到高,後面的配置可以覆蓋前面的配置:

第一種最基礎是配置文件/環境變量方式,是最不靈活的方式。因為整個集羣裏面文件都不同,假設也不同,很難使用同樣間隔,所以一般都給一個默認值。

第二種方式是基於路徑的設置做配置。比如:通過管理員指令給這個Alluxio的/data_center1路徑,添加property也就是sync.interval。意思就是假設來自數據中心這個路徑下的所有文件夾下面所有路徑,都按照一個小時一次的時間間隔,在超過一個小時之後,才去再進行一次原文件的同步。這樣的假設的根源就是:知道來自數據中心的文件,它的更新不是特別頻繁,那可以做出這樣的一個判斷。

第三種方式是直接寫在指令裏面。ls是大家最常用的命令。比如:對/data/tables下面的文件做ls操作的時候,給予一個大於零的值。意思就是:如果因為不小心誤操作或者多做了幾次ls,不小心多敲了幾次回車或者腳本寫錯,至少還有一個迴旋的餘地,不會一下子觸發大量元數據同步的操作。

Alluxio還提供一些語法糖指令,比如:loadMetadata指令就是專門為了觸發元數據同步。如果加上-F選項,實際上的意思就是把sync.interval設成0,相當於強制進行一次元數據的刷新。ls和metadata這兩個指令的區別:ls把文件展示在面前,ls的RPC有網絡開銷, 會把信息發給你,客户端要保存下來,並且展示出來。這個不是每一次都需要,假如只是想要觸發一次元數據的同步,只需loadMetadata就可以,返回值只有成功或者失敗,可以節約很多網絡帶寬和的內存開銷。

③ 基於消息的同步機制

以上是基於時間的同步機制,下面看一下另外一種思路,就是基於消息同步機制。

(圖20 基於消息同步)

 

首先需要Alluxio 2.0版本以上,以及Hadoop 2.6.1版本以上,因為HDFS底層的inotify機制是在2.6版本加進去的。實際上發生的就是從HDFS的namenode直接讀取HDFS有哪些文件發生了變化。實現原理就是維護了一個Alluxio和namenode之間通過HDFS的inotify機制保持了一個信息流。Alluxio定期心跳從這個信息流裏面去讀,有哪些文件發生了變化,然後根據這個變化的具體的類型,Alluxio決定要不要再去namenode觸發一次元數據的同步。

這樣的元數據同步是有的放矢,不再是基於時間猜測。每一次同步都是有理有據,知道文件已經改變,稱為Active Sync,也是因為在這裏化被動為主動,不再被動去猜,而是主動知道了有哪些變化,然後主動去觸發同步。但是這個inotify只能告訴告訴我們哪些文件發生了變化以及變化是什麼類型,包含的信息很少,具體要做同步還需要一個元數據同步的機制,所以這一步是繞不開的。具體的使用也非常簡單,可以通過指令來開啟它,也可以關閉,或者查看現在有哪些HDFS路徑或掛載點開啟了這個功能,這些指令也受Journal日誌保護,當開啟了Active Sync功能之後,就會被記錄在Journal日誌裏面,在重啟集羣之後,無需重敲一遍指令。

4. Active Sync性能取捨

Active Sync也不是萬能藥,它做了一些功能的取捨。每種設計都有它的取捨,因此也有適合和不適合的場景。

(圖21 Active Sync性能取捨)

 

Active Sync在確定了文件更改之後,再去做同步操作,它省掉了那些沒有變化、無用的同步操作。但是每一個文件的更改,都會觸發同步操作。具體文件的更改並不一定是客户端的請求,雖然是主動加載它,但實際上並不一定用到,有可能是多餘的操作。

Active Sync和基於時間戳的同步機制,各有利弊。具體選擇時需要進行考量,在後面的章節會總結分析哪種場景適合的配置。同時要注意Active Sync只支持HDFS,原因是隻有HDFS提供API,其他存儲沒有機制可以知道有哪些文件發生了變化,所以沒有辦法來實現。

05. 元數據同步實現與優化

在瞭解了機制後,現在瞭解一下元數據同步的實現原理,然後再看元數據同步的優化。

1. 元數據同步原理

目前元數據的同步粗略分為左下角的這幾個步驟。左上角我們列出了元數據同步的參與者。包括了RPC線程,就是Master端,來自於RPC的線程池。第2個參與者稱作同步線程池(sync thread pool)。第3個參與者稱作預取線程池(prefetch thread pool)。這裏有一個InodeTree,就是Alluxio裏面維護的所有文件的元數據,形成了一個樹狀的結構。圖中的UFS代表着實際的底層存儲。所有對UFS的連線都是一個對外部系統的RPC,是比較昂貴的操作。

(圖22 元數據同步原理)

 

同步步驟分三步:

① RPC線程向UFS讀取文件的元信息。在處理請求ls -R 時,對根節點全量的文件樹進行一次ls操作。此時基於時間戳,需要觸發元數據同步, RPC線程就會讀取文件根節點的信息,檢查掛載HDFS://alluxio根節點的信息。如果發現信息有更新,就去InodeTree裏面找根節點,則RPC線程對應地更新/創建/刪除Alluxio的Inode元信息,並且這個節點進行更新。

② 如果在處理遞歸的時候,發現下面還有其他目錄,整棵樹都需要進行一次搜索,此時把遞歸文件/文件夾提交到同步線程池(sync thread pool)。用廣度優先搜索的方式進行遍歷,也就是常説的BFS。當提交這個任務後。會去同步線程池,在BFS的過程中,按照一定的順序遍歷這棵樹,data -> user -> others -> report …

在這個過程中,主要是做兩件事情:

(1)如果發現走到某個節點,需要對進行一次請求,它不會自己做這個請求,而是把這個請求交給預取線程池(prefetch thread pool)。

(2)如果發現下一步要去遍歷User -> others -> report …

就把任務提前交給預取線程池,告訴它去讀HDFS裏面相關的這些路徑,這也就是稱為預取的原因。在真正處理這個路徑之前,就提前告訴它,因為操作會需要很長的時間,保證性能最好的方式就是提前做這個事情;等任務處理到的時候,已經準備好了。這樣最高地利用了多線程的併發。

在處理每個節點的時候,確認預取線程池準備好的結果;如果有更新,就更新自己的InodeTree。

③ 預取線程池負責從讀取文件或者文件夾信息,把結果交給這個同步線程池,來加速這個具體的同步過程。

2. 性能優化-緩存

在這個時間原理的基礎上,進行了一系列的優化。

(圖23 性能優化-緩存)

 

首先就是做了緩存的優化。主要的設計理念就是因為UFS操作非常貴、非常慢,希望儘可能多的地緩存結果,節省的操作。

緩存優化涉及下面這幾種緩存:

(1)緩存了哪些路徑不存在,因為確定這個緩存不存在,需要去讀取UFS,又需要有一個外部的RPC;

(2)緩存哪些UFS文件最近被讀取過,和時間戳相互配合。知道上一次讀取的時間戳,就可以根據時間戳確定是否再次讀取;

(3)緩存UFS文件的具體信息,預取線程和同步線程用來交換信息的數據結構。在預取完之後把結果放到緩存裏,由這個同步線程將結果更新進Alluxio InodeTree。

3. 算法優化-鎖優化

在緩存優化之外,基於Alluxio的版本迭代,也做了各種各樣的元數據同步優化,其中比較大的就是算法的優化。

(圖24 算法優化-鎖優化)

 

在Alluxio2.3版本之前,所有的RPC線程是自己進行元數據的同步。如果遞歸基於DFS自己遍歷這棵樹,從頭到尾都持有路徑的寫鎖。如果是遞歸,實際上是寫鎖鎖住了整個子樹。在鎖住子樹的過程中,其他線程就無法讀取子樹裏的內容,併發度低,由於是單線程,做了非常多需要Block時間長的操作。

在Alluxio2.3裏面非常大的優化,就是把它改成了多線程併發同步的算法,更多利用線程池操作,DFS改成了BFS。根據需要,讀鎖升級,而非從頭至尾持有寫鎖。讀寫鎖的好處就是讓不同的線程之間有了更高的併發度。這樣在同步某一棵子樹的過程中,其他線程還有機會可以讀到這裏面的內容,所以整體的併發會更高。

4. 性能優化-調整並行度

第三個優化來自於用户對線程池的配置。

(圖25 性能優化-並行度)

 

在Alluxio 2.3裏面加入了線程池之後,可以通過配置參數調整元數據同步的並行度。之前單線程沒有配置可言。調整並行度一般是通過這三個參數。

(1)第一個參數控制了單個RPC請求裏面的這個元數據同步,它的BFS並行度調的越高,並行度越高,更快的機會也就越大,同時對這個系統的佔用也會越多;

(2)第二個是同步線程池大小;

(3)第三個是預取線程池大小。

兩個線城池的大小,建議配置成CPU數的倍數,具體比例以及系統應該配多大,取決於這兩個線程所做工作的時間,以及中間的配合方式。所以沒有給出統一建議,建議根據具體程序運行的時候,CPU比如可以做一些火焰圖、分析延遲以及inodetree更新的延遲,做一個更加合理的配比。默認的配比都是CPU的倍數,如果沒有在無數據同步看到非常明顯的瓶頸,不需要進行特別細粒度的調節。

06. 對不同場景的推薦配置

最後針對不同的場景具體分析問題,推薦比較好的配置。

1. 場景1

(圖26 場景一配置)

 

最簡單的場景就是:所有的寫和讀全都經過了Alluxio。無疑Alluxio所有的元數據都是最新,此時無需做元數據同步。可以關閉元數據同步,提升性能。

2. 場景2

第二個場景稍微複雜一些。大部分的操作經過Alluxio,但是不排除還有一些場景,會繞過Alluxio直接改動原來裏面的文件。

(圖27 場景二配置)

 

在這種場景下,為了保證Alluxio和UFS元數據和數據的一致,還是要保留一部分元數據同步的操作。在這裏分HDFS和非HDFS進行考慮,主要是因為這些HDFS比別人多一個選項Active Sync。

HDFS的情況下,需要考慮這幾個點:如果HDFS更新非常頻繁,就是繞過Alluxio的更新非常頻繁,或者HDFS的namenode的壓力比較大,不建議使用Active Sync,而是使用基於時間的被動同步。使用被動同步的原因是Active Sync是基於Alluxio心跳,去namenode拉取具體有哪些文件發生了改動,每一次拉取都是一次RPC,給namenode施加的壓力。同時如果文件發生了改動,Alluxio就又會向namenode發起RPC去同步這些文件。因此希望儘量減少這些操作來降低namenode的壓力。

如果基於時間的被動同步,能給予減少元數據同步操作的機會,則使用基於時間的被動同步;反之要使用Active Sync,因為更加主動,時效性更高。如果不會帶來性能負擔,完全可以去嘗試一下。

如果不是HDFS,只能用基於時間的被動同步。

建議儘量節約元數據同步的操作,具體做法考慮每個數據、每個路徑更新的頻率大概是如何。基於這樣的考量,儘量給sync interval設一個比較大的值,儘量減少元數據的觸發。

3. 場景3

第三個場景稍微有些不同。大部分或全部更新都不經過Alluxio,而更新又非常頻繁。

(圖28 場景三配置)

 

在這種情況下,使用HDFS而且時效性又非常重要,使用Active Sync是唯一方案。因為如果把sync interval調到一個特別低的值,甚至可能觸發比Active Sync更多的同步。如果把它設成零之後,每一次都不會觸發同步,會進一步加大同步的操作開銷。

如果時效性並不十分重要,建議使用這個基於時間的被動同步。通過節省開銷,減少元數據同步的操作,就能提升系統的性能。同樣如果不是HDFS,則使用基於時間的被動同步。

以上是三個場景的分析,也是今天分享的全部內容。

07. 答疑

Q:在ASYNC_THROUGH mode當中,因UFS原因導致ASYNC上傳始終失敗,這種情況怎麼優雅地處理,以避免不一致?

A:這個問題非常好,問到了痛點。目前沒有一個特別優雅的解決方案。

在異步方式下沒有call back,或者hook的自動介入,只能人為介入。Alluxio2.4商業版中加入了一個job service dashboard查看異步任務的狀態。如果異步持久化失敗了,這個地方可以看到所有失敗的異步任務。目前只能通過這個方式觀察究竟有哪些東西失敗了,然後再人為進行干預。

我們已經注意到job service的問題,打算重新設計整個job service,從幾個方向,可擴展Scalability, Fault-tolerance,包括monitoring, failure mode, 恢復等。未來版本里面會做出更好的設計,有很多場景,並加入人工干預模式。通過開放一些接口,可以自動化地讀到這些東西,或是可能會增加界面,讓用户定義一些方式。

Alluxio到了一個新的階段,要好好思考,用更加scalable的方案去解決這些問題。這些異步的操作已經提上了議事日程。

Q:和HDFS可以不同路徑設置不同的同步時間嗎?

A:可以用配置參數的方法去實現。

分享嘉賓

想要獲取更多有趣有料的【活動信息】【技術文章】【大咖觀點】,請關注[Alluxio智庫]: