經典論文翻譯導讀之《Finding a needle in Haystack: Facebook’s photo storage》

【譯者預讀】面對海量小文件的存儲和檢索,Google發表了GFS,淘寶開源了TFS,而Facebook又是如何應對千億級別的圖片存儲、每秒百萬級別的圖片查詢?Facebook與同樣提供了海量圖片服務的淘寶,解決方案有何異同?本篇文章,爲您揭曉。

本篇論文的原文可謂通俗易懂、行雲流水、結構清晰、圖文並茂……正如作者所說的——“替換Facebook的圖片存儲系統就像高速公路上給汽車換輪子,我們無法去追求完美的設計……我們花費了很多的注意力來保持它的簡單”,本篇論文也是一樣,沒有牽扯空洞的龐大架構、也沒有晦澀零散的陳述,有的是對痛點的反思,對目標的分解,條理清晰,按部就班。既描述了宏觀的整體流程,又推導了細節難點的技術突破過程。以至於譯者都不需要在文中插入過多備註和解讀了^_^。不過在文章末尾,譯者以淘寶的解決方案作爲對比,闡述了文章中的一些精髓的突破點,以供讀者參考。

摘要

本篇論文描述了Haystack,一個爲Facebook的照片應用而專門優化定製的對象存儲系統。Facebook當前存儲了超過260 billion的圖片,相當於20PB的數據。用戶每個星期還會上傳1 billion的新照片(60TB),Facebook在峯值時需提供每秒查詢超過1 million圖片的能力。相比我們以前的方案(基於NAS和NFS),Haystack提供了一個成本更低的、性能更高的解決方案。我們觀察到一個非常關鍵的問題:傳統的設計因爲元數據查詢而導致了過多的磁盤操作。我們竭盡全力的減少每個圖片的元數據,讓Haystack能在內存中執行所有的元數據查詢。這個突破讓系統騰出了更多的性能來讀取真實的數據,增加了整體的吞吐量

1、介紹

分享照片是Facebook最受歡迎的功能之一。迄今爲止,用戶已經上傳了超過65 billion的圖片,使得Facebook成爲世界上最大的圖片分享網站。對每個上傳的照片,Facebook生成和存儲4種不同大小的圖片(比如在某些場景下只需展示縮略圖),這就產生了超過260 billion張圖片、超過20PB的數據。用戶每個星期還在上傳1 billion的新照片(60TB),Facebook峯值時需要提供每秒查詢1 million張圖片的能力。這些數字未來還會不斷增長,圖片存儲對Facebook的基礎設施提出了一個巨大的挑戰。

這篇論文介紹了Haystack的設計和實現,它已作爲Facebook的圖片存儲系統投入生產環境24個月了。Haystack是一個爲Facebook上分享照片而設計的對象存儲技術,在這個應用場景中,每個數據只會寫入一次、讀操作頻繁、從不修改、很少刪除。在Facebook遭遇的負荷下,傳統的文件系統性能很差,優化定製出Haystack是大勢所趨。

根據我們的經驗,傳統基於POSIX的文件系統的缺點主要是目錄和每個文件的元數據。對於圖片應用,很多元數據(比如文件權限),是無用的而且浪費了很多存儲容量。而且更大的性能消耗在於文件的元數據必須從磁盤讀到內存來定位文件。文件規模較小時這些花費無關緊要,然而面對幾百billion的圖片和PB級別的數據,訪問元數據就是吞吐量瓶頸所在。這是我們從之前(NAS+NFS)方案中總結的血的教訓。通常情況下,我們讀取單個照片就需要好幾個磁盤操作:一個(有時候更多)轉換文件名爲inode number,另一個從磁盤上讀取inode,最後一個讀取文件本身。簡單來說,爲了查詢元數據使用磁盤I/O是限制吞吐量的重要因素。在實際生產環境中,我們必須依賴內容分發網絡(CDN,比如Akamai)來支撐主要的讀取流量,即使如此,文件元數據的大小和I/O同樣對整體系統有很大影響

