sheepdog調研學習


1. 基本介紹

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

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

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

Sheep進程之間通過節點路由(gateway)的邏輯轉發請求,而具體的對象通過對象存儲的邏輯保存在各個節點上,這就把所有節點上的存儲空間聚合起來,形成一個共享的存儲空間。

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

Dog是一個命令行工具,啟動時,會向后台sheep進程發起TCP連接,通過連接傳輸控制指令。當sheep收到控制指令時,如果有需要,會將相應指令擴散到集群中,加上對稱式的設計,從而使得dog能夠管理整個集群

 

2. 基本架構

  • 由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啟動

啟動過程中會有一些初始化的工作,對於基本目錄的初始化,對於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,之后相應的句柄函數進行相應處理。

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

 

4. dog啟動流程

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

1、init_commands(&commands)函數將dog支持的命令都初始化在commands中進行調用,包括對vdi、cluster、node的命令操作,
2、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的簡單使用


免責聲明!

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



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