sheepdog調研學習

1. 基本介紹node

sheepdog是近幾年開源社區新興的分佈式塊存儲文件系統,採用徹底對稱的結構,沒 有相似元數據服務的中心節點。這種架構帶來了線性可擴展性,沒有單點故障和容易管理的特性。對於磁盤和物理節點,SheepDog實現了動態管理容量以及 隱藏硬件錯誤的特性。對於數據管理,SheepDog利用冗餘來實現高可用性,並提供自動恢復數據數據,平衡數據存儲的特性。除此以外,sheepdog 還有具備零配置、高可靠、智能節點管理、容量線性擴展、虛擬機感知(底層支持冷熱遷移和快照、克隆等)、支持計算與存儲混合架構的特色等。目前,開源軟件 如QEMU、Libvirt以及Openstack都很好的集成了對Sheepdog的支持。在 openstack中,能夠做爲cinder和glance的後端存儲。算法

sheepdog整體包括集羣管理和存儲管理兩大部分。集羣管理使用已有的集羣管理工具來管理,存儲管理基於本地文件系統來實現。目前支持的本地文件系統包括ext4和xfs後端

編譯後的sheepdog由兩個程序組成,一個是守護程序sheep,一個是集羣管理工具dog,守護程序sheep同時兼備了節點路由和和對象存儲的功能。centos

Sheep進程之間經過節點路由(gateway)的邏輯轉發請求,而具體的對象經過對象存儲的邏輯保存在各個節點上,這就把全部節點上的存儲空間聚合起來,造成一個共享的存儲空間。數據結構

Sheepdog由兩個程序組成,一個是後臺進程sheep,一個是前臺管理工具dog。Dog主要負責管理整個sheep集羣,包括集羣管理,VDI管理等。集羣管理主要包括集羣的狀態獲取,集羣快照,集羣恢復,節點信息,節點日誌,節點恢復等。VDI管理包括VDI的建立,刪除,快照,檢 查,屬性等等。架構

Dog是一個命令行工具,啓動時,會向後臺sheep進程發起TCP鏈接,經過鏈接傳輸控制指令。當sheep收到控制指令時,若是有須要,會將相應指令擴散到集羣中,加上對稱式的設計,從而使得dog可以管理整個集羣併發

 

2. 基本架構socket

  • 由corosync完成集羣成員管理和有關集羣消息傳遞,好比對於節點加入刪除等狀況檢測;
  • 由Qemu VM做爲Sheepdog的客戶端,進行快照克隆、建立虛擬卷等操做命令的執行,提供NBD/iSCSI協議支持;
  • 由gateway實現數據的DHT路由,接收QEMU塊驅動的I/O請求,經過散列算法得到目標節點,而後轉發I/O請求至該節點;
  • 由Sheep store數據本地存儲.

  • Corosync發送有關集羣處理的消息給Sheep,Sheep再進行集羣節點的加入刪除等操做
  • Qemu和Dog(提供了一系列系統命令)發送命令解析後的請求給Sheep,Sheep再根據具體的請求類型進行相關處理

 

3. 啓動流程分佈式

3.1 sheep啓動ide

啓動過程當中會有一些初始化的工做,對於基本目錄的初始化,對於obj、epoch、journal路徑的初始化,以及對於集羣和工做隊列的初始化。下圖能夠看到sheep基本的啓動流程

3.2 建立監聽端口

經過socket建立來自客戶端的請求,註冊對應的listen_handler和client_handler事件,對請求進行相應的處理。相關處理函數的函數指針賦值給fn和done,以下圖右下rx_work和rx_main便可知:

3.3 工做隊列初始化

在線程函數worker_routine中將對應請求操做的處理函數work->fn(work)根據不一樣隊列不一樣請求執行對應處理函數,執行完後加入完成隊列,再根據不一樣隊列不一樣請求執行對應處理函數done()

3.4 事件機制

event_loop函數根據事件觸發機制,等待新事件的到來,觸發epoll_wait,以後相應的句柄函數進行相應處理。

