首先說下 BLOB 的意思, 英文全稱是 Binary Large OBjects,能夠理解爲任意二進制格式的大對象;在 Facebook 的語境下,也就是用戶在帳戶裏上傳的的圖片,視頻以及文檔等數據,這些數據具備一次建立,屢次讀取,不會修改,偶爾刪除 的特色。node
以前簡單翻譯了 Facebook 的前驅之做 —— Haystack,隨着業務量發展,數據量進一步增大,過去玩法又不轉了,若是全部 BLOG 都用 Haystack 存,因爲其三備份的實現,在這個量級下,性價比很低。可是徹底用網絡掛載+傳統磁盤+Unix-like(POSIX)文件系統等冷存儲,讀取跟不上。因而計算機科學中最經常使用的分而治之的思想登場了。數據庫
他們首先統計了 BLOBs 的訪問頻次與建立時間的關係,而後提出了隨着時間推移 BLOB 訪問出現的冷熱分佈概念(和長尾效應差很少)。並據此提出了熱、溫分開的訪問策略:用 HayStack 當作熱存儲去應對那些頻繁訪問的流量,而後用 F4 去響應剩下的不那麼頻繁訪問的 BLOB流量,在此假設(F4只存儲那些基本不怎麼變更,訪問量相對不大的數據)前提下,能夠大大簡化 F4 的設計。固然有個專門的路由層於二者之上進行了屏蔽,並進行決策和路由。後端
對於 Haystack 來講,從其論文出來時,已通過去了七年(07~14)。相對於當時,作了少量更新,好比說去掉了 Flag 位,在 data file,Index file 以外,增長了 journal file,專門用來記錄被刪除的 BLOB 條目。緩存
對於 F4 來講,主要設計目的在於保證容錯的前提下儘量的減少***有效冗餘倍數***(effective-replication-factor),以應對日益增加的***溫數據*** 存取需求。此外更加模塊化,可擴展性更好,即能以加機器方式平滑擴展應對數據的不斷增加。服務器
我總結一下,本論文主要高光點就是溫熱分開,冗餘編碼,異地取或。網絡
到2014年,Facebook 大概有超 4000 億張圖片。架構
論文的結論是,訪問頻度的熱力圖是存在的,建立時間是影響其變化關鍵因子,並且溫部數據是持續增加的。運維
論文的度量方法也很簡單,就是追蹤其網站上不一樣類型的 BLOB 數據的訪問頻次隨着建立時間變化曲線,建立時間小於一天的數據的訪問頻次大概是建立時間一年的數據的100多倍。具體數據就不列了,能夠去 paper 裏看。異步
而後論文探討了區分熱數據和溫數據的一個界限,經過對訪問頻次和刪除頻次隨着建立時間的變化的分析,對於大部分 BLOG,獲得了一個的大概值:一個月。可是有兩個例外,一個是用戶頭像,一直是熱數據;另一個普通圖片,使用三個月做爲閾值。模塊化
熱數據老是那些頭部數據,相對來講增加較慢。可是歷史數據,也就是溫數據是隨着時間推移而尾巴愈來愈長,這勢必要求存儲架構進行相應的調整。
設計原則是讓每一個組件儘量簡單、內聚而且高度契合其要承擔的工做。這是從 UNIX 以來就一直在強調的一個原則。下圖是整體架構圖,包括建立(C1-C2,由 Haystack 負責),刪除(D1-D2,大部分是 Haystack 負責,少部分是 f4 負責)和讀取(R1-R4 由 Haystack 和 f4 共同負責)。
如前述論文 Haystack 所述,咱們將一批 BLOG 集結爲邏輯卷,儘量減小 meta 信息,從而減小IO次數。每一個邏輯卷咱們設計了 100G 左右的容量,在滿以前是爲 未鎖定 (unlocked) 的狀態,一旦達到容量,就變爲鎖定 (locked)狀態,只容許讀取和刪除。
每一個捲包含三個文件,一個數據文件,一個索引文件和一個備忘文件(journal file)。和 Haystack 論文提到的同樣,數據文件就是記錄 BLOG 自己和其原信息,索引文件就是內存中的查找結構的快照。備忘文件是新增的,它經過記錄全部被刪除的 BLOG 的記錄來進行刪除操做。而原 Haystack 論文中,刪除文件是經過直接修改索引文件和數據文件來實現的。在未鎖定階段,三個文件都可讀寫,在鎖定階段,只有備忘文件能夠讀寫,其餘兩個文件都會變成只讀的。
統籌整個系統,好比提供新的存儲機器;維持一個未鎖定卷(unlocked volumes )的池子;確保全部邏輯卷有足夠的物理捲來備份;根據需求適時建立物理卷;進行週期性的維護任務,好比說數據緊縮(compaction)和垃圾回收。
路由層負責BLOB 存儲系統向對外提供接口,它屏蔽了系統底層的實現,使得能夠方便添加如 f4 同樣的子系統。全部的路由層的機器角色都是同樣的,由於該層將全部狀態(如邏輯捲到物理卷的映射)都存在了另外的數據庫裏(將全部相關狀態收集起來額外存儲,使得剩下的部分無狀態能夠平滑擴展,這也是系統設計經常使用的原則)。這使得路由層的能夠不依賴其餘模塊來平滑擴展。
對於讀取請求,路由模塊會從 BLOB id 中解析出 邏輯卷 id,而後根據數據庫中讀出的映射關係來找到對應的全部物理卷信息。通常來講會從最近一個主機取數據,若是失敗的話,會產生一個超時事件,去下一個物理卷所在的主機進行嘗試。
對於建立請求,路由模塊會選取一個有空閒空間的邏輯卷,而後將 BLOB 發送到該邏輯卷對應的全部物理捲進行寫入(是並行發,仍是鏈式發仍是串行發?)若是遇到任何問題,就會中斷寫,而且已經寫入的數據會被廢棄,且戶從新挑選一個可用邏輯卷,重複上述過程。(看起來像並行寫,容錯策略也超級粗暴)
對於刪除請求,路由模塊會將其發送到全部對應的物理卷(而後就快速返回),而後對應物理主機程序會異步的進行刪除,遇到錯誤就一直重試,直到成功刪除全部對應物理捲上的對應 BLOB。(倒也簡單,但不知道實現的時候是會寫入 journal file 後返回,仍是隻是在內存中標記下就返回。對應的數據文件上的 BLOB 確定是在 compact 的時候纔會刪掉)。
路由層經過將實現細節隱藏,使得(對用戶)無感知地構建溫存儲成爲可能,當一個卷被從熱存儲移到溫存儲的時候,會在二者上同時存在一段時間,直到有效(邏輯捲到物理卷)的映射被更新後,客戶端的請求將被無感知的地導向溫存儲。
轉換層負責處理對檢索到的 BLOB 數據的變換操做,好比圖片的縮放和裁剪。在 Facebook 的老版本的系統中,這些計算密集型的操做會在存儲節點上完成。
增長轉換層能夠解放存儲節點,使其專一於提供存儲服務。將計算任務分離出來也有利於將存儲層和轉換層進行獨立的擴展。而後,它也可讓咱們精確地控制存儲節點的容量以剛好知足需求。更進一步,也可使咱們針對不一樣任務類型進行更優的硬件選型。好比說咱們能夠將存儲節點設計爲具備大量硬盤,但只有一個CPU和少許內存。
一開始是爲了處理熱點 BLOB 數據的請求,緩解後端存儲系統的壓力。對於溫存儲來講,它也能夠減少其請求壓力。這裏說的應該是 CDN 以及相似 akamai 內容分發商提供的緩存。
Haystack 開始是被設計來儘量的提升 IOPS 的,經過攬下全部建立請求,大部分的刪除請求和高頻讀請求,使得溫存儲的設計能夠大大簡化。
如相關 paper 提到的,Haystack 經過合併 BLOB 和簡化元信息使得 IOPS 大大提升。具體來講,包括將邏輯卷設計爲集合了一批 BLOB 的單個文件,利用三個物理卷對同一個邏輯捲進行冗餘備份等等。
讀請求過來後,會在內存中拿到請求的 BLOB 的元信息,而且看其是否被刪除,而後經過物理文件位置+ offset + size ,僅進行一次 IO 拿到對應 BLOB 數據。
當主機收到建立請求後,會同步的將 BLOB 數據追加到數據文件上,而後更新內存中的元信息並將更改寫入索引文件和備忘文件中(備忘文件不是隻記錄刪除操做嗎?)。
當主機收到刪除請求時,會更新索引文件和備忘文件。可是對應數據仍然存在於數據文件中,按期地咱們會進行緊縮操做,纔會真正的刪除數據,並回收相應空間。
Haystack 經過在一個數據中心的不一樣機架上各放一個副本,而後再不一樣數據中心再放一個副本的三副本策略得到了對硬盤,主機,機架甚至數據中心的容錯能力。而後經過 RAID-6(1.2倍冗餘數據編碼,可以小範圍的糾正錯誤,能夠讀讀糾錯碼之類的文章)進行額外的硬盤容錯,更上一層保險。可是付出的代價是 3*1.2 = 3.6 倍的有效冗餘因子,這也是 Haystack 的侷限之處,雖然最大化了 IOPS,可是在存儲使用上卻並不高效,形成了不少 BLOB 的數據冗餘。
有些類型的 BLOB 具備必定的過時時間,好比說用戶上傳的視頻,會從原始格式轉化爲咱們的存儲格式。在此以後原始視頻須要刪掉。咱們會避免將此類具備過時時間的數據移動到 F4 上,從而讓 Haystack 負責這些頻繁的刪除請求,並經過頻繁緊縮來回收空間。
設計目標是在容錯的基礎上儘量高效。也就是在可以容忍硬盤錯誤,主機故障,機架問題,數據中心災難的前提下,把有效冗餘倍數降一降。
f4 是溫數據存儲架構的子系統。包含一系列 數據單元(cell),每一個 cell 都在同一個數據中心(機房,datacenter)裏。當前(2014)的 cell 包含 14 個機架,每一個機架有15個主機,每一個主機有三十塊 4T 容量的硬盤。cell 負責存儲邏輯卷,每一個邏輯卷實際存儲時,會將數據利用裏所碼(Reed-Solomon coding,簡稱RS,這是前面提到的RAID-6 標準的重要成員)進行冗餘編碼,好比 RS(n, k) 就是每存 n 個比特,就要編入額外的 k 個比特,以此來容忍最多 k 個比特的出錯。經過這種編碼方式能夠解決硬盤,主機和機架出錯問題。
此外利用異或編碼(XOR coding)來解決跨數據中心或者地理位置的出錯問題。咱們選取兩個不一樣機房的對等數量 volume/stripe/block 結成對子,而後將每一對的異或值存在第三個機房。
每一個 f4 數據單元(cell) 只處理鎖定的卷(Volume),也就是隻用支持讀取和刪除操做。數據文件和索引文件都是隻讀的,Haystack 中的備忘文件在 f4 中是不存在的。咱們用了另外一種方式來達到「刪除」的效用,將每一個 BLOB 進行加密後存儲,將用於加密的祕鑰(key)存在一個外部數據庫中。響應刪除請求時,只須要將 BLOB 對應的祕鑰刪掉就行(有點絕,對用戶提供了隱私保證,並且將刪除操做的延時降到很低)。
索引文件因爲比較小,直接用了三副本存儲來保證可靠性,能夠省去編解碼帶來的額外複雜度。數據文件用 n=10, k = 4 的裏所碼進行編碼。具體來講,將每一個數據文件切分爲 n 個連續的數據塊(block),每一個具備固定尺寸 b(最後一個塊不滿,而又寫不進去一個新 BLOB 的狀況下,在結尾補零,相似這種打 padding 也是數據對齊經常使用的手法);對於每 n 個這樣的塊,生成 k 個一樣尺寸的奇偶校驗塊(parity block),這樣 n+k 個數據塊構成一個邏輯上的 條帶(stripe)。同一條帶上的任意兩個塊互稱爲兄弟塊(companion block)。正常讀取時,能夠直接從數據塊中讀(我猜是那n個塊,不用額外進行計算還原,有待考證,還得看裏所碼原理以及具體實現)。若是某些塊不可用了,就會在同一條帶上任取 n 塊,解碼後還原;此外還有個性質,就是讀取 n 個 block 上對應的 n 截數據(好比某個 BLOB),也能夠進行解碼(這兩個性質都是編碼決定的,相似於 n 元線性方程組,有 k 個冗餘方程)。
一般 b 爲 1G,即每一個數據塊(Block)選取 1G 大小(這有個疑問,看起來每一個Block仍在Volume中,而不是單獨拿出來,那麼定位一個物理block是否是就得經過 volume 文件打開句柄 + offset + length),選這麼大有兩方面的考慮,一個是儘可能減少 BLOB 的跨塊機率,以減小讀取一個 BLOB 還得屢次 IO 的頻率;另外一個是下降 block 所須要維護的總元信息數量。不選更大的是由於重建起來會付出更大代價(但爲何就是 1G 呢?)。
下圖是架構圖,接下來逐一介紹下各個模塊。
name node 維護了數據塊、奇偶校驗塊 到實際存儲這些塊的存儲節點(也就是下一節的存儲節點)之間的映射;這些映射(利用標準技術?還說參考了GFS,這沒大看懂,留個坑回頭讀 GFS 填上)分配到存儲節點中。名字節點使用主從備份策略進行容錯。
存儲節點是 Cell 的主要組件,處理全部常規的讀取和刪除請求。對外暴露兩個 API:Index API 負責提供 Volume 的有無檢查和位置信息;File API 提供實際的數據訪問。(File API 與 Data API 的區別估計在於,前者是提供上層抽象 BLOB 的操做接口,然後者會暴露底層數據塊 Block 的訪問的接口)
存儲節點將 index file (包括BLOB到 volume 的映射,偏移量和長度)存在硬盤上,而且加載到自定義存儲結構的內存中。此外還維持了volume 偏移量到物理數據塊的映射(因爲一個 volume 被整齊的切成了好多 block, 所以定位一個數據塊的邏輯位置,須要記下他的所在volume+offset)。上述兩個信息都被存在內存裏,以免硬盤 IO(彷佛後面也有變化,index 也不小隨着ssd更便宜,存ssd也能夠)。;
因爲每一個 BLOB 都是加密過的,其祕鑰放在額外的存儲,一般是數據庫中。經過刪除其祕鑰就能夠達到事實上的 BLOB 的刪除,這樣就避免了數據緊縮(爲何能夠不回收那些刪除空間呢,畢竟對於文存儲,刪除量只有很小一部分,以前的溫存儲的假設就用在這裏);同時也省去了用備忘文件(journal file)來追蹤刪除信息。
下面說下讀取流程。首先經過 Index API 來檢查文件是否存在(R1過程),而後將請求轉到該 BLOB 所在的數據塊所在的存儲節點上。Data API 提供了對數據塊和奇偶校驗塊(parity block)的訪問。正常狀況下的讀請求會被導向合適的存儲節點(R2流程),而後直接從該 BLOB 所在塊讀取它(R3)。在失敗的狀況下,會經過 Data API 讀取損壞模塊中的全部 n+k 個兄弟模塊中無缺的 n 個塊,送到回退節點(back-off node)進行重建。
在進行實際數據讀取(不管是 R1-R3 的正常流程仍是 R1,R4,R5的出錯回退流程)的同時,路由層(route tier)會並行的從外部數據庫讀取該 BLOB 對應的祕鑰,而後在路由層進行解密操做,這是一個計算密集型任務,放在這裏可讓數據層專一於存儲,而且兩層能夠獨立的擴展。
就是負責給出正常讀取流程出錯時的一種回退方案。
當 cell 中出現故障時,會有些塊變得不可用,就須要從其兄弟塊和奇偶校驗塊中進行在線恢復。回退模塊都是IO稀疏而計算密集型節點,來處理這些計算密集型的在線恢復操做。
回退模塊對外暴露 File API,以處理正常讀取失敗狀況下的回退重試(R4)。在此時,讀取請求已經被一個主卷服務器(primary volume-server,不過這是個什麼節點?)解析成了數據文件,偏移量和長度的元組,回退節點會向除損壞數據塊以外的 n-1 個兄弟塊和 k 個奇偶校驗塊中對應偏移量,讀取對應長度的信息。只要收到n個迴應(估計是並行發?而後爲了節省時間,收到任意n個迴應就開始幹活,進行差錯糾正?)
固然了,回了照顧讀取延遲,每次進行在線回退讀糾錯的時候,都只恢復對應BLOB的數據而不是其所在的整個數據塊 Block 的信息。整個數據塊的恢復會交給重建節點(Rebuilder Nodes)離線的去作。
在民用物理機數目達到必定量級的狀況下,硬盤和節點的故障是不可避免的。存儲在損壞模塊上的數據塊就須要進行重建。重建節點是存儲稀疏而計算密集型的,負責在後臺默默地進行重建工做。每一個重建節點經過探針(按期掃描其負責的範圍內的數據?仍是在每一個數據節點上安裝探針?)檢測數據塊錯誤,而且將其彙報到協調節點(Coordinator Nodes),而後經過取出同一條帶(Stripe)上兄弟塊和奇偶校驗塊中的沒有損壞過的n塊,對損壞節點進行重建(若是n+k中有其餘模塊壞了估計也一併重建吧)。這是一個很重的處理過程,而且會給存儲節點帶來極大的網絡和 IO 負載。所以重建節點會對其吞吐量進行限流,以防對正常的用戶請求形成不利影響。而統籌調度重建工做,以儘可能減少數據丟失的風險,則是協調節點的工做。
一個數據單元(cell)須要不少平常的運維任務,好比安排(大概就是肯定一個重建順序,而且在不一樣的重建節點間進行分配吧)損壞的數據塊重建,調整當前的數據分佈以儘量減少數據的不可用機率。協調節點也是存儲稀疏計算密集型的,用來執行數據單元範圍的任務。
如以前提到的,一個數據條帶上的不一樣數據塊須要被分散放置於不一樣的數據容錯區域內以最大化可靠性。然而,在通過故障,重建和替換後,確定會有一些不符合上述原則的狀況,好比兩個同條帶上的數據塊被放在了同一個數據容錯區域中。協調節點會運行一個平衡擺放位置的進程去檢查一個數據單元中的數據塊分佈。和重建操做同樣,也會給存儲節點帶來至關大的額外硬盤和網絡負載,所以協調節點也會進行自我限流以減少對正常請求的影響。
單個 f4 的數據單元都存在一個數據中心中,所以難以抵禦數據中心的故障。因而在開始的時候,咱們將兩份一樣的數據單元放在不一樣的數據中心中,這樣一個損壞仍然能夠利用另外一個對請求進行響應。這樣將有效冗餘因子從 Haystack 的 3.6 下降到了 2.8 。
考慮到數據中心級別的故障仍是很稀少的,咱們找到了一種能夠進一步減少有效冗餘因子的方案——固然,也減少了吞吐率。不過,如今XOR方案能夠將有效冗餘因子進一步作到 2.1。
地理備份異或編碼(XOR coding)方案經過將兩個不一樣的卷(Volume,大小同樣)作異或後的結果放在第三個數據中心的方式,提供了數據中心級別的容錯。如圖9同樣,每一個數據卷中的數據塊和奇偶校驗塊被與等量的其餘數據塊或者奇偶校驗塊(稱爲哥們塊,buddy block)被拿來作異或運算,獲得其異或塊(XOR block)。這些異或模塊的索引也是簡單的三備份存儲。
一旦某個 datacenter出現問題致使整個 volume 不可用,讀取請求會被路由到一個叫作 geo-bakoff node ,而後會從兩個 buddy node 和 XOR node 所在數據中心去取對應 BLOB 數據,進行損壞 BLOB的重建。選擇XOR編碼,固然是簡單又能知足需求。
負載因子的計算,(1.4 * 3) / 2 = 2.1
基本思想大概就這些,剩下的不翻了。可是論文說的有點囉嗦,同一個點在不一樣地方說了好幾遍,但同時一個模塊有時又分散在不一樣模塊中,很差連成一個總體,在這裏,我簡單總結一下。
一個數據單元(cell)存在一個數據中心中,包含 14 個機架。一個邏輯上的卷 (Volume),大約 100G,被分爲 100 個 1G 的數據塊(Block);而後每 10 個數據塊做爲一組(Companion Block)進行數據冗餘編碼(RS編碼)後,產生 4 個新的奇偶校驗塊(Parity Block),這 14 個數據塊+奇偶校驗塊稱爲一個條帶(stripe),被分別放置在不一樣機架上以進行容錯。其中哪些數據塊屬於一組的映射關係在名字節點( Name Node) 中維持着。
在存儲節點上,內存中須要維護兩個映射做爲 index 信息,一個是 BLOB id 到 volume,偏移量和大小的映射,一個是 volume 偏移量到 Block 實際物理位置的映射。當讀請求失敗的時候,讀取請求連同一些元信息(好比所在數據塊 id,以及在其上的偏移量)被導向回退節點(Backoff Node)。回退節點會根據 BLOB id 所在的 Block id 在 Name Node 拿到條帶上其餘數據塊位置信息,以及偏移量,只對該 BLOB 的全部對等數據進行解碼,還原出該 BLOB 後返回。
此外,協調節點(Coordinator Nodes)會根據探針的心跳信息,獲得全局數據分佈和狀態信息。協調節點據此將損壞的模塊交給重建節點(Rebuilder Nodes)進行數據重建;而且平衡、維持條帶上的全部塊被放在不一樣的數據容錯閾。
最後,在兩個不一樣數據中心的將全部數據塊配對後,進行異或(XOR)操做,獲得一個異或結果,放在第三個數據中心。這樣,這三個數據中心的任何數據條帶損壞到 RS 碼都沒法拯救的狀況下(好比有四個以上機架出問題了),就能夠經過其餘兩個數據中心數據進行 XOR 操做來搶救一下。
數據文件(data file):存儲一堆 BLOB 和其元信息的的文件
索引文件(index file):記錄 BLOB 在數據文件偏移量,長度和簡單信息的文件,用來快速 seek 取出 BLOB。
備忘文件(journal file):在 Haystack 中,用於記錄全部的刪除請求。
有效備份因子,有效冗餘倍數(effective-replica-factor):實際佔用的物理空間和要存的邏輯數據大小之間的比值。
兄弟模塊,夥伴模塊(companion block):用於編碼的 n+k 個數據塊中那 n 個模塊的稱呼。
奇偶校驗塊(parity block):用於編碼 n+k 個數據塊中那 k 個模塊的稱呼
溫存儲(warm storage):相對於熱存儲,指那些專門針對訪問頻次不怎麼高的數據所構建的存儲。
存儲節點,存儲機器(storage nodes,storage machines):都是指的負責存儲最終數據的的物理機。
緊縮(compact):Haystack 中會按期地檢查數據文件,將其複製一遍,可是略過全部重複和已經標記刪除的數據,從而回收對應空間。
副本,備份(replica):一種冗餘策略,廉價通用型機器上免不了出錯,爲了留有後手進行恢復,最經常使用策略就是多存幾份了,這幾份一樣的數據成爲多副本或者多備份。
祕鑰(encryption key):用來給 BLOB 進行加密的鍵
回退模塊(backoff node):其實我以爲翻譯成兜底模塊也挺好哈哈,就是應對出錯,取 n 個兄弟塊來進行恢復的。
數據單元(cell):由14個機架,每一個機架上有15臺機器組成的一個數據部署和回滾的的單元。
數據卷(volume):分邏輯卷和物理卷,包含多個數據條帶。
數據條帶(stripe):原始n個數據塊和生成的k個奇偶校驗塊所組成的集合,稱爲條帶。
數據塊(block):通常是1G左右,被分散在不一樣容錯單元中。