在剛剛過去的618大促中,京東視頻拋棄了私有存儲,將京東智聯雲對象存儲做爲京東視頻的惟一存儲。在整個618過程當中,京東智聯雲對象存儲提供了穩定的服務,助力618完美落幕。數據庫
618大促做爲京東集團最重要的活動,對全部服務的可用性有極高的要求,京東視頻做爲京東的一級系統,對存儲的故障更是零容忍,那麼如何保障系統的高可用呢?下面咱們就一塊兒來探討下京東智聯雲對象存儲在高可用架構設計上的一些思考。後端
做爲一個有狀態的服務,影響服務可用性的因素有不少,通常來講會有如下幾大類:緩存
對象存儲是一個複雜的系統,在設計和實現的過程當中,咱們遵循瞭如下原則,來保證對象存儲的高可用:服務器
下面咱們以對象存儲的架構爲例,詳細探討下在對象存儲中,咱們是如何實踐以上原則的。網絡
總體來講,對象存儲包括業務層(綠色部分)、數據存儲(黃色部分)、元數據存儲(藍色部分)三個部分組成,下面對這三個部分分別作更詳細的介紹:架構
對象存儲業務層主要作了一些認證鑑權限流等業務操做,從數據流的角度來看,他主要作了數據流的拆分和轉發的工做,下圖描述了一個基本的上傳流程:併發
從上圖中,咱們能夠看到,對象存儲業務層上傳的流程以下:運維
- 接收流式數據,拆分紅一個個的Slice;
- 把每一個Slice寫入到後端數據存儲,而且記錄下數據存儲返回的SliceId(clusterId, rgId, blobId);
- 把Key和全部SliceId存儲在元數據存儲中;
- 返回給客戶成功。
從上面的描述能夠看出,對象存儲業務自己是一個無狀態的服務,能夠簡單的經過多個節點來實現高可用,在現實中咱們也是這麼作的。分佈式
從上面的數據上傳流程中能夠看到,數據存儲是一個Blob的系統,它的基本接口是用戶寫入一份數據,數據存儲返回一個Id,這意味着能夠實現如下兩點:性能
- 寫入到數據存儲中的數據只會被讀取和刪除,永遠不會被修改,也意味着任意時間只要從任何一個副本讀到某個SliceId的數據,該數據必定是最新的數據;
- 任何一個Slice能夠寫入到任意一個集羣的任意一個複製組,保證寫入永遠高可用;
首先,咱們來看一下對象存儲數據存儲系統多集羣部署多邏輯結構圖:
從上面的邏輯部署圖能夠看到,數據存儲系統由兩類存儲系統組成,下面分別介紹一下:
上圖展現了一個Region標準的部署圖,通常來講,一個Region由三個AZ組成,業務層會跨越三個AZ部署,存儲集羣、WriteCache和DataStore都會部署三個集羣,作藍綠部署,其中DataStore跨越三個AZ,而WriteCache每一個AZ部署一套。
對象存儲是一個Blob系統,數據寫入到後端任意一個存儲集羣均可以,Sched負責調度一次寫入寫入到具體哪一個集羣。
流量調度會綜合集羣的容量/壓力等信息,把請求調度到合適的集羣,確保各個集羣能最大化地被利用。
在對象存儲數據存儲中,每一個區域會部署三套存儲集羣/緩存集羣,這些集羣作藍綠部署,任何更新或者運維操做都只會在一個集羣上進行,確保了任何的Bug或者人工誤操做不會影響到對象存儲的寫入。
在部署上,對象存儲數據存儲除了部署跨越多個可用區的DataStore,還在每一個AZ中部署了AZ內的WriteCache,確保了在跨AZ網絡中斷某個AZ造成了孤島也不影響數據的寫入。
在數據存儲中,底層的數據存儲有WriteCache,標準存儲-DataStore,EC存儲等多個系統,它們的基本架構都是同樣的,都是基於ReplicateGroup/Raft/Log的系統,寫入成功都只依賴於複製組中大部分節點響應成功。
下面咱們來看看底層存儲集羣的架構:
和一個常見的分佈式存儲系統相似,對象存儲底層存儲集羣也是由Client,DataNode,Master三個部分組成,下面分別對三個部分作一個介紹:
底層數據存儲高可用的核心思路以下:
- 全部的數據/服務都是多副本的,而且跨越多個AZ,保證單磁盤/機器/交換機/機房故障都不會影響用戶;
- 主幹流程上不存在中心節點,常見的系統設計中,Master可能會是一箇中心節點,少數幾個Master故障可能致使整個集羣不可用,在DataStore的設計中,咱們把Master分紅了多個系統,和主幹流程相關的數據路由信息被拆分到Allotter中,而且多節點部署,Master自己不影響核心流程;
- 數據讀取不依賴於複製組的存活,只要數據存在且能路由到就能訪問;
- 數據不強制要求三副本,可是保證絕大部分數據都是三副本。
下面咱們具體從讀寫的角度看看是如何實現高可用:
寫入流程以下:
- Client挑選一個可用的Allotter,從中分配一個可用RG;
- Client訪問Cache(有本地緩存),獲取到該RG對應的Leader的地址;
- Client向Leader發送寫入請求;
- Leader經過Raft把數據複製到全部副本,提交後響應客戶端;
- 若是中間有任何失敗,Client會重試1-5步驟。
讀取的流程和寫入基本相似,就再也不重複說明。
- 數據寫入路由依賴於Allotter和Cache,Allotter和Cache都是多節點有任意一個可用的節點就能提供服務,另外Client自己也能承擔絕大部分Allotter/Cache的功能,保證寫入路由高可用;
- 數據最終能夠存儲在任意一個RG中,一個集羣中會有數十萬到百萬級別的RG,有任何一個可用的RG就能夠成功地寫入;
- 一個集羣會包括數百臺存儲服務器,RG會隨機地分散在這些服務器上,保證了只要有部分存儲服務器可用,就必定會有可用的RG。
- 數據的路由會緩存在多個節點的Cache中以及Client內部,保證大量節點故障的時候仍然能找到數據的位置;
- 數據自己設計爲不會被修改,所以數據的讀取只依賴於數據所在節點進程的存儲而不依賴於數據所在複製組的存活,只要能找到數據的位置(上一步描述了其高可用),就能讀取到數據;
- 寫入自己不是三副本強一致,可是Allotter在作寫入流量調度時會優先選擇主從複製delay少的複製組,保證了絕大部分數據實際上都是有三副本的;
- 進程快速啓動,啓動後能在數秒開始提供讀服務;
- 每一個複製組數據較少,磁盤故障理論上在20分鐘之內能完成修復。
綜上所述,數據不可訪問的機率基本和數據丟失的機率同樣低。
對象存儲元數據管理系統核心是一個全局有序的KV系統,和數據存儲相比,它會有如下幾點不一樣:
- 數據會被覆蓋,用戶能夠對一個Object作覆蓋上傳,該操做會修改元數據存儲中該Key對應的Value;
- 數據量小,一般元數據存儲大概只有數據千分之一如下。
對於元數據的的高可用,咱們也採起了跨AZ多副本存儲、多集羣等機制,可是這些機制和數據存儲又不徹底一致,接下來咱們來詳細看看元數據的高可用方案:
上圖所示是對象存儲元數據存儲系統的核心架構,元數據存儲系統底層使用了Tikv做爲最終的存儲系統,和數據存儲同樣,元數據存儲在每一個區域一樣會部署三套跨越三個AZ的Tikv,多個Tikv集羣之間作藍綠部署,下降人爲操做/升級引入Bug等因素對整個元數據存儲系統等影響。
元數據管理系統採用了相似LSM Tree的架構,在多個集羣上經過各類不一樣角色的組件構建了統一的元數據管理服務,各個組件的關係見下表描述:
組件
描述
Writeable KV
當前正在讀寫的集羣,和LSM相似,刪除在Writeable KV寫入刪標記
ReadOnly KV
只讀集羣,寫入流量從Writeable切換到Backup以後,原來的Writeable就變成只讀
Stable KV
每次寫入流量切換會產生一個ReadOnly的KV,ReadOnly的KV過多會影響到讀取和Scan的性能,在ReadOnly的KV到達必定數量後就會合並進Stable KV
Backup KV
Writeable集羣的寫備份,在Writeable KV對應的物理集羣變成不可寫入的時候,寫入會切換到Backup,保證寫入高可用
Standby KV
Writeable集羣的讀備份,Standby集羣會經過相似Binlog的方式重作全部更新操做,保證Standby KV和Writeable KV的數據基本一致(有短暫延時),在Writeable集羣不可讀的時候會之間讀Standby集羣,Standby集羣的數據會隨着集羣切換隻讀而丟棄
和數據高可用的思路相似,元數據高可用也是由兩個部分組成:
- 存儲集羣作高可用;
- 多集羣容災,避免單集羣故障對可用性的影響。
下面咱們分別從讀寫兩個方面來看看具體是怎麼作的:
寫入高可用由Tikv集羣內部的高可用和多集羣兩個方面共同組成:
- Tikv自己跨越3個AZ作三副本存儲,單個故障域(磁盤/機器/交換機/機房) 故障不會影響集羣可用性;
- Writeable所在Tikv集羣故障,寫入能夠快速切換到Backup,且Backup和Writeable所在物理Tikv集羣不會是一個集羣,確保寫入不會中斷。
- 對於已經只讀的集羣(Readonly, Stable),數據已經不會發生變化,讀任意一個副本便可,只要數據存在就能夠讀到正確的數據,數據的可用性也能夠簡單的經過增長副原本提高;
- 對於讀寫集羣中的數據,數據會準實時的經過Binlog來同步到Standby:
a) 數據自己是三副本,保證高可用;
b) 集羣不可用的時候,能夠直接讀取Standby集羣數據,作集羣級別的容災。
通常來講,三個AZ高可用的存儲系統(例如對象存儲數據,元數據系統),因爲複製組能夠跨越三個AZ,能夠簡單的容忍單個AZ的完全故障。可是若是出現了AZ之間的網絡故障,致使某個AZ和其它AZ失聯,該AZ內部還能正常服務,這個時候就會造成一個孤島。
對於對象存儲來講,對象存儲上層的不少業務,好比說數據庫/主機等都是在一個AZ內部,也就是說若是某個AZ和其它AZ失聯,該AZ內部仍是會源源不斷的產生數據,咱們須要保證孤島內部生成的數據能成功的寫入對象存儲,下面咱們主要介紹對象存儲在這種狀況下的處理。
數據孤島本質上須要解決兩個問題:
- 孤島造成的時候,須要把數據寫入到孤島內部;
- 孤島結束後,須要把孤島內外的數據作合併。
對象存儲包括數據存儲和元數據存儲兩個有狀態的服務,下面分別介紹一下咱們如何利用多集羣來解決數據孤島的問題:
在數據存儲高可用的時候咱們提到過,在每一個AZ中,咱們會部署一套可靠的寫緩存服務,寫緩存的數據最終會Writeback到跨AZ的存儲集羣。
因爲數據存儲能夠選擇任何一個集羣寫入,某個AZ造成數據孤島後,該孤島內部產生的數據都會寫入到本AZ的寫緩存,保證了數據寫入的高可用。這部分數據最終會在AZ之間從新鏈接後寫入到跨AZ的集羣。
因爲對象存儲數據存儲系統是一個不可修改的系統,這意味着孤島內外不可能會對同一個ID作操做,孤島結束後數據合併會變的很簡單。
對象存儲自己容許覆蓋上傳,這意味着元數據是一個容許修改的系統,這也讓元數據存儲的數據孤島解決方案變的相對比較複雜。
和數據存儲孤島不一樣,元數據造成孤島後,若是孤島內外對同一個Object作修改,在一段時間內必然會致使孤島內外看到的數據不一致,這些不一致最終會達成一致,也就是說元數據存儲是一個最終一致的系統。
在對象存儲中,咱們經過多集羣 + 多版本來解決數據孤島問題。
- 對象存儲支持修改,可是對象存儲實際使用中修改相對較少;
- 一個Key在短期內(1s)被併發修改的機率很低,咱們認爲在極端異常狀況下毫秒級別的併發修改最終亂序是能夠接受的;
- 極端狀況下可能讀到老的數據,甚至交替讀到新老數據,可是最終會讀到肯定的一份數據。
上圖是一個多版本的元數據管理系統的架構,和最第一版本相比,多了一個IDService的服務,IDService自己是一個高可用的Id生成器,它會根據當前機器時間生成單調遞增的ID,一個大體的Key的結構爲timestampMS_自增ID_IDService集羣ID。IDService會多節點部署,保證高可用。
基於IDService,咱們實現了多版本的元數據管理系統,任何一次對元數據的修改都會經過IDService取得惟一的ID來做爲Version,而且在Tikv中存儲下全部Version的元數據。舉個例子,兩次的PutObjectMeta(Key, meta) + 一次Delete最終會在Tikv中存儲下如下三條記錄:
因爲IDService生成的是有序的,該Meta的全部版本中版本最大的記錄就是Meta的最新記錄,好比上面的例子,最新記錄是刪除記錄,該Object已經被刪除。
下圖是一個孤島故障切換的例子,在該例子中,AZ1造成了數據孤島,AZ2和AZ3能正常互聯。
在上圖能夠看到,咱們會在每一個AZ部署獨立的AZ內部獨立的Tikv和IDService, 在AZ1造成數據孤島後,整個Meta的服務會分紅兩個獨立的部分:
- 全局的Meta服務(綠色部分),會使用跨AZ的Tikv和IDService,此時服務於AZ2和AZ3;
- AZ1內部Meta(藍色部分),會寫入到AZ1內部的Tikv。
在Meta分裂成兩個集羣后,全部的寫入均可以成功。
在AZ1恢復和AZ2,3互聯後,全部的寫入會切換到多AZ多集羣,AZ1內部Tikv集羣的數據須要合併到跨AZ集羣。
因爲Meta實現了多版本,多個IDService之間也保證能生成惟一的ID,也就是說Key_Version是惟一的,所以AZ1內部Tikv的數據能夠直接合併到跨AZ集羣便可。
基於如下緣由,合併是有效的:
- IDService根據機器時間生成自增ID。
- 多個IDService機器的時間偏移保證可控。
- 極端異常狀況下能夠接受段時間內亂序。
基於以上三點,能夠保證直接合並的結果符合咱們預期。
點擊"閱讀原文",瞭解更多京東大規模分佈式對象存儲服務