一、listen_handler 偵聽到客戶端有鏈接請求時,會將該鏈接 fd 註冊到主線程 efd 中,該 fd 與 client_handler 綁定,當客戶端向該 fd 發送請求時,主線程會及時檢測到而且調用 client_handler 對請求進行處理
二、local_req_handler包括對gateway、cluster、io的相關處理
三、sigfd = signalfd(-1, &mask, SFD_NONBLOCK);
四、sys->local_req_efd = eventfd(0, EFD_NONBLOCK);

 

4. dog啓動流程

dog部分主要是執行客戶端的命令行請求,而後對命令進行解析,經過指定socket發送請求到sheep端,將請求交sheep端處理。

一、init_commands(&commands)函數將dog支持的命令都初始化在commands中進行調用,包括對vdi、cluster、node的命令操做,
二、setup_commands()函數先比較主命令,而後比較subvommmand,將對應的處理函數賦值給command_fn函數指針,最後調用此函數對命令進行處理

 

4.2 dog支持的命令

下面給出dog能執行的命令,及操做這些命令的函數

4.2.1 node命令

kill node_kill 刪除節點
list node_list 列舉節點信息
info node_info 顯示一個節點的信息
recovery node_recovery 顯示節點的恢復信息
md node_md 顯示md信息
log node_log 顯示節點有關日誌的信息

4.2.2 vdi命令

check vdi_check               檢查和修復image的一致性
create vdi_create               建立一個image
snapshot    vdi_snapshot           建立一個快照
clone          vdi_clone                 克隆一個image
delete         vdi_delete                刪除一個image
rollback      vdi_rollback             回滾到一個快照
list              vdi_list                     列舉images
tree            vdi_tree                   以樹的形式顯示images
graph         vdi_graph                以圖的形式顯示images
object        vdi_object                顯示image裏面對象的信息

track         
vdi_track                  顯示image裏面對象的版本蹤影

setattr      
vdi_setattr                設置一個vdi的屬性

getattr      
vdi_getattr                得到一個vdi的屬性

resize       
vdi_resize                從新設置一個image的大小

read         
vdi_read                   從一個image裏面讀數據

write         
vdi_write                  寫數據到一個image裏面

backup     
vdi_backup              在兩個快照之間建立一個增量備份

restore     
vdi_restore               從備份裏面復原images快照

cache       
vdi_cache                運行dog vdi cache獲得更多信息

 

4.2.3 cluster命令

info                  cluster_info                顯示集羣信息
format             cluster_format            建立一個sheepdog存儲
shutdown        cluster_shutdown       關閉sheepdog
snapshot         cluster_snapshot        爲集羣創建快照或復原集羣
recover            cluster_recover          看dog cluster recover得更多信息
reweight          cluster_reweight        reweight集羣


5. 部分數據結構

5.1 vdi object

struct sd_inode {
    char name[SD_MAX_VDI_LEN];           // vdi的名稱   
    char tag[SD_MAX_VDI_TAG_LEN];        // 快照名稱
    uint64_t create_time;                
    uint64_t snap_ctime;
    uint64_t vm_clock_nsec;              // 用於在線快照
    uint64_t vdi_size;
    uint64_t vm_state_size;              // vm_state的大小
    uint8_t  copy_policy;                // 副本策略
    uint8_t  store_policy;
    uint8_t  nr_copies;
    uint8_t  block_size_shift;
    uint32_t snap_id;
    uint32_t vdi_id;
    uint32_t parent_vdi_id;              // 父對象id

    uint32_t btree_counter;
    uint32_t __unused[OLD_MAX_CHILDREN - 1];

    uint32_t data_vdi_id[SD_INODE_DATA_INDEX];
    struct generation_reference gref[SD_INODE_DATA_INDEX];
};

6. QEMU塊驅動

Open

首先QEMU塊驅動經過getway的bdrv_open()從對象存儲讀取vdi

讀/寫(read/write)

塊驅動經過請求的部分偏移量和大小計算數據對象id, 並向getway發送請求. 當塊驅動發送寫請求到那些不屬於其當前vdi的數據對象是,塊驅動發送CoW請求分配一個新的數據對象.

寫入快照vdi(write to snapshot vdi)

咱們能夠把快照VDI附加到QEMU, 當塊驅動第一次發送寫請求到快照VDI, 塊驅動建立一個新的可寫VDI做爲子快照,併發送請求到新的VDI.