瞭解傳統途徑的缺點後,我們設計了Haystack來達到4個主要目標:

  • 高吞吐量和低延遲。我們的圖片存儲系統必須跟得上海量用戶查詢請求。超過處理容量上限的請求,要麼被忽略(對用戶體驗是不可接受的),要麼被CDN處理(成本昂貴而且可能遭遇一個性價比轉折點)。想要用戶體驗好,圖片查詢必須快速。Haystack希望每個讀操作至多需要一個磁盤操作,基於此才能達到高吞吐量和低延遲。爲了實現這個目標,我們竭盡全力的減少每個圖片的必需元數據,然後將所有的元數據保存在內存中
  • 容錯。在大規模系統中,故障每天都會發生。儘管服務器崩潰和硬盤故障是不可避免的,也絕不可以給用戶返回一個error,哪怕整個數據中心都停電,哪怕一個跨國網絡斷開。所以,Haystack複製每張圖片到地理隔離的多個地點,一臺機器倒下了,多臺機器會替補上來
  • 高性價比。Haystack比我們之前(NAS+NFS)方案性能更好,而且更省錢。我們按兩個維度來衡量:每TB可用存儲的花費、每TB可用存儲的讀取速度。相對NAS設備,Haystack每個可用TB省了28%的成本,每秒支撐了超過4倍的讀請求
  • 簡單。替換Facebook的圖片存儲系統就像高速公路上給汽車換輪子,我們無法去追求完美的設計,這會導致實現和維護都非常耗時耗力。Haystack是一個新系統,缺乏多年的生產環境級別的測試。我們花費了很多的注意力來保持它的簡單,所以構建和部署一個可工作的Haystack只花了幾個月而不是好幾年。

本篇文章3個主要的貢獻是:

  • Haystack,一個爲高效存儲和檢索billion級別圖片而優化定製的對象存儲系統
  • 構建和擴展一個低成本、高可靠、高可用圖片存儲系統中的經驗教訓。
  • 訪問Facebook照片分享應用的請求的特徵描述

文章剩餘部分結構如下。章節2闡述了背景、突出了之前架構遇到的挑戰。章節3描述了Haystack的設計和實現。章節4描述了各種圖片讀寫場景下的系統負載特徵,通過實驗數據證明Haystack達到了設計目標。章節5是對比和相關工作,以及章節6的總結。

2、背景 & 我的前任
在本章節,我們將描述Haystack之前的架構,突出其主要的經驗教訓。由於文章大小限制,一些細節就不細述了。

2.1、背景
這裏寫圖片描述

我們先來看一個概覽圖,它描述了通常的設計方案,web服務器CDN存儲系統如何交互協作,來實現一個熱門站點的圖片服務。圖1描述了從用戶訪問包含某個圖片的頁面開始,直到她最終從磁盤的特定位置下載此圖片結束的全過程。訪問一個頁面時,用戶的瀏覽器首先發送HTTP請求到一個web服務器,它負責生成markup以供瀏覽器渲染。對每張圖片,web服務器爲其構造一個URL,引導瀏覽器在此位置下載圖片數據。對於熱門站點,這個URL通常指向一個CDN。如果CDN緩存了此圖片,那麼它會立刻將數據回覆給瀏覽器。否則,CDN檢查URL,URL中需要嵌入足夠的信息以供CDN從本站點的存儲系統中檢索圖片。拿到圖片後,CDN更新它的緩存數據、將圖片發送回用戶的瀏覽器。

2.2、基於NFS的設計

在我們最初的設計中,我們使用了一個基於NFS的方案。我們吸取的主要教訓是,對於一個熱門的社交網絡站點,只有CDN不足以爲圖片服務提供一個實用的解決方案。對於熱門圖片,CDN確實很高效——比如個人信息圖片和最近上傳的照片——但是一個像Facebook的社交網絡站點,會產生大量的對不熱門(較老)內容的請求,我們稱之爲long tail(長尾理論中的名詞)。long tail的請求也佔據了很大流量,它們都需要訪問更下游的圖片存儲主機,因爲這些請求在CDN緩存裏基本上都會命中失敗。緩存所有的圖片是可以解決此問題,但這麼做代價太大,需要極大容量的緩存
這裏寫圖片描述

基於NFS的設計中,圖片文件存儲在一組商用NAS設備上,NAS設備的卷被mount到Photo Store Server的NFS上。圖2展示了這個架構。Photo Store Server解析URL得出卷和完整的文件路徑,在NFS上讀取數據,然後返回結果到CDN。

我們最初在NFS卷的每個目錄下存儲幾千個文件,導致讀取文件時產生了過多的磁盤操作,哪怕只是讀單個圖片。由於NAS設備管理目錄元數據的機制,放置幾千個文件在一個目錄是極其低效的,因爲目錄的blockmap太大不能被設備有效的緩存。因此檢索單個圖片都可能需要超過10個磁盤操作。在減少到每個目錄下幾百個圖片後,系統仍然大概需要3個磁盤操作來獲取一個圖片:一個讀取目錄元數據到內存、第二個裝載inode到內存、最後讀取文件內容。

爲了繼續減少磁盤操作,我們讓圖片存儲服務器明確的緩存NAS設備返回的文件“句柄”。第一次讀取一個文件時,圖片存儲服務器正常打開一個文件,將文件名與文件“句柄”的映射緩存到memcache中。同時,我們在os內核中添加了一個通過句柄打開文件的接口,當查詢被緩存的文件時,圖片存儲服務器直接用此接口和“句柄”參數打開文件。遺憾的是,文件“句柄”緩存改進不大,因爲越冷門的圖片越難被緩存到(沒有解決long tail問題)。值得討論的是可以將所有文件“句柄”緩存到memcache,不過這也需要NAS設備能緩存所有的inode信息,這麼做是非常昂貴的。總結一下,我們從NAS方案吸取的主要教訓是,僅針對緩存——不管是NAS設備緩存還是額外的像memcache緩存——對減少磁盤操作的改進是有限的。存儲系統終究是要處理long tail請求(不熱門圖片)。

2.3、討論
我們很難提出一個指導方針關於何時應該構建一個自定義的存儲系統。下面是我們在最終決定搭建Haystack之前的一些思考,希望能給大家提供參考。

面對基於NFS設計的瓶頸,我們探討了是否可以構建一個類似GFS的系統。而我們大部分用戶數據都存儲在Mysql數據庫,文件存儲主要用於開發工作、日誌數據以及圖片。NAS設備其實爲這些場景提供了性價比很好的方案。此外,我們補充了hadoop以供海量日誌數據處理。面對圖片服務的long tail問題,Mysql、NAS、Hadoop都不太合適。

我們面臨的困境可簡稱爲“已存在存儲系統缺乏合適的RAM-to-disk比率”。然而,沒有什麼比率是絕對正確的。系統需要足夠的內存才能緩存所有的文件系統元數據。在我們基於NAS的方案中,一個圖片對應到一個文件,每個文件需要至少一個inode,這已經佔了幾百byte。提供足夠的內存太昂貴。所以我們決定構建一個定製存儲系統,減少每個圖片的元數據總量,以便能有足夠的內存。相對購買更多的NAS設備,這是更加可行的、性價比更好的方案。

3、設計和實現
Facebook使用CDN來支撐熱門圖片查詢,結合Haystack則解決了它的long tail問題。如果web站點在查詢靜態內容時遇到I/O瓶頸,傳統方案就是使用CDN,它爲下游的存儲系統擋住了絕大部分的查詢請求。在Facebook,爲了傳統的、廉價的的底層存儲不受I/O擺佈,CDN往往需要緩存難以置信的海量靜態內容。

