Ceph分佈式存儲實踐應用之深刻Ceph實現原理

1. Crush算法與做用

CRUSH算法,全稱Controlled Replication Under Scalable Hashing (可擴展哈希下的受控複製),它是一個可控的、可擴展的、分佈式的副本數據放置算法, 經過CRUSH算法來計算數據存儲位置來肯定如何存儲和檢索數據。算法

  • 保障數據分佈的均衡性後端

    讓數據可以均勻的分不到各個節點上面,同時讓數據訪問的讀寫操做在各個節點和磁盤上保持負載均衡。緩存

  • 集羣的靈活伸縮性服務器

    能夠方便快速的增長或刪除節點,對失效的節點自動檢測處理,可以自動實現數據的均衡,而且儘量少的遷移改動數據。網絡

  • 支持更大規模的集羣架構

    可以作到數據分佈算法維護較小的元數據與計算量, 隨着集羣規模的不斷增長,保持較小的數據分佈式算法開銷,不能呈線性增加。負載均衡

2. Crush算法說明

PG到OSD的映射的過程算法稱爲CRUSH 算法,它是一個僞隨機的過程,能夠從全部的OSD中,隨機性選擇一個OSD集合,可是同一個PG每次隨機選擇的結果是不變的,實質上映射的OSD集合是固定的。框架

CRUSH使Ceph客戶機可以直接與OSDs通訊,而不是經過集中的服務器或代理。經過算法肯定的數據存儲和檢索方法,從而避免了單點故障、性能瓶頸和對其可伸縮性的物理限制。異步

Crush Map將系統的全部硬件資源描述成一個樹狀結構,而後再基於這個結構按照必定的容錯規則生成一個邏輯上的樹形結構,樹的末級葉子節點device也就是OSD,其餘節點稱爲bucket節點,根據物理結構抽象的虛擬節點,包含數據中心抽象、機房抽象、機架抽象、主機抽象。分佈式

file