VDI操做(VDI Operations)

查找(lookup)

當查找VDI對象時:

1)       經過求vdi名的哈希值獲得vdi id

2)       經過vdi id計算di對象

3)       發送讀請求到vdi對象

4)       若是此vdi不是請求的那個,增長vdi id並重試發送讀請求

快照,克隆(snapshot, cloning)

快照可克隆操做很簡單,

1)       讀目標VDI

2)       建立一個與目標同樣的新VDI

3)       把新vdi的‘'parent_vdi_id''設爲目標VDI的id

4)       設置目標vdi的''child_vdi_id''爲新vdi的id.

5)       設置目標vdi的''snap_ctime''爲當前時間, 新vdi變爲當前vdi對象

刪除(delete)

TODO:當前,回收未使用的數據對象是不會被執行,直到全部相關VDI對象(相關的快照VDI和克隆VDI)被刪除.

全部相關VDI被刪除後, Sheepdog刪除全部此VDI的數據對象,設置此VDI對象名爲空字符串.

對象恢復(Object Recovery)

epoch

Sheepdog把成員節點歷史存儲在存儲路徑, 路徑名以下:

        /store_dir/epoch/[epoch number]

每一個文件包括節點在epoch的列表信息(IP地址,端口,虛擬節點個數).

恢復過程(recovery process)

1)       從全部節點接收存儲對象ID

2)       計算選擇那個對象

3)       建立對象ID list文件"/store_dir/obj/[the current epoch]/list"

4)       發送一個讀請求以獲取id存在於list文件的對象. 這個請求被髮送到包含前一次epoch的對象的節點.( The requests are sent to the node which had the object at the previous epoch.)

5)       把對象存到當前epoch路徑.

衝突的I/O(conflicts I/Os)

若是QEMU發送I/O請求到某些未恢復的對象, Sheepdog阻塞此請求並優先恢復對象.

協議(Protocol)

Sheepdog的全部請求包含固定大小的頭部(48位)和固定大小的數據部分,頭部包括協議版本,操做碼,epoch號,數據長度等.

between sheep and QEMU

操做碼

描述

SD_OP_CREATE_AND_WRITE_OBJ

發送請求以建立新對象並寫入數據,若是對象存在,操做失敗

SD_OP_READ_OBJ

讀取對象中的數據

SD_OP_WRITE_OBJ

向對象寫入數據,若是對象不存在,失敗

SD_OP_NEW_VDI

發送vdi名到對象存儲並建立新vdi對象, 返回應答vdi的惟一的vdi id

SD_OP_LOCK_VDI

與SD_OP_GET_VDI_INFO相同

SD_OP_RELEASE_VDI

未使用

SD_OP_GET_VDI_INFO

獲取vdi信息(例:vdi id)

SD_OP_READ_VDIS

獲取已經使用的vdi id

between sheep and collie

操做碼

描述

SD_OP_DEL_VDI

刪除VDI

SD_OP_GET_NODE_LIST

獲取sheepdog的節點列表

SD_OP_GET_VM_LIST

未使用

SD_OP_MAKE_FS

建立sheepdog集羣

SD_OP_SHUTDOWN

中止sheepdog集羣

SD_OP_STAT_SHEEP

獲取本地磁盤使用量

SD_OP_STAT_CLUSTER

獲取sheepdog集羣信息

SD_OP_KILL_NODE

退出sheep守護進程

SD_OP_GET_VDI_ATTR

獲取vdi屬性對象id

between sheeps

操做碼

描述

SD_OP_REMOVE_OBJ

刪除對象

SD_OP_GET_OBJ_LIST

獲取對象id列表,並存儲到目標節點

 

 

7. oid到vnodes的映射

/* 調用 */

