概覽

Haystack 的基本思想就是將索引信息放在內存中,避免額外的IO。為了做到這一點,主要進行了兩方面的設計:

  1. 將小文件集合成大文件,減少文件數,從而減少了元信息的數目。
  2. 精簡文件元信息,去掉一切在 Facebook 場景中不需要的 POSIX 語義中元信息。

這樣就可以將數據元信息減小到一個內存可以放的下的量級,基本上每次每次數據訪問同一個一次 IO 就可以完成,而非以前的好幾次。

Facebook業務量級

2600w, 20P

峯值每秒鐘100w圖片。

key observation

傳統文件系統設計導致了過多的元信息查找,我們進行的減少了每個圖片的元信息使得Haystack 能夠在內存中進行所有的元信息查找操作。

定位

是一個對象存儲,用來存分享的圖片,針對的是一次寫,經常讀,從來不改,很少刪的數據集。不得不造輪子,因為傳統的分揀系統在如此高並發下,表現太差。

緣起

POSIX:目錄組織,並且每個文件存了過多的我們用不到的元信息,如用戶許可權。而在讀文件的時候,元信息必須預先讀入內存,在有數十億量級的文件時,這幾乎是不可忍受的。尤其對於NAS(network attached storage)

一個圖片讀寫需要三次訪問磁碟:

  1. 將文件名翻譯為inode
  2. 從磁碟讀入inode
  3. 根據inode信息讀文件本身

特點

高並發低延遲: CDN太貴,只能減少單個文件的metadata,然後將所有metadata保存在內存中,盡量做到一次訪盤。

容錯:異地備份

花銷: 比 CND 便宜。

簡單: 沒有那麼長時間的完善和測試,為了可用,只能做到盡量簡單。

舊流程

首先訪問 webserver,獲取圖片的的全局URL,裡麪包含圖片的位置信息。然後用CDN做緩存,命中返回,否則去 photo storage載入到CDN再訪問。

一個文件夾存數千張圖片的時候,目錄到block映射增大,一次不能載入內存,更加重了訪問次數。

總之,我們明白了無論是內部緩存還是外部緩存(memcached),都無助於長尾形式的訪問流量。

RAM-to-disk 比率,提高這個比率,但是一個圖片最少佔一個inode,從而帶來至少數百比特的額外元信息佔用。

新設計

傳統來說,如果網頁靜態內容服務如果出現瓶頸,就用CDN來解決。但是長尾文件的訪問還需靈眸他路,當然我們承認非熱門圖片的請求是可以訪盤的,但是我們可以減少這個次數。

一個圖片存一個文件元信息太多,那麼直觀的想法就是一堆圖片存成一個文件。

接下來我們區分兩種元信息:

  1. 應用元信息,用來構建瀏覽器可以用來檢索圖片的URL。
  2. 文件系統元信息,用以讓某個主機在其磁碟上定位該圖片。

概覽

三個主要組件:Haystack Store, Haystack Directory, Haystack Cache,簡略起見,我們將 Haystack 省略代稱為存儲,目錄和緩存。

  1. Store 對圖片的持久化層做了封裝,是唯一管理圖片文件元信息的組件。實現上來說,我們將主機的存儲進行劃分,稱為一個個物理上的 Volume。然後不同主機上的某幾個物理 volume 作為一組稱為一個邏輯上的 volume。那麼這幾個物理上的volume就是該邏輯 volume 的 replica,以進行備份,容錯和分流。
  2. Directory 維護了邏輯到物理的映射,以及另外一些應用信息,包括圖片到邏輯volume的映射,有空閑空間的邏輯volume
  3. Cache 起內部CDN的作用。當對熱門圖片的請求過來時,上層CDN不可用或者沒命中時,屏蔽了直接對store的訪問。

一個典型的要訪問CDN的圖片請求URL長這樣:http://?CDN?/?Cache?/?Machine id?/?Logical volume, Photo?

在CDN,Cache,Machine,層如果命中就返回,否則剝離該層次地址,然後將請求轉發到下一層。

上傳一個圖片的時候,首先請求被打到 web server 中;然後 server 從 Directory 中選擇一個可寫的邏輯卷(logic volume)。最後,webserver 給該圖片一個 id,並且將圖片上傳到選定的邏輯卷對應的幾個物理卷中。

這裡我有兩個問題:1. 如何選擇邏輯卷?和圖片請求的大區相關嗎? 2. 明顯有可能造成不一致。即從 Directory 選擇邏輯卷後,寫的時候發現全滿了或者網路問題寫失敗怎麼辦?試看paper接下來怎麼說。

Haystack Directory

Directory 負責四方面的功能:

  1. 維護邏輯捲到物理卷的映射

webserver 使用該映射關係進行上傳圖片和構建圖片請求URL

  1. 負責邏輯卷間寫的負載均衡和跨物理卷間的讀
  2. 決定一個請求是由CDN處理還是Cache處理
  3. 檢查是否由於邏輯卷達到容量或者操作原因導致邏輯卷變為只讀

Haystack Cache

Cache 會從CDN或者直接從用戶瀏覽器收到圖片的HTTP請求。Cache被組織成了分散式的Hash Table,並且用圖片的 id 去定位 cache。名不中,則從根據 URL 從指定 Store 拉去圖片。

只有當滿足

  1. 請求直接從瀏覽器而非 CDN 過來。
  2. 圖片被從可寫伺服器拉去