上面已經論述過,在不久的將來,CDN也不能完全的解決我們的問題,所以我們設計了Haystack來解決這個嚴重瓶頸:磁盤操作。我們接受long tail請求必然導致磁盤操作的現實,但是會盡量減少除了訪問真實圖片數據之外的其他操作。Haystack有效的減少了文件系統元數據的空間,並在內存中保存所有元數據

每個圖片存儲爲一個文件將會導致元數據太多,難以被全部緩存。Haystack的對策是:將多個圖片存儲在單個文件中,控制文件個數,維護大型文件,我們將論述此方案是非常有效的。另外,我們強調了它設計的簡潔性,以促進快速的實現和部署。我們將以此核心技術展開,結合它周邊的所有架構組件,描述Haystack是如何實現了一個高可靠、高可用的存儲系統。在下面對Haystack的介紹中,需要區分兩種元數據,不要混淆。一種是應用元數據,它是用來爲瀏覽器構造檢索圖片所需的URL;另一種是文件系統元數據,用於在磁盤上檢索文件。

3.1、概覽

Haystack架構包含3個核心組件:Haytack Store、Haystack Directory和Haystack Cache(簡單起見我們下面就不帶Haystack前綴了)。Store是持久化存儲系統,並負責管理圖片的文件系統元數據。Store將數據存儲在物理的捲上。比如,在一臺機器上提供100個物理卷,每個提供100GB的存儲容量,整臺機器則可以支撐10TB的存儲。更進一步,不同機器上的多個物理卷將對應一個邏輯卷。Haystack將一個圖片存儲到一個邏輯卷時,圖片被寫入到所有對應的物理卷。這個冗餘可避免由於硬盤故障,磁盤控制器bug等導致的數據丟失。Directory維護了邏輯到物理卷的映射以及其他應用元數據,比如某個圖片寄存在哪個邏輯卷、某個邏輯卷的空閒空間等Cache的功能類似我們系統內部的CDN,它幫Store擋住熱門圖片的請求(可以緩存的就絕不交給下游的持久化存儲)。在獨立設計Haystack時,我們要設想它處於一個沒有CDN的大環境中,即使有CDN也要預防其節點故障導致大量請求直接進入存儲系統,所以Cache是十分必要的。
這裏寫圖片描述

圖3說明了Store、Directory、Cache是如何協作的,以及如何與外部的瀏覽器、web服務器、CDN和存儲系統交互。在Haystack架構中,瀏覽器會被引導至CDN或者Cache上。需要注意的是Cache本質上也是一個CDN,爲了避免困惑,我們使用“CDN”表示外部的系統、使用“Cache”表示我們內部的系統。有一個內部的緩存設施能減少對外部CDN的依賴

當用戶訪問一個頁面,web服務器使用Directory爲每個圖片來構建一個URL(Directory中有足夠的應用元數據來構造URL)。URL包含幾塊信息,每一塊內容可以對應到從瀏覽器訪問CDN(或者Cache)直至最終在一臺Store機器上檢索到圖片的各個步驟。一個典型的URL如下:

http://<CDN>/<Cache>/<Machine id>/<Logical volume, Photo>

第一個部分指明瞭從哪個CDN查詢此圖片。到CDN後它使用最後部分的URL(邏輯卷和圖片ID)即可查找緩存的圖片。如果CDN未命中緩存,它從URL中刪除相關信息,然後訪問Cache。Cache的查找過程與之類似,如果還沒命中,則去掉相關信息,請求被髮至指定的Store機器()。如果請求不經過CDN直接發至Cache,其過程與上述類似,只是少了CDN這個環節。
這裏寫圖片描述

圖4說明了在Haystack中的上傳流程。用戶上傳一個圖片時,她首先發送數據到web服務器。web服務器隨後從Directory中請求一個可寫邏輯卷。最後,web服務器爲圖片分配一個唯一的ID,然後將其上傳至邏輯卷對應的每個物理卷。