oid_to_vnodes(oid, &req->vinfo->vroot, nr_copies, obj_vnodes);
/* 首先肯定第一個zone的位置,隨後按照zone進行便利 */
/*
Replica are placed along the ring one by one with different zones */ static inline void oid_to_vnodes(uint64_t oid, struct rb_root *root, int nr_copies, const struct sd_vnode **vnodes) { const struct sd_vnode *next = oid_to_first_vnode(oid, root); vnodes[0] = next; for (int i = 1; i < nr_copies; i++) { next: next = rb_entry(rb_next(&next->rb), struct sd_vnode, rb); if (!next) /* Wrap around */ next = rb_entry(rb_first(root), struct sd_vnode, rb); if (unlikely(next == vnodes[0])) panic("can't find a valid vnode"); for (int j = 0; j < i; j++) if (same_zone(vnodes[j], next)) goto next; vnodes[i] = next; } }
/* 這裏就是按照順時針將oid_hash分配到對應的節點上 */
/*
If v1_hash < oid_hash <= v2_hash, then oid is resident on v2 */ static inline struct sd_vnode * oid_to_first_vnode(uint64_t oid, struct rb_root *root) { struct sd_vnode dummy = { .hash = sd_hash_oid(oid), }; return rb_nsearch(root, &dummy, rb, vnode_cmp); }
/*
 * Create a hash value from an object id.  The result is same as sd_hash(&oid,
 * sizeof(oid)) but this function is a bit faster.
 */
static inline uint64_t sd_hash_oid(uint64_t oid)
{
    return sd_hash_64(oid);
}
 
 

/* 64 bit FNV-1a non-zero initial basis */
#define FNV1A_64_INIT ((uint64_t) 0xcbf29ce484222325ULL)
#define FNV_64_PRIME ((uint64_t) 0x100000001b3ULL

static inline uint64_t sd_hash_64(uint64_t oid)
{
    uint64_t hval = fnv_64a_64(oid, FNV1A_64_INIT);

    return fnv_64a_64(hval, hval);
}

 

 1 /* 就是FNV-1a的實現
 2  * The result is same as fnv_64a_buf(&oid, sizeof(oid), hval) but this function
 3  * is a bit faster.
 4  */
 5 static inline uint64_t fnv_64a_64(uint64_t oid, uint64_t hval)
 6 {
 7     hval ^= oid & 0xff;
 8     hval *= FNV_64_PRIME;
 9     hval ^= oid >> 8 & 0xff;
10     hval *= FNV_64_PRIME;
11     hval ^= oid >> 16 & 0xff;
12     hval *= FNV_64_PRIME;
13     hval ^= oid >> 24 & 0xff;
14     hval *= FNV_64_PRIME;
15     hval ^= oid >> 32 & 0xff;
16     hval *= FNV_64_PRIME;
17     hval ^= oid >> 40 & 0xff;
18     hval *= FNV_64_PRIME;
19     hval ^= oid >> 48 & 0xff;
20     hval *= FNV_64_PRIME;
21     hval ^= oid >> 56 & 0xff;
22     hval *= FNV_64_PRIME;
23 
24     return hval;
25 }
 1 static inline void
 2 disks_to_vnodes(struct rb_root *nroot, struct rb_root *vroot)
 3 {
 4     struct sd_node *n;
 5 
 6     rb_for_each_entry(n, nroot, rb)
 7         n->nr_vnodes = node_disk_to_vnodes(n, vroot);
 8 }
 9 
10 
11 static inline void
12 node_to_vnodes(const struct sd_node *n, struct rb_root *vroot)
13 {
14     uint64_t hval = sd_hash(&n->nid, offsetof(typeof(n->nid),
15                           io_addr));
16 
17     for (int i = 0; i < n->nr_vnodes; i++) {
18         struct sd_vnode *v = xmalloc(sizeof(*v));
19 
20         hval = sd_hash_next(hval);
21         v->hash = hval;
22         v->node = n;
23         if (unlikely(rb_insert(vroot, v, rb, vnode_cmp)))
24             panic("vdisk hash collison");
25     }
26 }
27 
28 static inline void
29 nodes_to_vnodes(struct rb_root *nroot, struct rb_root *vroot)
30 {
31     struct sd_node *n;
32 
33     rb_for_each_entry(n, nroot, rb)
34         node_to_vnodes(n, vroot);
35 }

 


參考資料:

1. 分佈式存儲系統sheepdog 

2. 分佈式系統sheepdog之sheep啓動流程

3. centos7下sheepdog的簡單使用

相關文章
相關標籤/搜索