才會在 Cache 中緩存該請求的圖片。

理由二是因為a. 圖片被寫後往往會很快被讀取 b. 讀寫分開速度會更快

Hyastack Store

Haystack 的主要設計就在於 Store 的組織上。

每個物理卷在物理上是一個大文件,每個 Store 機器和通過一個邏輯卷id + offset 來迅速定位一個圖片。Heystack 一個關鍵設計就在於此:不用硬碟訪問就可以獲取某個圖片的文件名,偏移量和大小。因為Store 的每個物理節點一直在內存中維護著每個物理卷對應文件的描述符和圖片 id 到其元信息的映射。

具體組織上來說,每個物理卷就是一個包含一個超級塊(superblock)和一系列 needles。每個needle保存圖片元信息和圖片本身。其中 flag 是為了標記該圖片是否被刪除,cookie 是在上傳圖片時隨機生成,為了防止猜 url ,而 alternate key 是為了保存同一個圖片的不同解析度的文件而增加的。Data Checksum 進行數據校驗,而 padding 可能是為了硬碟塊對齊。

為了加快圖片的訪問,物理機在內存中維護所有圖片的基本元信息,如下圖,只剩下了最簡單的幾項信息。為了加快機器重啟後內存中元信息的構建,物理機會將內存中的這些元信息定期做 snapshot 即 index file;其順序和 store file 保持一致。

根據這個中物理結構,我們來說回 Haystack 每個 API 對應的實際操作:

Photo Read

一個來自 Cache 的讀請求會攜帶 volume id, key, alternate key 和 cookie,Store 物理機會在內存中查找圖片相關的元信息,找到(文件描述符,offset 和 size), 從而讀出該圖片文件及其元信息;然後進行 cookie 比對和 checksum 校驗,通過後返回圖片數據。

Photo Write

來自 web server 的寫請求會攜帶邏輯 volume id,key,alternate key,cookie 和 圖片數據,每個物理機會同步的將這些信息追加到對應的 Store file 中。對於圖片的 update 請求(比如圖片旋轉),我們也是進行簡單的追加。但這樣會造成重複的 key + alternate key;如果其和原圖片落在不同邏輯卷中,Directory 只需要更新圖片到邏輯卷的映射就行;如果落在同一個邏輯卷,那麼通過 offset 就能獲取版本的新舊(看起來除了index file 這種順序組織,還有是利用 id 作為索引的 dict,新舊的元信息會落到一個桶內,每次取offset大的那個)

Photo Delete

刪除文件很直觀,就是同步的以此設置下內存和 store file 中對應圖片元信息的 flag。如果請求到某個被刪的 Photo,在內存中發現其 flag 被設置了,就報一個異常。暫時的,被置為已刪除的圖片所佔的空間會暫時不可用;在定期的緊縮(compact)時,會回收這部分空間。

The Index File

然後說回 index file。既然是定期做 snapshot 的到的 index file ,那麼就會出現不一致的問題。包括,新增文件寫入 volume 和內存後沒有及時寫到 index file 就宕機;設置某圖片 Store file 和內存刪除 flag 時候沒有及時同步到 index file 就宕機。應對這兩個問題也簡單,對於前者在重啟時,讀index file 時候,可以對比對應 offset 的 volume id,看是否是最新的,否則將最新的補到 index 和內存中就行;對於後者,每次讀取圖片時,除了內存中做檢查,額外檢查下 Store file 中圖片的刪除 flag 是否被設置了就行,並將其同步到內存和 index file 中。

Filesystem

此外,為了見少不必要的讀盤,就沒有採用傳統的 POSIX 文件系統,而是用了 XFS。

錯誤恢復

跑在大規模廉價硬體上的系統總避免不了出現一些錯誤,比如說硬體驅動錯誤,RAID 控制器問題,主板故障等等。我們的應對方法也簡單,做了兩件小事,一個是定期檢測,一個適時恢復。

我們用一個叫 Pitchfork 的後臺任務,定期檢測每臺存儲節點(Store machine)的健康狀況。比如查看與每個節點的連通情況,每個 volume 的可用性,並嘗試從物理節點讀一些數據以進行測試。一旦健康檢查失敗,Pitchfork 程序就回標記該物理機上的所有 volume id 為只讀。

稍後一旦確診,就會立即修復浙西問題。偶爾修復不了的,就只能先重置該節點數據,然後從備份節點利用一個比較重的 bulk sync 的操作去同步數據過來。

優化

一些常用的優化有,定期緊縮(Compaction):這是個在線操作,旨在收回被刪除文件和重複(key 和 alternate key都一樣)文件所佔的空間;具體做法是新生成一個文件,逐個拷貝有效的文件,跳過被刪除和重複的文件。一旦完成,就暫時組織所有到該 volume 的修改請求,然後交換 Store file 和內存映射。

精簡內存,由於現在用 flag 只做是否刪除的標誌,太浪費了。可以改成將內存中所有刪除了的文件對應的元信息的 offset 設置為0。並且不再內存中保存圖片的 cookie,改為從硬碟讀取,如此一來,省了百分之二十左右的內存。

批量上傳,硬碟在進行大批量寫的時相對隨機寫平均性能會好一些,因此我們儘可能的追求批量上傳。幸運的是,用戶更傾向於同時上傳一批圖片而非一張圖片。

下面還有一些進行性能評估的段落,就先不翻了。


推薦閱讀:
相關文章