3.2、Haystack Directory
Directory提供4個主要功能。首先,它提供一個從邏輯捲到物理卷的映射。web服務器上傳圖片和構建圖片URL時都需要使用這個映射。第二,Directory在分配寫請求到邏輯卷、分配讀請求到物理卷時需保證負載均衡。第三,Directory決定一個圖片請求應該被髮至CDN還是Cache,這個功能可以讓我們動態調整是否依賴CDN。第四,Directory指明那些邏輯卷是隻讀的(只讀限制可能是源自運維原因、或者達到存儲容量上限;爲了運維方便,我們以機器粒度來標記卷的只讀)。

當我們增加新機器以增大Store的容量時,那些新機器是可寫的;僅僅可寫的機器會收到upload請求。隨時間流逝這些機器的可用容量會不斷減少。當一個機器達到容量上限,我們標記它爲只讀,在下一個子章節我們將討論如何這個特性如何影響Cache和Store。

Directory將應用元數據存儲在一個冗餘複製的數據庫,通過一個PHP接口訪問,也可以換成memcache以減少延遲。當一個Store機器故障、數據丟失時,Directory在應用元數據中刪除對應的項,新Store機器上線後則接替此項。

【譯者YY】3.2章節是整篇文章中唯一一處譯者認爲沒有解釋清楚的環節。結合3.1章節中的URL結構解析部分,讀者可以發現Directory需要拿到圖片的“原始URL”(頁面html中link的URL),再結合應用元數據,就可以構造出“引導URL”以供下游使用。從3.2中我們知道Directory必然保存了邏輯捲到物理卷的映射,僅用此映射+原始URL足夠發掘其他應用元數據嗎?原始URL中到底包含了什麼信息(論文中沒看到介紹)?我們可以做個假設,假如原始URL中僅僅包含圖片ID,那Directory如何得知它對應哪個邏輯卷(必須先完成這一步映射,才能繼續挖掘更多應用元數據)?Directory是否在upload階段將圖片ID與邏輯卷的映射也保存了?如果是,那這個映射的數據量不能忽略不計,論文也不該一筆帶過。

從原文一些細枝末節的描述中,譯者認爲Directory確實保存了很多與圖片ID相關的元數據(存儲在哪個邏輯卷、cookie等)。但整篇論文譯者也沒找到對策,總感覺這樣性價比太低,不符合Haystack的作風。對於這個疑惑,文章末尾擴展閱讀部分將嘗試解答。讀者先認爲其具備此能力吧。

3.3、Haystack Cache
Cache會從CDN或者直接從用戶瀏覽器接收到圖片查詢請求。Cache的實現可理解爲一個分佈式Hash Table,使用圖片ID作爲key來定位緩存的數據。如果Cache未命中,Cache則根據URL從指定Store機器上獲取圖片,視情況回覆給CDN或者用戶瀏覽器。

我們現在強調一下Cache的一個重要行爲概念。只有當符合兩種條件之一時它纔會緩存圖片:(a)請求直接來自用戶瀏覽器而不是CDN;(b)圖片獲取自一個可寫的Store機器。第一個條件的理由是一個請求如果在CDN中沒命中(非熱門圖片),那在我們內部緩存也不太需要命中(即使此圖片開始逐漸活躍,那也能在CDN中命中緩存,這裏無需多此一舉;直接的瀏覽器請求說明是不經過CDN的,那就需要Cache代爲CDN,爲其緩存)。第二個條件的理由是間接的,有點經驗論,主要是爲了保護可寫Store機器;原因挺有意思,大部分圖片在上傳之後很快會被頻繁訪問(比如某個美女新上傳了一張自拍),而且文件系統在只有讀或者只有寫的情況下執行的更好,不太喜歡同時併發讀寫(章節4.1)。如果沒有Cache,可寫Store機器往往會遭遇頻繁的讀請求。因此,我們甚至會主動的推送最近上傳的圖片到Cache。

3.4、Haystack Store
Store機器的接口設計的很簡約。讀操作只需提供一些很明確的元數據信息,包括圖片ID、哪個邏輯卷、哪臺物理Store機器等。機器如果找到圖片則返回其真實數據,否則返回錯誤信息。

