[ Ceph ] 基本概念、原理及架構


前言

在實現容器化的初期,計划使用 Ceph 作為容器的存儲。都說存儲是虛擬化之母,相對容器來說,存儲也起到了至關重要的作用。

選用 Ceph 作為容器化存儲理由如下:

  1. 方便后期橫向擴展;
  2. Ceph能夠同時支持快存儲、對象存儲、文件存儲,容器使用塊存儲,后期也會用到對象存儲來取代 OSS 服務。

基於以上理由,采用 Ceph 分布式存儲應該是個不錯的選擇。容器化第一步從 存儲 開始。

 

Ceph 官方文檔(英文):https://docs.ceph.com/docs/master/start/intro/

Ceph 官方文檔(中文):http://docs.ceph.org.cn/start/intro/

 

Ceph 簡介

不管你是想為 雲平台 提供 Ceph 對象存儲或塊設備,還是想部署一個 Ceph 文件系統 ,所有 Ceph 存儲集群的部署都始於一個個 Ceph節點、網絡和 Ceph存儲集群。

 

創建Ceph 存儲,至少需要以下服務:

  1. 一個Ceph Monitor
  2. 兩個OSD守護進程

 

而運行 Ceph 文件系統客戶端時,則必須要有元數據服務器(Metadata Server) ,使用 Ceph 作為文件系統這種需求場景應該不會很多,而本次的容器化項目也不會涉及到 文件系統存儲,因此不深究。

 

 

Ceph OSDs:Ceph OSD 守護進程(Ceph OSD)的功能是存儲數據,處理數據的復制、恢復、回填、再均衡,並通過檢查其他OSD 守護進程的心跳來向 Ceph Monitors 提供一些監控信息。當 Ceph 集群設定有2個副本時,至少需要2個OSD守護進程,集群才能達到 active+clean 狀態( Ceph 默認有3個副本,但你可以調整副本數)。

可以這樣理解:Ceph 是通過一個個 OSD 來存儲數據的,Ceph 默認的OSD 是3個,但是可以手動設置為2個,最少2個才能使集群達到健康可用的狀態,並且使用 OSD 可以實現數據的高可用。

 

Monitors:Ceph Monitor維護着展示集群狀態的各種圖表,包括監視器圖、 OSD 圖、歸置組( PG )圖、和 CRUSH 圖。 Ceph 保存着發生在Monitors 、 OSD 和 PG上的每一次狀態變更的歷史信息(稱為 epoch )。

可以這樣理解:Monitor 就是Ceph 的一個監視器,Ceph 在存儲數據的時候,各項數據指標都會記錄顯示出來。至於什么 PG / CURSH 后面會討論到。

 

Ceph 把客戶端數據保存為存儲池內的對象。通過使用 CRUSH 算法, Ceph 可以計算出哪個歸置組(PG)應該持有指定的對象(Object),然后進一步計算出哪個 OSD 守護進程持有該歸置組。 CRUSH 算法使得 Ceph 存儲集群能夠動態地伸縮、再均衡和修復。

官方文檔一句話,將Ceph的原理總結出來,但是對於初次接觸Ceph的來說,簡直生澀難懂。總結幾個關鍵字:對象、CRUSH 算法、歸置組(PG)、動態地伸縮

到此,就需要明白 Ceph 整體的工作原理是怎樣的?不然這個疑問會一直圍繞着我們。

 

Ceph 存儲原理介紹

通過查詢資料,發現胖哥這篇文章寫的非常不錯,值得反復理解:大話Ceph - CRUSH 那點事兒

【以下理論知識來源胖哥文章及自己的一些理解】

 

首先拋出一個問題:將一份數據存儲到 Ceph 集群中,一共需要幾步走?

Ceph 的答案是兩步:

  1. 計算PG,也就是官方文檔中的歸置組
  2. 計算OSD

既然提到了計算,那肯定就會有算法,那算法是不是所謂的 CRUSH 呢?

 

計算PG

首先,要明確Ceph的一個規定:在Ceph 中,一切皆對象。(這里提到了對象,和官方文檔吻合,一切皆對象不難理解,同 python一樣,一切皆對象)

