美文网首页Ceph
ceph rbd:总览

ceph rbd:总览

作者: chnmagnus | 来源:发表于2018-02-06 16:10 被阅读35次

    基本原理

    整体概念:
    官方文档:CEPH BLOCK DEVICE
    rbd总体架构和原理:《ceph设计原理与实现》第六章
    rbd快照和克隆补充:《ceph源码分析》第九章

    其他一些不错的资料:
    ceph rbd快照原理解析
    理解 OpenStack + Ceph (3):Ceph RBD 接口和工具 [Ceph RBD API and Tools]
    理解 OpenStack + Ceph (4):Ceph 的基础数据结构 [Pool, Image, Snapshot, Clone]

    对外接口

    rbd使用方法有两种:

    1. 通过librbd,通过函数接口来操作。
    2. 通过kernel module,走kernel 的路径,使用时类似于普通块设备,可以mkfs、mount。

    kernel module的方式可以参考相关文档
    目前应用比较广泛的是librbd的方式,接入openstack、iscsi等都是使用的这种方式。下面对librbd进行介绍。

    librbd

    librbd大部分操作通过librados来实现,部分元数据相关的操作通过cls模块直接注册在osd上。

    librbd使用方法

    作为一个对外接口库,librbd默认支持c和cpp,其对应的头文件在src/include/rbd下,针对c和cpp分别是librdb.h和librbd.hpp。具体的实现在src/rbd目录下。cls相关的代码在cls/rbd目录下,具体见附录A。

    src/include/rbd/librbd.hpp中最主要的两个类是class CEPH_RBD_API RBDclass CEPH_RBD_API Image。RBD 主要负责创建、删除、克隆镜像等操作, 而Image 类负责镜像的读写,以及快照相关的操作等等。

    要使用librbd,需要先安装下面两个包。可以通过yum安装,也可以通过下载ceph源码编译后,通过make install进行安装。

    $  yum list | grep librbd
    librbd1.x86_64                          1:0.80.7-3.el7                 base     
    librbd1-devel.x86_64                    1:0.80.7-3.el7                 base     
    

    至于如何使用librbd来编程,请参考下面的代码,这是使用librbd的一般流程。
    编译时记得加上链接参数:g++ librbdtest.cpp -lrados -lrbd
    更多函数的使用请参考 librbd.hpp。另外 这里 有一些不错的示例。

    #include <rbd/librbd.hpp>
    #include <rados/librados.hpp>
    
    #include <cstring>
    #include <iostream>
    #include <string>
    
    void err_msg(int ret, const std::string &msg = ""){
        std::cerr<< "[error] msg:" << msg << " strerror: " << strerror(-ret) <<  std::endl;
    }
    void err_exit(int ret, const std::string &msg = ""){
        err_msg(ret, msg);
        exit(EXIT_FAILURE);
    }
    
    int main(int argc, char* argv[]) {
        int ret = 0;
        // rados
        librados::Rados rados;
    
        // use client.admin keyring
        ret = rados.init("admin");
        if (ret < 0)
            err_exit(ret,"failed to initialize rados");
        // read ceph.conf 
        ret = rados.conf_read_file("/path/to/ceph.conf");
        if (ret < 0)
            err_exit(ret, "failed to parse ceph.conf");
        // connect to cluster
        ret = rados.connect();
        if (ret < 0)
            err_exit(ret, "failed to connect to rados cluster");
    
        std::string pool_name = "rbd";
        librados::IoCtx io_ctx;
        
        ret = rados.ioctx_create(pool_name.c_str(), io_ctx);
        if (ret < 0) {
            rados.shutdown();
            err_exit(ret, "failed to create ioctx");
        }
        
        // rbd
        librbd::RBD rbd;
        
        std::string image_name = "image1";
        librbd::Image image;
        // open image
        ret = rbd.open(io_ctx, image, image_name.c_str());
        if (ret < 0) {
            io_ctx.close();
            rados.shutdown();
            err_exit(ret, "failed to open rbd image");
        }
        
        // now, you can operate image
        
        // check the image info
        librbd::image_info_t info;
        ret = image.stat(info, sizeof info);
        if (ret < 0) {
            err_msg(ret, "get image stat failed");
        } else {
            std::cout << "info.size:" << info.size << std::endl;
            std::cout << "info.obj_size:" << info.obj_size << std::endl;
            std::cout << "info.num_objs:" << info.num_objs << std::endl;
            std::cout << "info.order:" << info.order << std::endl;
            std::cout << "info.block_name_prefix:" << info.block_name_prefix << std::endl;
        }
    
    
    done:
        image.close();
        io_ctx.close();
        rados.shutdown();
        exit(EXIT_SUCCESS);
    }
    
    

    librbd代码小窥

    这里只是librbd最简单的代码流程,只是为了告知你各个功能的实现函数在哪,至于执行这个功能所使用的异步机制等过程没有提及。

    让我们通过一些函数来看一下librbd的代码流程。rbd是基于rados实现的,但rados中并没有rbd的逻辑,可以说,librbd就是rbd的完整实现,rados只是作为存储。

    另外,在librbd中,使用了一种独特的代码风格。操作对外提供的api在class RBD和class Image中,但这些函数的实现仅仅是一些参数的解析和传递,其最终的功能实现,都会封装到一个名为<operation>Request的类中,这个类往往包含完成该操作所必须的数据和功能。在类的注释中还会包含该操作执行逻辑的流程图,或者状态图。

    所以当你需要寻找某个功能的实现代码时,直接寻找以这个功能命名的<operation>Request类吧。

    RBD::create为例:
    1) 首先在librbd.hpp/librbd.cc中,定义了对外的create函数接口。其函数实现,简单调用了internal.h/internal.cc中定义的librbd::create函数。

    2)librbd::create函数做的主要工作就是准备create需要的各种参数,准备create操作可能用到的工具(线程池等),然后将这些数据封装到image::CreateRequest对象中。最后调用对象的send()函数开始执行流程。最后通过cond.wait()来等待操作的完成。代码如下:

      int create(IoCtx& io_ctx, const std::string &image_name,
             const std::string &image_id, uint64_t size,
             ImageOptions& opts,
                 const std::string &non_primary_global_image_id,
                 const std::string &primary_mirror_uuid,
                 bool skip_mirror_enable)
      {
        // 此处省略代码内容:
        // 根据参数准备image id、order、format等属性,没有则设默认值
        ...
        if (old_format) {
          // 如果是旧版的format,调用format v1的create函数,现在很少使用
          r = create_v1(io_ctx, image_name.c_str(), size, order);
        } else {
          // 从ceph ctx中获得全局的线程池和队列
          ThreadPool *thread_pool;
          ContextWQ *op_work_queue;
          ImageCtx::get_thread_pool_instance(cct, &thread_pool, &op_work_queue);
    
          C_SaferCond cond;
          // 创建image::CreateRequest对象,
          // 其实就是new 了一个image::CreateRequest对象。
          image::CreateRequest<> *req = image::CreateRequest<>::create(
            io_ctx, image_name, id, size, opts, non_primary_global_image_id,
            primary_mirror_uuid, skip_mirror_enable, op_work_queue, &cond);
          req->send();
          // 等待创建请求完成
          r = cond.wait();
        }
    
        int r1 = opts.set(RBD_IMAGE_OPTION_ORDER, order);
        assert(r1 == 0);
    
        return r;
      }
    

    3)image::CreateRequest对象中给出的流程图,或者说状态图。这幅图描述了send()函数执行后的逻辑,其中的状态转移是通过if判断和函数调用来实现的。

    Image在rados中的创建过程如下:

    • 创建一个rbd_id.<name>对象,映射image name到image id。
    • 增加name_<name>->image idid_<id>->name的映射到rbd_directorty对象的omap。
    • 创建rbd_header.<id>对象,在其omap和xattr中记录该image的metadata。
    • 如果开启了object map特性,创建rbd_object_map.<id>对象,记录该image所有data object的情况
    • 数据对象不会被创建,直到有数据写入
      /**
       * @verbatim
       *
       *                                  <start> . . . . > . . . . .
       *                                     |                      .
       *                                     v                      .
       *                               VALIDATE POOL                v (pool validation
       *                                     |                      .  disabled)
       *                                     v                      .
       *                             VALIDATE OVERWRITE             .
       *                                     |                      .
       *                                     v                      .
       * (error: bottom up)           CREATE ID OBJECT. . < . . . . .
       *  _______<_______                    |
       * |               |                   v
       * |               |          ADD IMAGE TO DIRECTORY
       * |               |               /   |
       * |      REMOVE ID OBJECT<-------/    v
       * |               |           NEGOTIATE FEATURES (when using default features)
       * |               |                   |
       * |               |                   v         (stripingv2 disabled)
       * |               |              CREATE IMAGE. . . . > . . . .
       * v               |               /   |                      .
       * |      REMOVE FROM DIR<--------/    v                      .
       * |               |          SET STRIPE UNIT COUNT           .
       * |               |               /   |  \ . . . . . > . . . .
       * |      REMOVE HEADER OBJ<------/    v                     /. (object-map
       * |               |\           OBJECT MAP RESIZE . . < . . * v  disabled)
       * |               | \              /  |  \ . . . . . > . . . .
       * |               |  *<-----------/   v                     /. (journaling
       * |               |             FETCH MIRROR MODE. . < . . * v  disabled)
       * |               |                /   |                     .
       * |     REMOVE OBJECT MAP<--------/    v                     .
       * |               |\             JOURNAL CREATE              .
       * |               | \               /  |                     .
       * v               |  *<------------/   v                     .
       * |               |           MIRROR IMAGE ENABLE            .
       * |               |                /   |                     .
       * |        JOURNAL REMOVE*<-------/    |                     .
       * |                                    v                     .
       * |_____________>___________________<finish> . . . . < . . . .
       *
       * @endverbatim
       */
    

    C_InvokeAsyncRequest相关的流程之后补充。

    附录A cls/rbd介绍

    元数据相关的操作,通过cls注册到osd上。

    cls是ceph的一个模块扩展,用户可以自定义对象的接口的实现方法,通过动态链接的形式加入osd中,在osd上直接执行。在此不做展开,具体可以参考这里

    这部分rbd代码主要包含两部分,cls_rbd.h/cccls_rbd_client.h/cc。类似于服务端和客户端的关系,前者定义了具体在osd上执行的函数,后者在客户端执行,将函数参数封装后发送给服务端(osd),然后在osd上执行。

    snapshot_add函数为例,该函数主要负责在rbd_header对象中增加新的snapshot元数据信息:

    • cls_rbd.cc函数中,对函数进行定义和注册。下面的代码注册了rbd模块,以及snapshot_add函数。
      cls_register("rbd", &h_class);
      cls_register_cxx_method(h_class, "snapshot_add",
                  CLS_METHOD_RD | CLS_METHOD_WR,
                  snapshot_add, &h_snapshot_add);
    
    • cls_rbd_client.h/cc定义了通过客户端访问osd注册的cls函数的方法。以snapshot_add函数为例,这个函数将参数封装进bufferlist,通过ioctx->exec方法,把操作发送给osd处理。
        void snapshot_add(librados::ObjectWriteOperation *op, snapid_t snap_id,
                  const std::string &snap_name, const cls::rbd::SnapshotNamespace &snap_namespace)
        {
          bufferlist bl;
          ::encode(snap_name, bl);
          ::encode(snap_id, bl);
          ::encode(cls::rbd::SnapshotNamespaceOnDisk(snap_namespace), bl);
          op->exec("rbd", "snapshot_add", bl);
        }
    
    • cls_rbd.cc定义了方法在服务端的实现,其一般流程是:从bufferlist将客户端传入的参数解析出来,调用对应的方法实现,然后将结果返回客户端。
    /**
     * Adds a snapshot to an rbd header. Ensures the id and name are unique.
     */
    int snapshot_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
    {
      bufferlist snap_namebl, snap_idbl;
      cls_rbd_snap snap_meta;
      uint64_t snap_limit;
      // 从bl中解析参数
      try {
        bufferlist::iterator iter = in->begin();
        ::decode(snap_meta.name, iter);
        ::decode(snap_meta.id, iter);
        if (!iter.end()) {
          ::decode(snap_meta.snapshot_namespace, iter);
        }
      } catch (const buffer::error &err) {
        return -EINVAL;
      }
      // 判断参数合法性,略
      ......
      // 完成操作,在rbd_header对象中增加新的snapshot元数据,并更新sanp_seq。
      map<string, bufferlist> vals;
      vals["snap_seq"] = snap_seqbl;
      vals[snapshot_key] = snap_metabl;
      r = cls_cxx_map_set_vals(hctx, &vals);
      if (r < 0) {
        CLS_ERR("error writing snapshot metadata: %s", cpp_strerror(r).c_str());
        return r;
      }
      return 0;
    }
    

    相关文章

      网友评论

        本文标题:ceph rbd:总览

        本文链接:https://www.haomeiwen.com/subject/ogbgzxtx.html