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
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塊驅動
首先QEMU塊驅動經過getway的bdrv_open()從對象存儲讀取vdi
塊驅動經過請求的部分偏移量和大小計算數據對象id, 並向getway發送請求. 當塊驅動發送寫請求到那些不屬於其當前vdi的數據對象是,塊驅動發送CoW請求分配一個新的數據對象.
咱們能夠把快照VDI附加到QEMU, 當塊驅動第一次發送寫請求到快照VDI, 塊驅動建立一個新的可寫VDI做爲子快照,併發送請求到新的VDI.
當查找VDI對象時:
1) 經過求vdi名的哈希值獲得vdi id
2) 經過vdi id計算di對象
3) 發送讀請求到vdi對象
4) 若是此vdi不是請求的那個,增長vdi id並重試發送讀請求
快照可克隆操做很簡單,
1) 讀目標VDI
2) 建立一個與目標同樣的新VDI
3) 把新vdi的‘'parent_vdi_id''設爲目標VDI的id
4) 設置目標vdi的''child_vdi_id''爲新vdi的id.
5) 設置目標vdi的''snap_ctime''爲當前時間, 新vdi變爲當前vdi對象
TODO:當前,回收未使用的數據對象是不會被執行,直到全部相關VDI對象(相關的快照VDI和克隆VDI)被刪除.
全部相關VDI被刪除後, Sheepdog刪除全部此VDI的數據對象,設置此VDI對象名爲空字符串.
Sheepdog把成員節點歷史存儲在存儲路徑, 路徑名以下:
/store_dir/epoch/[epoch number]
每一個文件包括節點在epoch的列表信息(IP地址,端口,虛擬節點個數).
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路徑.
若是QEMU發送I/O請求到某些未恢復的對象, Sheepdog阻塞此請求並優先恢復對象.
Sheepdog的全部請求包含固定大小的頭部(48位)和固定大小的數據部分,頭部包括協議版本,操做碼,epoch號,數據長度等.
操做碼 |
描述 |
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 |
操做碼 |
描述 |
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 |
操做碼 |
描述 |
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 }
參考資料: