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

  • 相关阅读:
    C# post请求 HttpWebRequest
    C# 获取当前路径
    计算n的阶乘
    查找2-n之间素数的个数
    循环嵌套-打印不定长特殊*号图形
    流程控制-物流费用计算(嵌套if)
    基于Arduino的按键控制LED实验
    基于Arduino的红外遥控
    PS/2的相关知识
    Arduino_URO端口与AtMega328p引脚对应图
  • 原文地址:https://www.cnblogs.com/yunlion/p/10642196.html
Copyright © 2020-2023  润新知