內部專家親自揭秘!滴滴物件儲存系統的演進之路

儲存頻道發表於2018-11-20

   本文根據汪黎老師於第十屆中國系統架構師大會(SACC 2018)的現場演講《滴滴物件儲存系統的架構演進實踐》內容整理而成。

   講師介紹:

  汪黎,博士,中國計算機學會儲存專委委員,曾獲中國計算機學會優秀博士論文、首屆中國開源軟體競賽銀獎等獎勵。主要研發領域為檔案系統、作業系統、分散式儲存,為知名開源專案LVS、Ceph成員。曾任國防科技大學計算機學院副研究員,教研室主任,天河雲端儲存研發負責人;現擔任滴滴出行高階技術專家,滴滴雲端儲存研發負責人。

   正文:

  大家好,我分享的題目是《滴滴物件儲存系統的架構演進實踐》。

  首先,什麼是物件儲存系統?當前物件儲存系統這個概念稍微有一點混淆。一般有兩種定義方式:一種是透過儲存系統本身的技術實現路線來將其定義為物件儲存系統。比如經典的、在高效能運算常用的Lustre檔案系統,它的後端就是一個典型的物件儲存設計思想。現在非常流行的Ceph,它的後端RADOS也是物件儲存的架構。另外一個定義方式,是從儲存系統的應用場景來定義的——即某個儲存系統適用於物件儲存這個場景。而今天我想交流的,就是基於第二種定義方式的物件儲存系統。

  那麼,物件儲存的應用場景有什麼特點?它一般儲存的都是靜態的、非結構化的資料,比較典型的有圖片、音影片、網站的靜態資源,還包括一些比如虛擬機器的映象,快照,以及備份資料等等。這樣的資料,普遍具有一次寫、不修改、多次讀、較少刪除的訪問特點。另外,資料對外訪問介面一般是透過HTTP的介面來訪問,對延時的要求並不太高。但物件儲存系統對海量資料儲存要求是很高的,另外對資料可靠性的要求也很高。

   滴滴物件儲存系統GIFT V1.0

  滴滴的物件儲存系統,內部稱為GIFT,是為了解決公司小檔案儲存問題而開發的物件儲存系統,比如圖片、網頁靜態資源等。 訪問的方式為,其中ns即namespace是全域性唯一的,ns和key均為使用者指定,常見的介面就是上傳、刪除、下載。一開始這個系統的架構比較簡單,主要分兩部分,第一部分是API層,用Go實現的接入服務,為使用者提供檔案上傳、下載、查詢等功能。後端的儲存服務,用於儲存檔案資料以及後設資料。檔案資料使用SeaweedFS儲存,它是開源的,參考了Facebook的Haystack,不過Haystack本身沒有開源。另外在我們一開始設計時,資料和後設資料是分離儲存的。因為不管是SeaweedFS還是Haystack,要支援大量後設資料的儲存和複雜的查詢,其實是相對困難的。所以後設資料單獨使用Mysql叢集進行儲存。

  這裡簡單介紹一下Facebook Haystack,這是一個比較經典的物件儲存系統。Haystack是Facebook用於儲存圖片等小檔案的物件儲存系統,其核心思想是採用多對一的對映方式,把多個小檔案採用追加寫的方式聚合到一個POSIX檔案系統上的大檔案中,從而大大減少儲存系統的後設資料,提高檔案的訪問效率。當要存大量的小檔案時,如果用傳統的檔案系統,一對一地去存小檔案,儲存空間利用率很低、查詢的開銷非常高。

  另外介紹一下SeaweedFS的架構。它包含兩個元件,一個是資料儲存叢集,即Volume Server Cluster,每個volume server對應一個磁碟,每個磁碟有一系列的volume檔案,每個volume檔案預設大小為30GB,用於儲存小物件。每個volume檔案對應一個index檔案,用於記錄volume中儲存的小物件的偏移和長度,在volume server 啟動時快取在記憶體中。

  這也是Haystack的一個重要設計思想,它做了一個兩級的後設資料對映,第一級把它對映到一個大檔案,這樣檔案系統本身的後設資料開銷是很小的。第二次對映的時候,只需要在大檔案內部去找它的偏移和長度。而且做完兩級對映之後,可以使大檔案裡對應的小檔案的後設資料全部快取在記憶體裡面,這樣就可以大大提高它的查詢的效率。

  另外一個元件稱為Master Server Cluster,執行的是raft協議,維護叢集的一致性狀態,一般與volume server混部。volume server會向它上報自己的狀態,然後它能夠維護整個volume server cluster的拓撲。同時它還負責物件寫入時volume server的分配,以及負責資料讀取時volume id到volume server地址對映。

  下面我們展開來講SeaweedFS。首先上傳過程,第一步是客戶端先與master server cluster的leader通訊,leader進行volume分配,給客戶端返回一個fskey及volume server地址,fskey包含volume id,needle id及cookie三部分。Volume id跟volume server對應,needle id用於在一個volume內做索引,cookie用於安全考慮,大家有興趣進一步瞭解的話,可以參看Haystack的論文。

  第二步,客戶端將檔案及fskey傳送給對應的volume server,該volume server為儲存主副本的server,稱為primary,primary將檔案追加到對應的volume檔案尾部,並將offset,length,needle id記錄到對應的index檔案。

  Primary將檔案寫入後,將檔案傳送給兩個儲存副本的volume server,稱為slave,待slave返回寫入成功後,給客戶端返回寫入成功。

  (如下圖)

  刪除操作體現了Haystack的思想——它的刪除是非同步刪除的。客戶端用物件對應的fskey與master server中的leader通訊,得到fskey中volume id對應的volume server地址。這實際上是做了一層間接,因為volume server有可能會掛掉,所以不能直接把volume server的地址給client。同時也便於master做一些負載均衡。每次讀的時候也會首先跟leader通訊,然後拿到volume server地址之後,再去訪問主副本。然後主副本就會把這個物件做一個標記刪除,它不會真正去刪那個檔案,不會真正從大檔案裡把那部分挖空。只是標記一下,不會做資料刪除,Volume server會透過一個非同步的compaction操作來回收資料。

  非同步compaction是什麼?就是說這個大檔案,每刪一個檔案就會留下一個空洞,如果Volume裡面已經有很多的空洞了,現在決定要重新用這個資源,就會做一次compaction,就是把Volume裡面剩餘的、還沒有刪除的檔案全部拷出來,重新把它拼成一個連續的檔案,然後把其他的空洞對應的地方釋放掉。

  下載其實跟刪除類似,首先客戶端用物件對應的fskey與master server中的leader通訊,得到fskey中volume id對應的volume server地址,然後訪問volume server,volume server根據volume id,needle id訪問對應volume檔案的index檔案,得到物件在volume檔案內的偏移和長度。Volume server再訪問對應volume檔案,給client返回物件資料。

   伴隨業務增長下的第一次演進——GIFT V2.0

  以上介紹就是GIFT最早的版本(V1)。隨著本身資料檔案量和業務量的增加,問題也越來越多。其中的一個表現是什麼?隨著業務量不斷增加(TB -> PB),架構無法支援PB級儲存。因為資料儲存SeaweedFS是有中心設計模式,叢集規模增大存在併發訪問效能瓶頸問題。讀寫請求必須先經過master server,再訪問volume server獲取資料。master server執行raft協議,只有一個leader在工作,其它為follower。只有leader處理讀寫請求,存在單點效能瓶頸。在高併發的情況下,訪問延遲明顯的增加。然後就是,我們最早的設計,後設資料的儲存為單庫設計,所以是所有使用者叢集共享的,造成QPS受限。

  所以我們就對它做了一個架構的演進。主要的思想,首先在資料儲存方面,我們從業務邏輯角度支援接入服務管理多個SeaweedFS叢集,就是多個儲存子叢集。當一個SeaweedFS叢集的容量達到閾值時,將其標記成只讀,新資料寫入其它叢集。而且我們支援SeaweedFS叢集的動態加入、動態註冊。叢集擴容過程為,首先一個新的SeaweedFS叢集會向接入服務註冊,然後接入服務將其加入可寫叢集列表,接入服務對上傳檔案的bucket, object資訊做hash,選取一個可寫叢集,進行寫入操作。這是從資料的角度解決QPS和儲存容量的需求。

  從後設資料的角度,我們支援了支援多庫分表模式,可對不同使用者叢集採用不同庫,解決海量檔案併發訪問問題。下面具體介紹一下。從後設資料的角度,我們做了一個分庫和分表。首先我們可能有不同的Region,而不同的Region下面可能還有不同的使用者叢集(下圖1)。我們除了有一個全域性唯一的配置庫以外,從物件的後設資料庫的角度,做了一個分庫和分表的設計。(下圖2)

