Flat Datacenter Storage

簡介

這一段一直在嘗試構建對分佈式系統更深的理解,期望能夠在腦中形成知識圖譜,然而卻不可得。仔細分析了原因,感覺是因爲自己讀論文過於浮躁,讀的多而不精,導致很多細節並無把握,最後雖然看起來讀了很多(安慰自己內心式的自欺欺人),卻往往是丟了西瓜撿了芝麻。今天要說的文章是關於扁平化數據存儲的(FDS),發表在OSDI 2012。很慚愧,作爲一個做分佈式系統的同學,竟然還未認真讀過此文。

動機

傳統的分佈式系統對網絡依賴很嚴重,網絡往往成爲瓶頸。因此,data locality成爲很多分佈式計算任務考慮的重要一環,也就是常說的"Move computation to Data",以減少網絡傳輸帶來的開銷。這種“locality aware”的系統極大的影響系統資源的利用率。

近年來,網絡的傳輸速度越來越快,像全雙工高速互聯網絡已經是標配,從而分佈式系統任務不需要過度的擔心網絡會成爲瓶頸。本文設計的flat storage一反常態,提出“no-local”的概念。對於計算任務來說,所有的存儲都可認爲是“remote”的,不需要去關心data的locality。如果需要數據,直接請求FDS即可(管它跨不跨網絡)。

在這種設計下,可以將數據的粒度劃分的儘可能的小,以儘可能利用遠端多個機器的聚合帶寬。這種小粒度的數據也會任務的再調度提供了很大的優勢。分佈式計算影響嚴重的慢節點問題,會極大的浪費系統資源,整個任務都在等待這個最慢的子任務完成後才能完成,而只有整個任務完成後才能釋放資源。而現在數據粒度很小,子任務也很小,即使某一個子任務很慢,一般也不會花太久的時間,另外對於細粒度的子任務可以立刻進行重新調度,代價也很小。

系統設計

數據組織方式

數據在邏輯上以Blob方式進行組織,一個blob有一個唯一的global ID(128 bit),它通常很大。每個Blob被進一步劃分成大小固定的tracts,tracts很小,HDD的典型配置是8MB。tract從0開始編號。

每一個disk交給一個tractServer來管理,對於一個1TB的硬盤,有大約10^6個tracts,這些tracts的metadata可以完全cache在內存中。對於單個tracts的讀寫,tractServer提供跨越文件系統的訪問,直接使用raw disk接口。

所有的tractServer由metaserver來管理,用於跟蹤tractserver的心跳,進行全局的恢復。

數據訪問接口

既然有了global ID,很容易想到FDS應該是對外提供key-value的接口訪問,根據global ID創建Blob,刪除Blob,追加數據等。單個讀寫操作是原子的,但是多個讀寫之間的順序無法保證。

所有的API訪問都是non-blocking的,每個操作需要綁定一個callback函數,當數據返回後,client library會回調到這些callback函數中。這使得用戶可以同時發起多個請求,以提高性能。例如典型的併發請求是50個。特別類似於Erasure coding的striping layout數據讀方式。

對象定位

對於KV系統,一個關鍵的問題就是對象的定位?如何知道某個Blob上的某一個tract存儲在哪裏。常見的有一致性hash策略或者LSM的樹策略。FDS採用的方案很特別,它是一種確定性的hash算法:

Tract_locator = (Hash(g) + i) mod TLT_Length

其中g是Blob的global ID,i爲這個tract在這個Blob上的索引。

傳統的KV系統在定位key的時候,直接hash(key)後對節點數目求模,帶來的問題就是如果節點數目(其實就是模數)改變的話,絕大object都需要被重新映射,這會涉及到大量的對象遷移,這促進了一致性hash的提出,不再使用物理節點個數作爲模數,取而代之的是使用虛擬節點,每個物理節點管理一部分虛擬節點,虛擬節點的個數不變,從而hash後映射的節點不變,如果增加或者減少物理節點,只需要遷移少量的物理節點。

