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的简单使用

posted @ 2019-04-02 13:49  yunlion  阅读(2620)  评论(0编辑  收藏  举报