3. Crush算法原理

  1. Ceph的存儲結構

    Ceph爲了保存對象,會先構建一個池(pool),把pool能夠比喻成一個倉庫,一個新對象的保存就相似於把一個包裹放到倉庫裏面。

    爲了更好的提高效率,Pool能夠劃分爲若干的PG(Placement Group)歸置組,這相似於倉庫裏面有不一樣的儲物架,全部的儲物架組成了一個完整的倉庫,全部的PG也就構建成了一個pool。

  2. PG的分配存儲

    對象是如何保存至哪一個PG上?假設Pool名稱爲rbd,共有256個PG,每一個PG編個號分別叫作0x0, 0x1, 0x2,... 0xFF。 具體該如何分配?這裏能夠採用Hash方式計算。

    假設有兩個對象名, 分別爲bar和foo的,根據對象名作Hash計算:

    HASH(‘bar’) = 0x3E0A4162

    HASH(‘foo’) = 0x7FE391A0

    經過Hash獲得一串隨機的十六進制的值, 對於一樣的對象名,計算出的結果可以永遠保持一致,但咱們預分配的是256個PG,這就須要再進行取模處理, 所得的結果會落在【0x0,0xFF】區間:

    0x3E0A4162 % 0xFF ===> 0x62

    0x7FE391A0 % 0xFF ===> 0xA0

    實際在Ceph中, 存在不少個Pool,每一個Pool裏面存在若干個PG,若是兩個Pool裏面的PG編號相同,該如何標識區分?Ceph會對每一個pool再進行編號,好比上面名稱爲rbd的Pool,給定ID編號爲0, 再新增一個Pool,ID編號設定爲1,那麼一個PG的實際編號是由pool_id + . + pg_id組成。由此推論, 剛纔的bar對象會保存在編號爲0的pool,pg編號爲62裏面。

  3. OSD的分配存儲

    Ceph的物理層,對應的是服務器上的磁盤,Ceph將一個磁盤或分區做爲OSD,在邏輯層面,對象是保存至PG內,如今須要打通PG與OSD之間的聯繫, Ceph當中會存在較多的PG數量,如何將PG平均分佈各個OSD上面,這就是Crush算法主要作的事情: 計算PG -> OSD的映射關係。

    上述所知, 主要兩個計算步驟:

    POOL_ID(對象池) + HASH(‘對象名稱’) % pg_num(歸置組)==> PG_ID (完整的歸置組編號)

    CRUSH(PG_ID)==> OSD (對象存儲設備位置)

  4. 爲何須要採用Crush算法

    若是把CRUSH(PG_ID)改爲 HASH(PG_ID)% OSD_NUM 可否適用? 是會存在一些問題。

    1)若是掛掉一個OSD,全部的OSD_NUM 餘數就會發生變化,以前的數據就可能須要從新打亂整理, 一個優秀的存儲架構應當在出現故障時, 可以將數據遷移成本降到最低, CRUSH則能夠作到。

    2)若是增長一個OSD, OSD_NUM數量增大, 一樣會致使數據從新打亂整理,可是經過CRUSH能夠保障數據向新增機器均勻的擴散, 且不須要從新打亂整理。

    3)若是保存多個副本,就須要可以獲取多個OSD結果的輸出, 可是HASH方式只能獲取一個, 可是經過CEPH的CRUSH算法能夠作到獲取多個結果。

  1. Crush算法如何實現

    每一個OSD有不一樣的容量,好比是4T仍是800G的容量,能夠根據每一個OSD的容量定義它的權重,以T爲單位, 好比4T權重設爲4,800G則設爲0.8。

    那麼如何將PG映射到不一樣權重的OSD上面?這裏能夠直接採用CRUSH裏面的Straw抽籤算法,這裏面的抽籤是指挑取一個最長的籤,而這個籤值得就是OSD的權重。若是每次都存儲在容量最大的OSD上,很容易將該節點塞滿, 這就須要採起相似隨機權重的算法來作實現。

    ![file](/img/bVcQ9tP)

    主要步驟:

    • 計算HASH: CRUSH_HASH( PG_ID, OSD_ID, r ) ==> draw

      把r當作一個常數,將PG_ID, OSD_ID一塊兒做爲輸入,獲得一個HASH值。

    • 增長OSD權重: ( draw &0xffff ) * osd_weight ==> osd_straw

      將計算出的HASH值與OSD的權重放置一塊兒,這樣就可以獲得每一個OSD的籤長, 權重越大的,數值越大。

    • 遍歷選取最高的權重:high_draw

    Crush目的是隨機跳出一個OSD,而且要知足權重越大的OSD,挑中的機率越大,爲了保障隨機性,將每一個OSD的權重都乘以一個隨機數也就是HASH值,再去結果最大的那個。若是樣本容量足夠大, 隨機數對選中的結果影響逐漸變小, 起決定性的是OSD的權重,OSD的權重越大, 被挑選的機率也就越大,這樣可以作到數據的有效分佈。

    Crush所計算出的隨機數,是經過HASH得出來,這樣能夠保障相同的輸入, 會得出一樣的輸出結果。 因此Crush並非真正的隨機算法, 而是一個僞隨機算法。

    這裏只是計算得出了一個OSD,在Ceph集羣中是會存在多個副本,如何解決一個PG映射到多個OSD的問題?

    將以前的常量r加1, 再去計算一遍,若是和以前的OSD編號不同, 那麼就選取它;若是同樣的話,那麼再把r+2,再從新計算,直到選出三個不同的OSD編號。
    ![file](/img/bVZkcX)

    假設常數r=0,根據算法(CRUSH_HASH & 0xFFFF) * weight 計算最大的一個OSD,結果爲osd.1的0x39A00,也就是選出的第一個OSD,而後再讓r=1, 生成新的CRUSH_HASH隨機值,取得第二個OSD,依次獲得第三個OSD。

4. IO流程圖

file

步驟:

  1. client鏈接monitor獲取集羣map信息。
  2. 同時新主osd1因爲沒有pg數據會主動上報monitor告知讓osd2臨時接替爲主。
  3. 臨時主osd2會把數據全量同步給新主osd1。
  4. client IO讀寫直接鏈接臨時主osd2進行讀寫。
  5. osd2收到讀寫io,同時寫入另外兩副本節點。
  6. 等待osd2以及另外兩副本寫入成功。
  7. osd2三份數據都寫入成功返回給client, 此時client io讀寫完畢。
  8. 若是osd1數據同步完畢,臨時主osd2會交出主角色。
  9. osd1成爲主節點,osd2變成副本。