每個Store機器管理多個物理卷。每個物理卷存有百萬張圖片。讀者可以將一個物理卷想象爲一個非常大的文件(100GB),保存爲’/hay/haystack’。Store機器僅需要邏輯卷ID和文件offset就能非常快的訪問一個圖片。這是Haystack設計的主旨:不需要磁盤操作就可以檢索文件名、偏移量、文件大小等元數據。*Store機器會將其下所有物理卷的文件描述符(open的文件“句柄”,卷的數量不多,數據量不大)緩存在內存中。同時,圖片ID到文件系統元數據(文件、偏移量、大小等)的映射(後文簡稱爲“內存中映射”)是檢索圖片的重要條件,也會全部緩存在內存中*。

現在我們描述一下物理卷和內存中映射的結構。一個物理卷可以理解爲一個大型文件,其中包含一系列的needle。每個needle就是一張圖片。圖5說明了卷文件和每個needle的格式。Table1描述了needle中的字段。
這裏寫圖片描述
這裏寫圖片描述

爲了快速的檢索needle,Store機器需要爲每個卷維護一個內存中的key-value映射。映射的Key就是(needle.key+needle.alternate_key)的組合,映射的Value就是needle的flag、size、卷offset(都以byte爲單位)。如果Store機器崩潰、重啓,它可以直接分析卷文件來重新構建這個映射(構建完成之前不處理請求)。下面我們介紹Store機器如何響應讀寫和刪除請求(Store僅支持這些操作)。

【譯者注】從Table1我們看到needle.key就是圖片ID,爲何僅用圖片ID做內存中映射的Key還不夠,還需要一個alternate_key?這是因爲一張照片會有4份副本,它們的圖片ID相同,只是類型不同(比如大圖、小圖、縮略圖等),於是將圖片ID作爲needle.key,將類型作爲needle.alternate_key。根據譯者的理解,內存中映射不是一個簡單的HashMap結構,而是類似一個兩層的嵌套HashMap,Map<long/*needle.key*/,Map<int/*alternate_key*/,Object>>。這樣做可以讓4個副本共用同一個needle.key,避免爲重複的內容浪費內存空間。

3.4.1、讀取圖片
Cache機器向Store機器請求一個圖片時,它需要提供邏輯卷id、key、alternate key,和cookie。cookie是個數字,嵌在URL中。當一張新圖片被上傳,Directory爲其隨機分配一個cookie值,並作爲應用元數據之一存儲在Directory。它就相當於一張圖片的“私人密碼”,此密碼可以保證所有發往Cache或CDN的請求都是經過Directory“批准”的(Cache和Store都持有圖片的cookie,若用戶自己在瀏覽器中僞造、猜測URL或發起攻擊,則會因爲cookie不匹配而失敗,從而保證Cache、Store能放心處理合法的圖片請求)。

當Store機器接收到Cache機器發來的圖片查詢請求,它會利用內存中映射快速的查找相關的元數據。如果圖片沒有被刪除,Store則在卷文件中seek到相應的offset,從磁盤上讀取整個needle(needle的size可以提前計算出來),然後檢查cookie和數據完整性,若全部合法則將圖片數據返回到Cache機器。

3.4.2、寫入圖片
上傳一個圖片到Haystack時,web服務器向Directory諮詢得到一個可寫邏輯卷及其對應的多臺Store機器,隨後直接訪問這些Store機器,向其提供邏輯卷id、key、alternate key、cookie和真實數據。每個Store機器爲圖片創建一個新needle,append到相應的物理卷文件更新內存中映射。過程很簡單,但是append-only策略不能很好的支持修改性的操作,比如旋轉(圖片順時針旋轉90度之類的)。Haystack並不允許覆蓋needle,所以圖片的修改只能通過添加一個新needle,其擁有相同的key和alternate key。如果新needle被寫入到與老needle不同的邏輯卷,則只需要Directory更新它的應用元數據,未來的請求都路由到新邏輯卷,不會獲取老版本的數據。如果新needle寫入到相同的邏輯卷,Store機器也只是將其append到相同的物理卷中。Haystack利用一個十分簡單的手段來區分重複的needle,那就是判斷它們的offset(新版本的needle肯定是offset最高的那個),在構造或更新內存中映射時如果遇到相同的needle,則用offset高的覆蓋低的。