一下舉例來說明了,一切皆對象,無論是視頻、照片、文字還是其他格式文件:

不論是視頻,文本,照片等一切格式的數據,Ceph統一將其看作是對象,因為追其根源,所有的數據都是二進制數據保存於磁盤上,所以每一份二進制數據都看成一個對象,不以它們的格式來區分他們。

既然是對象,那對象就應該有對象名。而區分2個對象的就是通過 對象名 來區分的。那如果兩個對象的文件名一樣呢?

現在一開始的問題就變成:把一個對象存儲到Ceph 集群中分幾步走?

已知:Ceph 集群是有若干服務器,確切的說就是一堆磁盤組成,而在Ceph中 每塊磁盤就看作是一個 OSD。

文件又簡化為:把一個對象存儲到 OSD 中分幾步走?

 

Ceph中的邏輯層

Ceph為了保存一個對象,對上構建了一個邏輯層,也就是池(Pool),這個不難理解,就像虛擬化中的存儲池化,用來保存對象,如果把Pool比喻為一個中國象棋棋盤,那么保存一個對象的過程類似於把一粒芝麻放置到棋盤中。

簡單如圖:

 

 

Pool 再一次細分,即將一個 Pool 划分為若干的PG(歸置組),這類似於棋盤上的方格,所有方格構成了整個棋盤,也就是說所有的PG構成了一個 Pool。

 

 

通過這兩個圖,我們可以總結下:文件是一個個對象,對象則是存儲在每個PG里的,而多個PG 構成了Pool

現在問題又來了,對象怎么知道要保存到哪個PG上呢?假定這里我們的 Pool 名叫 rbd,共有 256 個PG,給每個PG 編號分別叫做 0, 1,2, 3, 4

要解決這個問題,首先看目前有什么?

  1. 不同的對象名
  2. 不同的PG編號

這里就引入了Ceph的第一個算法:HASH

對於對象名分別為 bar 和 foo 兩個對象,對他們的對象名進行計算即可:

  • HASH(‘bar’) = 0x3E0A4162
  • HASH(‘foo’) = 0x7FE391A0
  • HASH(‘bar’) = 0x3E0A4162

HASH算法應該是最常用的算法,對對象名進行HASH后,得到一串十六進制的輸出值,也就是說通過HASH我們將一個對象名轉化為一串數字,那么上面的第一行和第三行是一樣的有什么意義?意義就是對於一個同樣的對象名,計算出來的結果永遠都是一樣的,但是HASH算法的確將對象名計算得出了一個隨機數。有了這個隨機數,就對這個隨機數除以PG總數,比如 256 ,得到的余數一定是落在 1-256 之間的,也就是這 256 個PG中的某一個。

公式:HASH('bar') % PG數

  • 0x3E0A4162 % 0xFF ===> 0x62
  • 0x7FE391A0 % 0xFF ===> 0xA0

通過上面的計算,對象bar 保存到編號為 0x62 的PG中,對象 foo 保存在編號 0xA0 的PG中。對象 bar 永遠都會保存在 PG 0x62中!對象 foo 永遠都會保存到PG 0xA0中!

目前,可以總結一個對象是如何存入PG 中的:

對 對象名進行 HASH 取到一個隨機數,然后在用這個隨機數 對 PG 總數取余,得到的值一定落在 1- PG總數之間,改對象名的數據就會永遠的存儲在這個PG 中。

所以,自對象名確定了, 那么該對象保存數據的PG也就確定了。

由此衍生出來一個問題,對象名確定唯一性,難道就不管對象數據的大小了嗎?

答案是肯定的,也就是Ceph不區分對象的真實大小內容以及任何形式的格式,只認對象名。

 

這里給出更Ceph一點的說明,實際上在Ceph中,存在着多個pool,每個pool里面存在着若干的PG,如果兩個pool里面的PG編號相同,Ceph怎么區分呢? 於是乎,Ceph對每個pool進行了編號,比如剛剛的rbd池,給予編號0,再建一個pool就給予編號1,那么在Ceph里,PG的實際編號是由pool_id+.+PG_id組成的,也就是說,剛剛的bar對象會保存在0.62這個PG里,foo這個對象會保存在0.A0這個PG里。其他池里的PG名稱可能為1.12f, 2.aa1,10.aa1等。

 