5. Ceph 通訊機制

網絡通訊框架三種不一樣的實現方式:

  • Simple線程模式

    • 特色:每個網絡連接,都會建立兩個線程,一個用於接收,一個用於發送。
    • 缺點:大量的連接會產生大量的線程,會消耗CPU資源,影響性能。
  • Async事件的I/O多路複用模式

    • 特色:這種是目前網絡通訊中普遍採用的方式。新版默認已經使用Asnyc異步方式了。
  • XIO方式使用了開源的網絡通訊庫accelio來實現

    • 特色:這種方式須要依賴第三方的庫accelio穩定性,目前處於試驗階段。

消息的內容主要分爲三部分:

  • header //消息頭類型消息的信封
  • user data //須要發送的實際數據

    • payload //操做保存元數據
    • middle //預留字段
    • data //讀寫數據
  • footer //消息的結束標記

file

步驟:

  • Accepter監聽peer的請求, 調用 SimpleMessenger::add_accept_pipe() 建立新的 Pipe, 給 SimpleMessenger::pipes 來處理該請求。
  • Pipe用於消息的讀取和發送。該類主要有兩個組件,Pipe::Reader,Pipe::Writer用來處理消息讀取和發送。
  • Messenger做爲消息的發佈者, 各個 Dispatcher 子類做爲消息的訂閱者, Messenger 收到消息以後, 經過 Pipe 讀取消息,而後轉給 Dispatcher 處理。
  • Dispatcher是訂閱者的基類,具體的訂閱後端繼承該類,初始化的時候經過 Messenger::add_dispatcher_tail/head 註冊到 Messenger::dispatchers. 收到消息後,通知該類處理。
  • DispatchQueue該類用來緩存收到的消息, 而後喚醒 DispatchQueue::dispatch_thread 線程找到後端的 Dispatch 處理消息。

6. Ceph RBD 塊存儲 IO流程圖

file

osd寫入過程:

  1. 採用的是librbd的形式,使用librbd建立一個塊設備,向這個塊設備中寫入數據。
  2. 在客戶端本地經過調用librados接口,而後通過pool,rbd,object、pg進行層層映射,在PG這一層中,能夠知道數據是保存在哪三個OSD上,這三個OSD分別爲主從的關係。
  3. 客戶端與primary OSD創建SOCKET 通訊,將要寫入的數據傳給primary OSD,由primary OSD再將數據發送給其餘replica OSD數據節點。

7. Ceph 心跳和故障檢測機制

問題:

故障檢測時間和心跳報文帶來的負載, 如何權衡下降壓力?

  1. 心跳頻率過高則過多的心跳報文會影響系統性能。
  2. 心跳頻率太低則會延長髮現故障節點的時間,從而影響系統的可用性。

故障檢測策略應該可以作到:

及時性:節點發生異常如宕機或網絡中斷時,集羣能夠在可接受的時間範圍內感知。

適當的壓力:包括對節點的壓力,和對網絡的壓力。

容忍網絡抖動:網絡偶爾延遲。

擴散機制:節點存活狀態改變致使的元信息變化須要經過某種機制擴散到整個集羣。

file

OSD節點會監聽public、cluster、front和back四個端口

  • public端口:監聽來自Monitor和Client的鏈接。
  • cluster端口:監聽來自OSD Peer的鏈接。
  • front端口:客戶端鏈接集羣使用的網卡, 這裏臨時給集羣內部之間進行心跳。
  • back端口:在集羣內部使用的網卡。集羣內部之間進行心跳。
  • hbclient:發送ping心跳的messenger(送信者)。

file

Ceph OSD之間相互心跳檢測

  • 同一個PG內OSD互相心跳,他們互相發送PING/PONG信息。
  • 每隔6s檢測一次(實際會在這個基礎上加一個隨機時間來避免峯值)。
  • 20s沒有檢測到心跳回復,加入failure隊列。

本文由mirson創做分享,如需進一步交流,請加QQ羣:19310171或訪問www.softart.cn

相關文章
相關標籤/搜索