本文采用類似的套路,這個裏面的TLT_Length是個定值,在系統構建之初已經確定,且不會改變,從而確保了上述公式映射後的Tract_locator的值不變。

TLT表

TLT是一個行表,每行是一個entry,存儲着tractserver的地址。當客戶端發起請求的時候,使用global ID和tract index取得tract_locator,對應到TLT的第tract_lcoator行,從而取得tractserver的地址,然後再向此tractserver發起數據讀取請求。對於單副本的系統,每個entry只包含一個tractserver,對於n副本的系統,每個entry包含n個tractserver。

看到這裏,你有沒有疑問,如果增加或者刪除一個tractserver會發生什麼? 說實話,我當時一直是有疑問,直到看到後面才逐漸明白。

可以看出TLT這個表只存儲server的地址,而不需要存儲Blob以及tracts的具體位置。tract在寫入之後,它的TLT的位置就永久固定了。TLT這個表只有在系統修改tractserver的配置(例如增加,刪除等)纔會發生改變,不過要注意的是:只是entry的內容改變,TLT表的行數目不變。

TLT表的動態性

在這裏插入圖片描述
上圖是一個TLT表的例子,採用的三副本,所有每個entry有3列的replica,其中A,B,C…代表是一塊磁盤(或者是tractserver),相同的字母代表相同的tractserver。要注意的是還有一個version列,這是實現動態添加或者刪除server的關鍵所在。例如現在server B由於故障被metaserver檢測到,於是所有包含B的,那一行的version都會增加1,以表明TLT有新的更新。B所在的位置會被隨機選取的server填充,這些server會負責將缺失的數據填充完畢。

客戶端在讀寫之前首先從metaserver中拿到整個TLT表,每個讀寫請求必須攜帶version號。客戶端在B故障之後開始讀取Row=1的tract,但是發現版本號不匹配,於是必須先向metaserver請求新的TLT表,才能發起請求。通過這種方式,可以確保一直讀到新的數據。

快速恢復

當一個tractserver故障,直接由對應的replication的節點負責恢復即可,越多的節點參與恢復,所需要的時間越短。我們考慮一種情況,有n個server,2副本,TLT表有n行,第i行存儲i和i+1,那麼一個server i故障只會有兩個節點i-1和i+1參與恢復。而如果將這個TLT表擴展爲n*(n-1)行,也就是選取n個裏面的任意兩個組合爲一行,這樣一個server故障的話,會有額外的n-1個server參與恢復,從而每個節點承受的恢復帶寬大大降低1/(n-1),從而降低恢復時間。這也帶來一個問題,任意的兩個故障都會導致數據丟失,因爲任意的兩個故障都對應TLT表的一行。

如果你讀過copyset的論文,大概就會有散佈寬度的概念,數據散佈的越開,數據恢復的時間會越短,但是故障導致數據丟失的概率會增大。

那對於三副本呢,還可以使用n*(n-1)行碼?我們的回答是確定的,n*(n-1)行依然可以確保任意一個故障節點都會有其他全部的節點參與恢復,只需要TLT前兩列replication和2副本一樣即可。我們給TLT表的第3列增加一個額外的隨機分配的server,這樣任意故障3個節點概率竟然不是1了,而是2/n。證明:首先故障節點的前兩個節點在TLT表有2個,TLT表的第3個節點是隨機分配的,因此3個節點剛好撞到TLT表的一行的概率爲2*(1/n) = 2/n。

那對於4副本呢,還可以使用n*(n-1)行,數據丟失概率爲2/(n^2)。

與GFS的比較

metaserver很簡單,只負責維護心跳和TLT表,不需要管理Blob和tracts。因此,FDS中的tract的大小可以非常小,而在GFS中block不能太小,因爲要存儲block的元數據。

讀取一個對象可以活得非常大的聚合帶寬:一次發出多個read quest,利用全網的併發帶寬。

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