Ceph 中的物理層

在邏輯層中,已經知道一個文件是如何存儲在Ceph 中PG 里,簡單來說就是通過對對象名進行hash 然后對PG總數取余,得到的余數就是對應的PG,然后將數據存儲在這個PG歸置組里。

接下來看看Ceph里的物理層。若干服務器,服務器上有若干磁盤,通常,Ceph 將一個磁盤看作一個OSD(實際上,OSD是管理磁盤的一個程序),於是物理層由若干OSD組成,我們最終目標是將對象保存到磁盤上,在邏輯層里,對象是保存到PG里的,那么現在的任務就是 打通PG和OSD 之間的隧道。PG相當於一堆余數相同的對象的組合,PG把這一部分對象打了個包,現在我們需要把很多的包平均的安放在各個OSD上,這就是CRUSH 算法所有做的事:CRUSH計算 PG -> OSD的映射關系

此時,加上剛剛邏輯層的對象到PG的算法,可以總結出兩個公式:

  • 池ID+HASH('對象名') % PG_num --> PG_ID
  • CRUSH(PG_ID) --> OSD

在這里采用兩種算法,HASH 和 CRUSH ,這兩種算法有何差異呢?為什么不能直接用 HASH(PG_ID) 到對應的 OSD 上呢?

CURSH(PG_ID) ==> 改為 HASH(PG_ID) % OSD_num ==> OSD

以下是胖哥的推斷:

  1. 如果掛掉一個OSD,OSD_num=1,於是所有的 PG % OSD_num 的余數都會變化,也就是說這個PG保存的磁盤發生了變化,這最簡單的解釋就是,這個PG 上的數據要從一個磁盤轉移到另一個磁盤上去,一個優秀的存儲架構應當在磁盤損壞時使得數據遷移量降到最低,CRUSH 可以做到;
  2. 如果保存多個副本,我們希望得到多個OSD 結果的輸出,HASH只能獲得一個,但是CRUSH 可以獲得任意多個;
  3. 如果增加OSD 的數量,OSD_num 增大了,同樣導致PG 在OSD之間的胡亂遷移,但是CRUSH 可以保證數據向新增機器均勻的擴散。

 

總結起來就是 HASH 算法只適合 1 對 1 的映射關系,並且計算的兩個值還不能變,因此這里就不適合 PG -> OSD 的映射計算。因此,這里開始引入 CRUSH 算法。

 

CRUSH 算法

這里不打算詳細介紹 CRUSH 源碼,只是通過舉例的方式來理解 CRUSH 算法。

首先來看要做什么:

  • 把已有的PG_ID 映射到OSD上,有了映射關系就可以把一個PG保存到一塊磁盤上;
  • 如果想要保存三個副本,可以把一個PG映射到三個不同的 OSD 上,這三個 OSD 上保存着一模一樣的PG內容。

 

在來看看有什么:

  • 互不相同的PG_ID;
  • 如果給OSD也編個號,那么就有了互不相同的 OSD_ID;
  • 每個OSD最大的不同的就是它們的容量,即 4T還是800GB的容量,我們將每個OSD的容量又稱為OSD的權重(weight),規定4T權重為4,800G為0.8,也就是以 T 位單位的值。

 

現在的問題轉化為:如何將 PG_ID 映射到有各自權重的 OSD 上。這里直接使用 CRUSH 算法中的 straw 算法,翻譯過來就是 抽簽,說白了就是挑個最長的簽,這里的簽指的是 OSD 的權重。

那總不能每次都挑選容量最大的OSD吧,這不分分鍾都把數據存滿那個最大的OSD了嗎? 所以在挑之前把這些OSD 搓一搓,這里直接介紹CRUSH 算法:

  • CRUSH_HASH( PG_ID, OSD_ID, r ) ===> draw
  • ( draw &0xffff ) * osd_weight ===> osd_straw
  • pick up high_osd_straw