(圖1)

(圖2)

  具體到上傳過程,使用者指定了它在一個Bucket的下面一個object。首先會根據Bucket的資訊,去查全域性唯一的一張表,從這裡查到Bucket對應的Region在什麼地方,然後得到Region之後,才會同時去查Region作為關鍵字所對應的儲存叢集有哪些。這也是一張全域性唯一的表,Region下面因為會有一系列的支援儲存叢集,就是SeaweedFS叢集。進一步就會去做一個排程,然後決定去寫入哪一個儲存叢集。

  寫完之後,SeaweedFS會返回一個fskey,我們需要存下來,fskey以及物件本身的一系列後設資料存在什麼地方。從後設資料儲存的角度來講,每一個Region下面針對不同的使用者叢集有一個分庫。查這張全域性的表,我們可以得到Region下面真正存這些後設資料的庫是哪個,然後再去訪問那個庫。在那個庫下面我們又進一步做了一個分表的設計,就是說這個庫下面的所有這些物件的後設資料,是有一系列的分表的,而分表的依據就是把Bucket和object聯合起來,再做一次CRC,再模上整個分表的總數,就得到了它要存到哪個分表裡面,然後就把它對應的這一系列的後設資料存進去。這樣就從後設資料的角度去解決了整個叢集的海量PB級可擴充套件的一個設計。(如下圖)

  下載就是反過來,先拿到Bucket查這個表,得到它所在的Region,然後就得到它對應的後設資料的資料庫,用剛才說的CRC得到資料庫下面都有是哪張分表。然後就可以從分表裡得到它對應的後設資料,後設資料裡面是記載了它所儲存的資料儲存叢集的資訊。拿到資料儲存叢集的資訊以及fskey後再去訪問對應的SeaweedFS叢集,就可以得到物件的資料。整個就是這樣的一個過程。(如下圖)

  以上就是GIFT2.0版本的設計,它的可擴充套件性已經很不錯了,已經能夠支撐到一個PB級的儲存。

   曇花一現的GIFT V3.0與更好的V3.5

  隨著我們業務發展,有了新的需求,一個是之前我們這套系統主要提供對內服務,而我們現在還想支撐一個對外服務,這時我們可能就需要加入一些像計費、認證的邏輯,同時還需要去支援大檔案的儲存,就像我們剛才提到SeaweedFS其實更多是存小檔案,所以我們又做了一個3.0版的一個設計。

  主要增加了一些功能元件:接入服務提供認證和資料操作介面;儲存服務,小檔案儲存在SeaweedFS,大檔案儲存在HDFS;Report Service(上報服務)用來定時推送使用者統計量資訊;Cleanup Service(清除服務)用來刪除過期資料;RDS主要用來儲存object和fskey對應關係以及配置表資訊。架構圖如下:

  其實3.0這個版本存在的時間不是那麼長,因為還覺得它存在一些問題:對於SeaweedFS,運維複雜,不支援故障自恢復、需手動恢復資料,且恢復以volume為粒度;不支援資料rebalance;Master server維護volume id與volume server的對映關係,為單節點,所有請求經過master server;不支援糾刪碼。

  HDFS存在NN問題,它本身其實也是一個有中心化設計。而在GIFT應用場景下,後設資料已由RDS儲存處理,我們希望儲存服務本身儘量保證可擴充套件性、高可靠性,不希望在這裡還有一些後設資料限制它的可擴充套件性,所以儲存服務應儘量採用無中心化的模式。然後HDFS本身也不太適合小檔案。雖然它本身的出錯恢復有一些考慮,但我們覺得還是不夠強,隨著叢集規模越大,出錯恢復越慢。另外還有一個問題,整個架構中大小檔案用兩個系統去存,本身對我們的運維成本控制不太理想,所以我們進一步想把儲存的後端再做一些迭代和最佳化。

  之後,就是GIFT3.5版本。我們把儲存底層換掉,採用CephRADOS object storage。Ceph大家比較熟悉,現在應該是開源的雲端計算場景下應用最多的儲存元件。我們決定儲存底層換成RADOS統一去支撐大小檔案。用這樣一個架構去支援大小檔案,當然有一些邏輯需要我們自己做,比如說小檔案的合併,就像剛才介紹的思路,跟Haystack、SeaweedFS都完全類似。

  就是把一系列小檔案聚合到一個RADOS的大物件裡面,聚合的方式用追加寫。大檔案做分片。刪除非同步做GC。

  下面介紹整個架構。當前的架構就是最前端有做負載均衡的LVS,當然也有CDN,因為提供物件儲存,尤其對外服務,CDN是必不可少的元件。然後資料接入服務,這裡面支援Bucket、Object的邏輯,還支援認證的邏輯,然後支援大檔案LOB小檔案Needle這樣一些RADOS這一層的業務邏輯,要做聚合、做分片。然後新的元件有兩個,一個是CMS,維護整個Ceph叢集本身的拓撲一致性。DSS就是RADOS的一個儲存元件,類似於剛才的volume server,主要是負責資料儲存。MDS是提供後設資料服務的。上報服務和清除服務還是延續3.0的設計。

  下面講我們為什麼要用RADOS。RADOS是國際上廣泛部署使用的大規模分散式儲存系統Ceph的底層儲存元件,提供可伸縮、高可靠的物件儲存。它的優勢在於:高資料可靠性,資料冗餘支援多副本或糾刪碼,支援scrub發現靜默資料錯誤;無後設資料服務、去中心化的設計,良好的橫向擴充套件能力,支援PB級儲存;運維簡單,資料自恢復,叢集自伸縮,資料自平衡;所有元件叢集化設計,無單點故障。RADOS在生產環境的應用廣泛。

  下圖是Ceph的架構,Client提供標準塊、檔案介面的訪問能力,Monitor監視和維護叢集狀態和拓撲結構,OSD儲存資料。

  接下來具體介紹,在這樣一個架構下一些具體問題的處理方式。第一個是怎麼支撐大檔案。對於一個大檔案來講,我們可以在業務這一層做分片。對於大於4M(可配置)以上大檔案,儲存到多個rados物件,提高大檔案訪問效能。因為一個大檔案是分別存到了多個盤上面,在讀取的時候就可以併發了。然後是小檔案的設計思想,小檔案合併儲存到一個rados物件,rados物件後設資料記錄小檔案在物件內的位置和偏移,小檔案使用非同步回收,透過順序寫提高效能。同時採用二級後設資料方式減少後設資料開銷,提高空間利用率。

  非同步GC剛才也提到了,比如說已經有很多小檔案,現在刪了一個,中間就留下了一個空洞,當空洞越來越多的時候,到了一定的閾值,就重新去做一個複製,然後把剩下的資料聚合起來,重新形成一個連續的物件,把剩下的空間釋放出來。(如下圖)

  這裡還有一個上報服務的設計。因為我們要對外提供服務,所以我們需要定期上報使用者的用量統計資訊。這裡我們利用了redis的分散式鎖,上報服務可以在多個節點上同時執行,多個上報服務之間相互不感知,只透過redis分散式鎖去做一個類似於搶佔的模式,來保證不會上報有衝突,或者說同時去上報同一個使用者資訊。具體就是用到redis這樣一個有續集的介面,針對每個使用者,以使用者的ID作為key,它的值就是這一次上報的時間。

  那麼每一個上報服務是怎麼執行的?一來先用redis這個介面做一個排序,根據上次的上報時間做升序排列,最久沒有上報的排在最前面,然後首先去抓列表裡第一個元素來上報。因為大家都會做這個操作,因為大家相互不感知,但是來之前首先會加一把鎖,如果加鎖成功了,那就證明只有我會上報這個資料,別人就肯定不會上報。我就用redis分散式鎖的機制,用一個set lock操作去嘗試上這個鎖。如果上鎖成功,那麼就上報資料,之後就重新把元素的值改成當前時間,然後再把鎖清除。如果同時有另外一個上報服務也想上報這個資料,它會發現加鎖失敗,那麼就自然跳過了上報,然後去找列表裡的第二個元素上報,這樣就保證了大家的一個互斥性。然後也保證了各個上報服務可以並行工作,提升上報的效率。(具體參考下圖)

  異地災備的思想比較簡單。(如下圖)我們支援兩機房,主要就是利用訊息佇列,主資料中心這邊(A機房)儲存完成之後,會向訊息佇列傳送一個訊息,告訴它我現在已經上傳一個檔案,包括訪問的url是什麼樣的——這樣一些資訊。對應(B機房)這邊的備份模組,它訂閱了這樣一個訊息佇列,就可以收到對應的釋出訊息。它知道A機房那邊剛剛上傳一個檔案,訪問的url,然後就可以用url把資料拉過來,存到這邊,實現異地備份。

  另外有一個比較棘手的問題。就是Ceph本身雖然確實比較好,但這種去中心化其實有一個通用的問題,就是當你叢集擴容的時候,需要做資料遷移。這個問題跟一致性雜湊類似,當叢集擴容的時候,有些資料對應的雜湊值就會改變,這就導致一個資料遷移。而資料遷移是我們非常不喜歡的,因為現在僅僅是做了一個擴容操作,就需要做大量的遷移,需要重新做平衡,這樣會影響業務的效能。因為在後端的遷移操作會佔用大量的IO頻寬,所以這個問題我們也是想透過一個方式去解決。

  簡單介紹下Ceph的對映過程,它實際上是一個兩級對映。首先一個Object的名字透過Hash對映到PG,PG(Placement Group)就是一個物件組。引入物件組的概念,避免了object與OSD的直接對映,減小了海量物件管理的複雜性,RADOS的許多操作是以PG為單位進行。

  這個時候首先透過雜湊對映到一個PGID上面,PGID再透過一個CRUSH演算法,選出三組OSD(假如是三副本),其中第一個就是主的儲存副本,其他為從副本。就是這樣的一個過程。(如下圖)

  現在說剛才這個問題,Ceph本身的去中心化CRUSH演算法確實好,自身可擴充套件性非常好。但帶來問題就是叢集擴容的時候,資料要遷移,會導致叢集效能的抖動。那麼我們解決的一個思路,就是想讓它擴容的時候不遷移。思想其實還是跟我們之前解決SeaweedFS本身單叢集效能的思想類似,就是在業務這一層多做一層排程。Ceph本身其實CRUSH規則的應用粒度是pool粒度的,每個pool可以指定不同的CRUSH物件對映規則。利用這一點,我們去增加一層基於pool的排程。具體的演算法就是,如果現在急需擴容,加了一些叢集的節點,我就新建一個pool,然後為pool去配一個CRUSH規則。

  因為Ceph的物件放置規則是可以去描述的、可以指定的,那麼我們就可以去寫CRUSH規則,使得該pool的物件只分布在新增的節點上。在上傳檔案時進行pool排程,選擇空間佔用較少的pool,將檔案寫到選擇的pool。由於已有的pool的叢集節點不變,不會產生資料遷移。所以原來的資料都不會變,新來的資料只會到新的節點上面,主要是透過這樣一個思想去解決遷移問題。

   最後總結一下GIFT V3.5架構的特點 ,它具有:

  1、良好的擴充套件性

  ·接入層無狀態,支援透過負載均衡擴充套件

  ·後設資料儲存層透過分庫分表方式支援橫向擴充套件

  ·資料儲存層透過無中心的設計和分叢集方式支援橫向擴充套件

  2、良好的服務可用性

  ·接入層、後設資料儲存層、資料儲存層都無單點失效問題

  3、良好的資料可靠性

  ·資料多副本儲存,可指定副本放置規則

  ·資料自恢復

  以上主要就是我的一些分享,謝謝!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545805/viewspace-2220818/,如需轉載,請註明出處,否則將追究法律責任。

相關文章