聊一聊分佈式對象存儲

1. 前言

今天來聊聊我正在讀的一本分佈式對象存儲的書籍。

前天11月10號,想着京東有滿200-100的活動,就買了一些書,準備沉澱一下。自己打算在分佈式系統上搞幾年,所以買的書基本上都是關於分佈式存儲的。本身也沒想着買一些分佈式系統的經典教材,就隨便選了幾本京東上銷量比較高的,偏實用一些的書籍。權當心血來潮,未經過任何調研。

當前看的這本是《分佈式對象存儲-原理,架構和go語言實現》。

2. 總體感覺

書今天上午開始看,還未看完,先說一下總體感覺,從半價角度上來說,這本書物超所值,如果是給有一定編程能力且打算入行分佈式系統的同學看,那麼就更值得了。對於想自己動手實現一個分佈式系統的同學,按照書中的代碼,一點點的敲,完整實現一個分佈式的對象存儲不成問題,應該會有不少的成就感。然而若是給架構師或者研究分佈式的同學看,不是很建議,內容顯得過寬泛且不夠深入(主觀感覺)。書中雖然知識點衆多,但各個知識點有效篇幅內容明顯不足,而且更大的煩擾是各種實現層代碼散佈全書導致關於分佈式的核心內容就更少了(不過對於一個想自己實現一個系統的同學來說,更多的代碼反而更好,這就顯得仁者見仁智者見智了)。保守估計關於代碼以及代碼的函數流程的闡述佔據了全書的1/2,甚至更多。

  • 優點
    完整的講述了一個分佈式對象存儲從0到1的過程,不斷改造和擴展新功能,循序漸進的方式來講解,是其優勢之處。書中涉及到知識點很多,有分佈式系統的基本涉及,元數據管理,去重,糾刪碼,壓縮等,也就是說的常見的分佈式系統的feature方面都有,能夠給一個不在這個領域的人一個宏觀的感知。例如,本書能夠回答的是目前的分佈式系統的架構是如何,有什麼主要特性。
  • 缺點
    大量的代碼讓我遇到代碼的時候都大量的跳躍,一上午的時間,讀了三章(其實很多代碼都沒有細看,主要看了架構設計以及思路設計),收穫偏小。但是因爲自己還沒有讀完,所以也沒有太大的發言權,只是自己一廂情願的看法而已了。

3. 內容總結

看書不總結書中內容,對於記憶力很差的我,等於白看,因此現在把我看到的內容寫下來,權當一記錄,也希望能夠給其他想買這本書的同學一些參考。

3.1. 對象存儲簡介

傳統的存儲系統有NAS和SAN,它們本質上是兩個點,NAS是相當於有一個文件服務器,可進行文件上傳和下載等操作,而SAN更強調storage的概念,它將多個存儲設備組成一個整體,對外只提供storage的功能,而沒有文件等上層抽象概念,可以把它想象成一個大的網絡硬盤。這些內容其實與對象存儲並不矛盾。對象存儲按照其名字來說,核心就是將對象進行存儲。對象有唯一標識,可認爲是key,然後對象的數據,可認爲是value。

如果實現一個單機版本的對象存儲呢?
基於Go語言,使用REST請求(GET用於下載,PUT用於上傳)

  1. 啓動一個Listen Service,並且綁定handler(處理函數)
  2. 在處理函數判斷是GET還是PUT請求然後進入不同的處理
  3. 如果是GET請求,直接根據路徑,拿到對象的內容(其實就是一個本地的file文件),返回給客戶端
  4. 如果是PUT請求,根據路徑,將post的body寫入到指定的文件中
  5. 客戶端模擬使用curl,可用於演示put和get請求

這裏不得不說go實現真的好簡潔,寥寥幾十行,就實現了基本的HTTP服務,並能讀寫文件數據返回到客戶端。

書中很多地方說了REST,原來並不知道這種稱呼,簡單看了一下網上的介紹也不甚了了,基本感覺就是提供了數據訪問的接口的標準定義,都按照這樣的標準請求和返回,方便解析。有點像HTTP的規範定義版本?

3.2. 分佈式系統

對於3.1的單機對象存儲,最先要做的就是實現分佈式,也就是可擴展性。要想做到這一點,一個重要的概念就是請求和數據分離。其實就是有專門的接口服務負責接收請求並轉發(感覺好像代理服務器,但是不同的是它需要維護數據節點的狀態信息),有專門的數據服務負責數據的存儲。這樣的話,如果容量不足需要擴容數據存儲,直接拉幾臺機器加入到數據集羣中即可。

CSDN上的PDF版本需要5分,奈何沒那麼多積分,只能手機拍照截圖了。

如上圖所示,爲了實現兩個服務的分離,接口存儲和數據存儲需要加入中間層,以隔離兩個服務。 本書使用RabbitMQ(看着像消息隊列Kafka),負責上下層之間的消息轉發。當客戶端的寫入操作到達接口層,需要找到相應的數據節點,完成寫入;當客戶端的讀取操作到達接口層,也需要找到存儲的節點,發起讀取;另外接口層需要知道哪些數據節點是alive的,這樣以便於寫入的時候避開掛掉的數據節點。

欲完成以上功能,需要如下:

  • 心跳
    數據節點每間隔一斷時間(5s)發起心跳請求消息,扔到apiServer exchange中(感覺像kafka的topic)。接口服務作爲消費者,消費apiServer中的消息,收到某個節點心跳就記錄當前節點。注意,這個心跳消息會廣播到所有的接口服務(說是廣播,其實從本質上是所有的接口服務都獨立訂閱了apiServer,都能收到apiServer中的全部消息)。這樣每個接口服務都能看到所有的數據節點,接口服務節點之間沒有任何差異,這也就實現了接口服務的平行擴容。
  1. 客戶端發GET到接口服務
  2. 接口服務生產消息到dataServer
  3. dataServer中的消息被所有的數據節點消費,只有具有指定對象的data節點纔會返回,並返回其監聽地址
  4. 接口服務收到data節點的監聽地址後,向data節點發起數據讀取請求
  5. 接口服務節點將讀取到的數據返回給客戶端

  • 與讀的過程基本一致,不同的是寫入的節點是隨機選取的其中一個,然後是將數據傳遞到數據節點,交給數據節點寫入。

總體來說,讀和寫的過程可以歸結爲兩個步驟:

  1. 定位數據節點(對於讀,只有存儲了請求對象的節點返回;對於寫,隨機選擇)
  2. 發起數據讀寫(接口節點和數據節點傳遞對象的內容,或者寫入,或者讀出)

這裏面有一些問題:

  1. 所有的數據讀寫請求,都完整了經過了接口服務和數據服務,似乎有點浪費,是否能夠做到將數據服務的節點返回給客戶端,由客戶端發起讀取操作呢?
  2. Put同一個對象,可能到達不同的數據節點,從而存儲多份數據
  3. 不可容錯,當前的數據節點丟了一臺,數據就丟了
  4. 是否需要一致性Hash?目前的對象的節點定位要求所有數據節點都參與,也就是說接口節點不知道對象的位置,從而不得不向所有的數據節點發起詢問,如果採用hash求模的方案,就可直接根據hash模值定位到數據節點。
  5. 數據版本號。如果能夠使得一份數據有多份版本?

上面的很多問題,需要很多內容才能完善。例如版本號是一種數據一致性的方案(MVCC),那也有很多其他的方案,不過文中並無涉及;關於數據hash,有不少保證一致性hash的方案,也無涉及。

3.3. 元數據服務

說實話,在看到元數據服務的時候,以爲要自己實現一套元數據的存儲方案,仔細一讀發現使用的是ES(elasticSearch)。對於ES,同樣不熟悉,等後面看是否專門寫個博客介紹一下。簡單來說,ES負責存儲對象的元數據信息,接口服務利用ES返回的元數據從而數據節點中取得對象的Value。

爲什麼會有這個ES? 對象要支持多版本訪問(既有最新版本,也有歷史版本),使得對象要有版本號的概念,另外數據本身可能還有長度屬性,類別屬性(是圖片還是文本等)等等的內容,這些內容不可能存儲在接口服務器中的。因爲如果存在接口節點中,接口服務就無法做到數據一致性了,例如通過接口節點A增加了一個新的對象,接口節點B就無法感知到,客戶端向B請求,會報告對象不存在。從本質來說,正是由於接口服務節點的無狀態特性(只負責轉發請求,不過本文中還維護的data節點的alive),才賦予了接口服務節點的平行擴容能力。ES充當了中心化的有狀態服務,給接口節點提供元數據查詢服務,所有的更新操作都會經過ES,從而確保數據的一致性。

那如何設置一個對象的元數據metadata,簡單起見,使用四種屬性:name,version,hash,size
唯一標識:

  • name和version唯一標識一個對象。客戶端可使用name+version向ES發起請求,ES返回其metadata;
  • hash唯一標識一個對象。數據存儲以hash作爲唯一標識,請求數據服務時候,需要傳入hash值。

這種上層name+version,下層hash的操作有一定的好處。從上層客戶端使用來說,name纔是最容易記憶和關心的,對外公開使用name更易記憶和理解;從底層存儲服務來說,只需要唯一標識?那爲何不使用name+version呢?這主要因爲hash值提供區分文件是否unique的屬性。假設文件A和文件B的內容完全一樣,如果使用name+version的方式數據需要存儲兩份,而使用hash值,只需要存儲一份(這其實是屬於Dedup研究的範疇Dedup簡介, Dedup一種應用),只要在元數據映射中記錄A -> Hash Identifier, B -> Hash Identifier即可。

以GET操作爲例

  1. curl向接口服務發起Get object請求,傳入name;
  2. 接口節點向ES發起name的元數據;
  3. ES返回name的元數據,未指定版本則返回最新版本(name, version, hash, size)的元數據;
  4. 接口節點使用hash值向data節點發起定位請求(數據存儲在哪個data節點上);
  5. 存儲了該object的data節點返回自己監聽的地址;
  6. 接口節點利用返回的data地址,向其發起對象讀取請求;
  7. data節點返回對象的value信息;
  8. 接口節點將value轉發給curl客戶端。

額外的問題:

  1. 上傳數據的時候需要指定hash值,這個hash值服務器並無校驗
  2. 數據傳輸通路完整的經過接口服務節點
  3. 元數據需要保證數據一致性,例如多個寫請求同時到達,進行版本增加以及數據存儲要保證原子操作。

3.4. 校驗去重

3.5. 冗餘恢復

3.6. 斷點續傳

3.7. 數據壓縮

3.8. 數據維護

4. 書籍的額外建議

  1. 每一章前面一個作者的ppt的照片,感覺不是很好,技術書籍很少見,給人以湊頁之嫌。

參考資料:

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