第一行,我們姑且把r當做一個常數,第一行實際上就做了搓一搓的事情:將PG_ID, OSD_ID和r一起當做CRUSH_HASH的輸入,求出一個十六進制輸出,這和HASH(對象名)完全類似,只是多了兩個輸入。所以需要強調的是,對於相同的三個輸入,計算得出的draw的值是一定相同的。

這個draw到底有啥用?其實,CRUSH希望得到一個隨機數,也就是這里的draw,然后拿這個隨機數去乘以OSD的權重,這樣把隨機數和OSD的權重搓在一起,就得到了每個OSD的實際簽長,而且每個簽都不一樣長(極大概率),就很容易從中挑一個最長的。

 

說白了,CRUSH希望隨機挑一個OSD出來,但是還要滿足權重越大的OSD被挑中的概率越大,為了達到隨機的目的,它在挑之前讓每個OSD都拿着自己的權重乘以一個隨機數,再取乘積最大的那個。那么這里我們再定個小目標:挑個一億次!從宏觀來看,同樣是乘以一個隨機數,在樣本容量足夠大之后,這個隨機數對挑中的結果不再有影響,起決定性影響的是OSD的權重,也就是說,OSD的權重越大,宏觀來看被挑中的概率越大。

 

上面的內容不理解也沒關系,這里在簡單梳理下PG 選擇一個OSD時做的事情:

  • 給出一個 PG_ID ,作為 CRUSH_HASH 的輸入;
  • CRUSH_HASH(PG_ID, OSD_ID, r) 得出一個隨機數(重點是隨機數,不是HASH);
  • 對於所有的OSD用他們的權重乘以每個OSD_ID 對應的隨機數,得到乘積;
  • 選出乘積最大的OSD;
  • 這個PG就會保存到這個OSD上。

 

通過上面的說明,已經可以解決一個PG映射到多個OSD的問題了, 而常量r,當 r+1,在求一遍隨機數,再去乘以每個OSD的權重,再去選出乘積最大的OSD,如果和之前的OSD 編號不一樣,那么就選中它,如果和之前的OSD 編號一樣的話,那么再把 r+2,再選一次隨機數,直到選出我們需要的三個不一樣編號的OSD為止!

當然實際選擇過程還要稍微復雜一點,我這里只是用最簡單的方法來解釋CRUSH在選擇OSD的時候所做的事情。

 

CRUSH 算法的應用

理解了上面CRUSH選擇OSD的過程,我們就很容易進一步將CRUSH算法結合實際結構,這里給出Sage在他的博士論文中畫的一個樹狀結構圖:

 

 

 

最下面的藍色長條可以看成一個個主機,里面的灰色圓柱形可以看成一個個OSD,紫色的cabinet可以也就是一個個機櫃, 綠色的row可以看成一排機櫃,頂端的root是我們的根節點,沒有實際意義,你可以把它看成一個數據中心的意思,也可以看成一個機房的意思,不過只是起到了一個樹狀結構的根節點的作用。

基於這樣的結構選擇OSD,我們提出了新的要求:

  • 一共選出三個OSD;
  • 這三個OSD 需要都位於一個row下面;
  • 每個cabinet內至多有一個OSD。

這樣的要求,如果用上一節的CRUSH 選 OSD 的方法,不能滿足二三兩個要求,因為 OSD 的分布是隨機的。

那么要完成這樣的要求,先看看有什么:

  • 每個OSD的weight;
  • 每個主機也可以有一個weight,這個weight由主機內的所有 OSD 的weight累加而得;
  • 每個cabinet的weight由所有主機的weight累加而得,其實就是這個cabinet下的所有OSD的權重值和;
  • 同理推得每個row的weight有cabinet累加而得;
  • root的weight其實就是所有的OSD的權重之和。

 

所以在這棵樹狀結構中,每個節點都有了自己的權重,每個節點的權重由下一層節點的權重累加而得,因此根節點root的權重就是這個集群所有的OSD的權重之和,那么有了這么多權重之后,我們怎么選出那三個OSD呢?