3.4.3、圖片刪除
在刪除圖片時,Store機器將內存中映射和卷文件中相應的flag同步的設置爲已刪除(軟刪除機制,此刻不會刪除needle的磁盤數據)。當接收到已刪除圖片的查詢請求,Store會檢查內存中flag並返回錯誤信息。值得注意的是,已刪除needle依然佔用的空間是個問題,我們稍後將討論如何通過壓縮技術來回收已刪除needle的空間。

【筆者YY】:每個卷文件包含:number.dat、number.idx兩個文件。dat文件是實際上存儲數據的文件,idx文件是爲了存儲卷服務器掛了之後更快的構建內存中圖片id到元數據映射的。圖片刪除只更新內存和卷文件中,idx文件並不更新。但是下文有方法使得idx文件和dat文件同步

3.4.4、索引文件
Store機器使用一個重要的優化——索引文件——來幫助重啓初始化。儘管理論上一個機器能通過讀取所有的物理捲來重新構建它的內存中映射,但大量數據(TB級別)需要從磁盤讀取,非常耗時。索引文件允許Store機器快速的構建內存中映射,減少重啓時間。

Store機器爲每個卷維護一個索引文件。索引文件可以想象爲內存中映射的一個“存檔”。索引文件的佈局和卷文件類似,一個超級塊包含了一系列索引記錄,每個記錄對應到各個needle。索引文件中的記錄與卷文件中對應的needle必須保證相同的存儲順序。圖6描述了索引文件的佈局,Table2解釋了記錄中的不同的字段。
這裏寫圖片描述
這裏寫圖片描述

使用索引幫助重啓稍微增加了系統複雜度,因爲索引文件都是異步更新的,這意味着當前索引文件中的“存檔”可能不是最新的。當我們寫入一個新圖片時,Store機器同步append一個needle到卷文件末尾,並異步append一個記錄到索引文件當我們刪除圖片時,Store機器在對應needle上同步設置flag,而不會更新索引文件這些設計決策是爲了讓寫和刪除操作更快返回,避免附加的同步磁盤寫。但是也導致了兩方面的影響:一個needle可能沒有對應的索引記錄、索引記錄中無法得知圖片已刪除

我們將對應不到任何索引記錄的needle稱爲“孤兒”。在重啓時,Store機器順序的檢查每個孤兒,重新創建匹配的索引記錄,append到索引文件。我們能快速的識別孤兒是因爲索引文件中最後的記錄能對應到卷文件中最後的非孤兒needle處理完孤兒問題,Store機器則開始使用索引文件初始化它的內存中映射

由於索引記錄中無法得知圖片已刪除,Store機器可能去檢索一個實際上已經被刪除的圖片。爲了解決這個問題,可以在Store機器讀取整個needle後檢查其flag,若標記爲已刪除,則更新內存中映射的flag,並回復Cache此對象未找到。

3.4.5、文件系統
Haystack可以理解爲基於通用的類Unix文件系統搭建的對象存儲,但是某些特殊文件系統能更好的適應Haystack。比如,Store機器的文件系統應該不需要太多內存就能夠在一個大型文件上快速的執行隨機seek。當前我們所有的Store機器都在使用的文件系統是XFS,一個基於“範圍(extent)”的文件系統。XFS有兩個優勢:首先,XFS中鄰近的大型文件的”blockmap”很小,可放心使用內存存儲;第二,XFS提供高效文件預分配,減輕磁盤碎片等問題。

使用XFS,Haystack可以在讀取一張圖片時完全避免檢索文件系統元數據導致的磁盤操作。但是這並不意味着Haystack能保證讀取單張圖片絕對只需要一個磁盤操作。在一些極端情況下會發生額外的磁盤操作,比如當圖片數據跨越XFS的“範圍(extent)”或者RAID邊界時。不過Haystack會預分配1GB的“範圍(extent)”、設置RAID stripe大小爲256KB,所以實際上我們很少遭遇這些極端場景。

3.5、故障恢復
對於運行在普通硬件上的大規模系統,容忍各種類型的故障是必須的,包括硬盤驅動故障、RAID控制器錯誤、主板錯誤等,Haystack也不例外。我們的對策由兩個部分組成——一個爲偵測、一個爲修復。

爲了主動找到有問題的Store機器,我們維護了一個後臺任務,稱之爲pitchfork,它週期性的檢查每個Store機器的健康度。pitchfork遠程的測試到每臺Store機器的連接,檢查其每個卷文件的可用性,並嘗試讀取數據。如果pitchfork確定某臺Store機器沒通過這些健康檢查,它會自動標記此臺機器涉及的所有邏輯卷爲只讀。我們的工程師將在線下人工的檢查根本故障原因。

一旦確診,我們就能快速的解決問題。不過在少數情況下,需要執行一個更加嚴厲的bulk同步操作,此操作需要使用複製品中的卷文件重置某個Store機器的所有數據。Bulk同步發生的機率很小(每個月幾次),而且過程比較簡單,只是執行很慢。主要的瓶頸在於bulk同步的數據量經常會遠遠超過單臺Store機器NIC速度,導致好幾個小時才能恢復。我們正積極解決這個問題。

3.6、優化
Haystack的成功還歸功於幾個非常重要的細節優化。

3.6.1、壓縮
壓縮操作是直接在線執行的,它能回收已刪除的、重複的needle所佔據的空間。Store機器壓縮卷文件的方式是,逐個複製needle到一個新的卷文件,並跳過任何重複項、已刪除項。在壓縮時如果接收到刪除操作,兩個卷文件都需處理。一旦複製過程執行到卷文件末尾,所有對此卷的修改操作將被阻塞,新卷文件和新內存中映射將對前任執行原子替換,隨後恢復正常工作。

3.6.2、節省更多內存
上面描述過,Store機器會在內存中映射中維護一個flag,但是目前它只會用來標記一個needle是否已刪除,有點浪費。所以我們通過設置偏移量爲0來表示圖片已刪除,物理上消除了這個flag。另外,映射Value中不包含cookie,當needle從磁盤讀出之後Store纔會進行cookie檢查。通過這兩個技術減少了20%的內存佔用。

當前,Haystack平均爲每個圖片使用10byte的內存。每個上傳的圖片對應4張副本,它們共用同一個key(佔64bits),alternate keys不同(佔32bits),size不同(佔16bits),目前佔用(64+(32+16)*4)/8=32個bytes。另外,對於每個副本,Haystack在用hash table等結構時需要消耗額外的2個bytes,最終總量爲一張圖片的4份副本共佔用40bytes。作爲對比,一個xfs_inode_t結構在Linux中需佔用536bytes。

3.6.3、批量上傳
磁盤在執行大型的、連續的寫時性能要優於大量小型的隨機寫,所以我們儘量將相關寫操作捆綁批量執行。幸運的是,很多用戶都會上傳整個相冊到Facebook,而不是頻繁上傳單個圖片。因此只需做一些巧妙的安排就可以捆綁批量upload,實現大型、連續的寫操作。

章節4、5、6是實驗和總結等內容,這裏不再贅述了。

4、總結
本篇論文以long tail無法避免出發,探究了文件元數據導致的I/O瓶頸,推導了海量小文件的存儲和檢索方案,以及如何與CDN等外部系統配合搭建出整套海量圖片服務。其在各個痛點的解決方案以及簡約而不簡單的設計值得我們學習。文章末尾將這些痛點列出並與淘寶的解決方案逐一對比,以供讀者發散。

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