仿照CRUSH 選 OSD 的方法:

  • CRUSH從root下的所有的row中選出一個row;
  • 在剛剛的一個row下面的所有cabinet中,CRUSH選出三個cabinet;
  • 在剛剛的三個cabinet下面的所有OSD中,CRUSH分別選出一個OSD。

因為每個row都有自己的權重,所以CRUSH選row的方法和選OSD的方法完全一樣,用row的權重乘以一個隨機數,取最大。然后在這個row下面繼續選出三個cabinet,再在每個cabinet下面選出一個OSD。

這樣做的根本意義在於,將數據平均分布在了這個集群里面的所有OSD上,如果兩台機器的權重是16:32,那么這兩台機器上分布的數據量也是1:2。同時,這樣選擇做到了三個OSD分布在三個不同的cabinet上。

那么結合圖例這里給出CRUSH算法的流程:

  • take(root) ============> [root]
  • choose(1, row) ========> [row2]
  • choose(3, cabinet) =====> [cab21, cab23, cab24] 在[row2]下
  • choose(1, osd) ========> [osd2107, osd2313, osd2437] 在三個cab下
  • emit ================> [osd2107, osd2313, osd2437]

 

這里給出CRUSH算法的兩個重要概念:

  • BUCKET/OSD:OSD和我們的磁盤一一對應,bucket是除了OSD以外的所有非子節點,比如上面的 cabinet,row,root等都是;
  • RULE:CRUSH選擇遵循一條條選擇路徑,一個選擇路徑就是一個rule。

 

RULE 一般分為三步走: take -> choose N -> emit. 

Take這一步負責選擇一個根節點,這個根節點不一定是root,也可以是任何一個Bucket。

choose N 做的就是按照每個 Bucket 的weight 以及每個 choose 語句選出符合條件的 Bucket,並且,下一個 choose 的選擇對象為上一步得到的結果。

emit 就是輸出最終結果相當於出棧。

 

這里再舉個簡單的例子,也就是我們最常見的三個主機每個主機三個OSD的結構:

 

 

我們要從三個host下面各選出一個OSD,使得三個副本各落在一個host上,這時候,就能保證掛掉兩個host,還有一個副本在運行了,那么這樣的RULE就形如:

  • take(root) ============> [default] 注意是根節點的名字
  • choose(3, host) ========> [ceph-1, ceph-2, ceph-3]
  • choose(1, osd) ========> [osd.3, osd.1, osd.8]
  • emit()

這里在簡單總結下:

我們把一個生產環境的機房畫成一個樹狀結構:

最下面一層為OSD層,每個OSD有自己的權重。

OSD的上面由host/rack/row/room/root等等節點構成,每個節點的權重都是由下層的節點累加而成。

CRUSH選擇每個節點的算法(straw)都是一樣的,用它們的weight乘以一個隨機數取其中最大,只是我們通過choose的語句來判斷選擇的節點類型和個數。

最后不要忘了選出來的結果是PG->OSD的映射,比如:pg 0.a2 ---> [osd.3, osd.1, osd.8]和pg 0.33 ---> [osd.0, osd.5, osd.7], 每個PG都有自己到OSD的映射關系,這個關系用公式總結就是: CRUSH(pg_id) ---> [osd.a, osd.b ...osd.n]。

 

到目前為止,我們已經完成了一份數據保存到一群Server的第二步,再整體回顧下這個流程:

  • 每個文件都有一個唯一的對象名;
  • Pool_ID + HASH(對象名) % PG_NUM 得到PG_ID;
  • CRUSH(PG_ID) 得到該PG將要保存的OSD組合;
  • 這個對象就會保存到位於這些OSD上的PG上(PG就是磁盤上的目錄)

所以,HASH算法負責計算對象名到PG的映射,CRUSH負責計算PG到OSD的映射,暫且記住這一點。

 

Ceph 架構

通過上面對原理的理解,再看下圖Ceph架構就很容理解了。

 

從上往下看:

1. Ceph 對外提供了 4 個接口:

  • 應用直接訪問RADOS,這個需要通過自行開發調用接口,適合自主開發;
  • 對象存儲接口,支持(Amazon)S3 和 Swift 的調用方式,適合存儲對象存儲,如圖片、視頻等;
  • 塊存儲接口(RBD),主要對外提供塊存儲,例如提供給虛擬機使用的塊設備存儲;
  • 文件存儲(CephFS),類似於NFS 的掛載目錄存儲。

2. 應用直接訪問、對象存儲、塊存儲都是依賴於 LibRados庫文件的

3. Ceph將底層是一個RADOS對象存儲系統,通過RADOS對象存儲系統對外提供服務

4. MDS 元數據節點、MON 管理控制節點、很多的 Pool存儲池

5. 在Pool存儲池中包含了很多PG歸置組,然后通過 CRUSH 算法將PG中的數據存儲到各個 OSD 組中。

 

在上面的步驟中,我們需要重點關注第 5 點,上文說過,在 Ceph中,一切皆對象。首先通過將對象名 通過 HASH 算法得到一個16進制的數,再對 Pool中的PG總數取余,得到的余數一定落在 PG總數中的一個節點上,Ceph就將這個對象名的數據存儲在這個PG里,然后再通過 CRUSH_HASH(PG_ID, OSD_ID, r) 計算得到一個值,這樣就可以將數據落到OSD上,而在Ceph中,OSD 就是磁盤的代名詞。通過圖示來表示這一過程:

 

 

Ceph 特點及核心組件

 

Ceph 特點:

高性能

  • 摒棄了傳統的集中式存儲元數據尋址的方案,采用 CRUSH 算法,數據分布均衡,並行度高;
  • 考慮了容災域的隔離,能夠實現各類負載的副本放置規則;
  • 能夠支持上千個存儲節點的規模,支持TB到PB級的數據。

高可用

  • 副本數可以靈活控制
  • 支持故障域分隔,數據強一致性
  • 多種故障場景自動進行修復自愈
  • 沒有單點故障,自動管理

高擴展

  • 原生分布式去中心化;
  • 擴展靈活;
  • 隨着節點增加而線性增長。

豐富特性

  • 支持三種存儲接口:塊存儲、對象存儲、文件存儲;
  • 支持自定義接口,支持多個語言。

 

核心組件:

Monitor

  • 一個Ceph集群需要多個Monitor組成的小集群,用來保存OSD的元數據。

OSD

  • OSD 全稱Object Storage Device,也就是負責響應客戶端請求返回具體數據的進程。一個Ceph集群一般都有很多個OSD,Ceph最終數據也是由OSD保存到磁盤上的。

MDS

  • MDS全稱Ceph Metadata Server,是CephFS服務依賴的元數據服務。如果不使用文件存儲,則不需要安裝。

Object

  • Ceph最底層的存儲單元是Object對象,每個Object包含元數據和原始數據。

PG

  • PG全稱Placement Grouops,是一個邏輯的概念,一個PG包含多個OSD。引入PG這一層其實是為了更好的分配數據和定位數據。

RADOS

  • RADOS全稱Reliable Autonomic Distributed Object Store,是Ceph集群的精華,用戶實現數據分配、Failover等集群操作。

Libradio

  • Librados是Rados提供庫,因為RADOS是協議很難直接訪問,因此上層的RBD、RGW和CephFS都是通過librados訪問的,目前提供PHP、Ruby、Java、Python、C和C++支持。

CRUSH

  • CRUSH是Ceph使用的數據分布算法,類似一致性哈希,讓數據分配到預期的地方。

RBD

  • RBD全稱RADOS block device,是Ceph對外提供的塊設備服務。

RGW

  • RGW全稱RADOS gateway,是Ceph對外提供的對象存儲服務,接口與S3和Swift兼容,如果不使用對象存儲則不需要安裝。

CephFS

  • CephFS全稱Ceph File System,是Ceph對外提供的文件系統服務。

 

到此, Ceph的一些基礎概念、原理及架構的學習完畢,接下來嘗試搭建一個Ceph集群。

 

參考鏈接:

http://xuxiaopang.com/2016/11/08/easy-ceph-CRUSH/

https://blog.csdn.net/uxiAD7442KMy1X86DtM3/article/details/81059215

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM