Ceph学习——Librbd块存储库与RBD读写流程源码分析

Librbd是Ceph提供块存储的库,它实现了RBD接口,基于LIbrados实现了对RBD的基本操作。Librbd对于元数据的相关操作是通过cls_rbd实现的。cls_rbd是Cls的一个扩展模块,Cls允许用户自定义对象的操作接口和实现方法,为用户提供了一种比较直接的接口扩展方式。Librbd的数据读写相关操作则是通过直接Librados来直接访问。

Librbd 包含了rbd的相关操作,并发送给ImageRequestWQ类处理(队列),而后该类将其中的的请求发送给ImageRequest处理,ImageRequest将Image进行分片(将一个 块 分解成 对象 进行处理,Ceph的底层本质还是对象存储) 等操作后,将各个对象调用ObjectRequest类进行处理 ,每个ObjectRequest请求分别处理。ImageRequest和ObjectRequest下面都包含很多相关操作的子类,用子类实现了具体的接口。关于RBD的读写流程用了函数调用的流程图表示,思路更加清晰,并且最新版本函数、类的名字都改变了。
结构如图:


这里写图片描述

Cls——Ceph的扩展模块

cls_rbd是Cls的一个扩展模块,Cls允许用户自定义对象的操作接口和实现方法,为用户提供了一种比较直接的接口扩展方式。
该模块下包含几个文件:


这里写图片描述

1)cls_rbd_types 主要是注册了rbd模块,以及自定义的一些方法

...
...
...
//对应的方法
int create(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{
  string object_prefix;
  uint64_t features, size;
  uint8_t order;
  int64_t data_pool_id = -1;

  try {
    bufferlist::iterator iter = in->begin();
    ::decode(size, iter);
    ::decode(order, iter);
    ::decode(features, iter);
    ::decode(object_prefix, iter);
    if (!iter.end()) {
      ::decode(data_pool_id, iter);
    }
  } catch (const buffer::error &err) {
    return -EINVAL;
  }
  ...
  ...
  ...
  //注册模块以及方法
  cls_register("rbd", &h_class);
  cls_register_cxx_method(h_class, "create",
              CLS_METHOD_RD | CLS_METHOD_WR,
              create, &h_create);
 ...
 ...
 ...

2)cls_rbd_client 定义了客户端访问rbd函数。调用模块和客户端的读写操作流程类似,都是通过ioctx->ecex 函数,封装操作并发送给对应的OSD处理。见( 超链接留空 )

Librbd 库

RBD是ceph中提供块存储的客户端服务,之所以说是客户端服务是相对于RADOS而言,RBD是基于librados API开发的一个块存储服务。Ceph底层的实现是对象存储,通过librbd实现了块存储的接口,对外提供块存储的服务。
所以,块存储在客户端也会被拆成n个对象来存储处理。

下面摘取
麦子迈:解析Ceph: Librbd–块存储库 中的基础名词介绍。

  • Image: 对应于LVM的Logical Volume,是能被attach/detach到VM的载体。在RBD中,Image的数据有多个Object组成。(image 镜像 可以作是块存储的呈现形式)

  • Snapshot: Image的某一个特定时刻的状态,只能读不能写但是可以将Image回滚到某一个Snapshot状态。Snapshot必定属于某一个Image。

  • Clone: 为Image的某一个Snapshot的状态复制变成一个Image。如ImageA有一个Snapshot-1,clone是根据ImageA的Snapshot-1克隆得到ImageB。ImageB此时的状态与Snapshot-1完全一致,区别在于ImageB此时可写,并且拥有Image的相应能力。

关于 块存储 原理的一些东西,可以操作集群 实际看看:Ceph介绍之RBD实现原理

RBD元数据

当在一个pool里面创建一个RBD对象的时候,会对应的生成其它的对象以及相关的元数据用于管理rbd。

  • rbd _dircetory对象:该对象在每一个Pool都存储在,用来保存该pool下的所有的RBD的目录信息。当创建RBD设备的时候,会首先检查该对象的存在,如无则创建。该对象中包含了 Ceph中专门用于存储元数据 的结构omap。该omap属性里保存了所有RBD设备的名字和ID信息。在omap属性中 保存这key-value键值对,key为 “name”+设备名字,value为”_id“+RBD的ID 。
  • 另外还会创建id_obj对象:对象名字为 rbd_id. ,内容为RBD的ID信息。
  • head_obj对象:其中的omap保存了RBD相关的元数据。
  • rbd_object_map. :保存其对象和父image对象的映射信息。

另外还会创建数据对象 ,用于存储数据,因为Ceph存储的本质就对象存储,所以即使提供的块存储也要拆分成对象来管理。

源码目录简单介绍

librbd 模块下的源代码现在文件比较多。根据网上别人的总结。。。。
http://blog.csdn.net/scaleqiao/article/details/51165598

1)operation, 这个目录里实现了关于image的主要操作,包括flatten、resize、trim、snapshot create、snapshot remove、snapshot protect、snapshot unprotect、snapshot rename、snapshot rollback

2)librbd.cc,这个文件里并没有具体实现所有的接口,而其实大部分接口的具体实现是在internal.cc,除了I/O相关的接口,read/write。

3) ImageCtx.h和ImageCtx.cc,定义了image的上下文,包括image layout的初始化、卷/snap相关元数据的获取接口以及后来引入的rbd mirror的相关元数据结构(object map、journal 等)的创建。

4)AioImageRequestWQ.h/AioImageRequestWQ.cc、AioImageRequest.h/AioImageRequest.cc,实现了aio相关的I/O接口,进入rbd的read请求会转变成一个AioImageRead的实例,而write请求会变成一个AioImageWrite的对象。而这些对象最终会分别调到AioImageRead.send_request()和AbstractAioImageWrite::send_request()这两个函数,来处理IO请求(其中主路径中关于journal的部分可以先略过去)。

5) AioObjectRequest.h/AioObjectRequest.cc,I/O请求会通过Striper::file_to_extents()被影射成针对某个/某些对象的操作,这些操作都被定义/实现在这两个文件中

6) AioCompletion.h/AioCompletion.cc,这两个文件实现了Aio的回调处理逻辑。上层应用可以通过rbd_aio_create_completion()创建一个AioCompletion的对象,并传入自己的回调函数。这样当具体操作结束之后,AioCompletion对象会调用上层应用的回调函数

7) LibrbdWriteback.h/LibrbdWriteback.cc,这两个文件主要是通过现有的ObjectCacher机制,在librbd中支持Writeback这种I/O方式

8) ImageWatcher.h/ImageWatcher.cc,这是后来引入的watch/notify机制,用于增加了管理image exlusive lock的支持

9) 日志 Journal*,这些文件主要实现rbd mirror的功能

10)CopyupRequest.h/CopyupRequest.cc,这个主要用于处理clone卷的逻辑

看完思路清晰不少。但是目前最新的版本而言,改动了一些,主要是名字的AIo都没了。 但是跳转的处理逻辑依然还是差不多 集中在 ImageRequestWQ、ImageRequest、ObjectRequest之中。

RBD的创建

rbd有很多个创建函数。根据传入的参数不同来调用不同的函数。其过程比较简单,都是调用librbd::create来设置相应的参数。RBD的创建过程,其本质是调用Cls的RBD模块设置相关的元数据信息。相关的元数据的操作函数也一样。

 int RBD::create(IoCtx& io_ctx, const char *name, uint64_t size, int *order)
  {
    TracepointProvider::initialize<tracepoint_traits>(get_cct(io_ctx));
    tracepoint(librbd, create_enter, io_ctx.get_pool_name().c_str(), io_ctx.get_id(), name, size, *order);
    int r = librbd::create(io_ctx, name, size, order);//设置参数
    tracepoint(librbd, create_exit, r, *order);
    return r;
  }

 ...
 ...
 ...

  int RBD::create4(IoCtx& io_ctx, const char *name, uint64_t size,
           ImageOptions& opts)
  {
    TracepointProvider::initialize<tracepoint_traits>(get_cct(io_ctx));
    tracepoint(librbd, create4_enter, io_ctx.get_pool_name().c_str(), io_ctx.get_id(), name, size, opts.opts);
    int r = librbd::create(io_ctx, name, "", size, opts, "", "", false);//设置参数
    tracepoint(librbd, create4_exit, r);
    return r;
  }

RBD 数据读写流程

在Imagectx中处理了Image的上下文自信,每一个Image中都有一个AioImageRequestWQ ,用于处理Image的操作,他是一个工作队列,通过线程池来实现异步请求处理。


这里写图片描述

RBD中包含几个数据操作的关键的类,其中 ObjectRequest用于对某一个对象的处理的基类,而ImageRequest则是处理Image的基类。图中可以看到他们都有不少子类,其中 AbstractImageWrite下面还有 imageWrite、ImageDiscard等子类,图中为画出。而 ImageRequest和ObjectRequest的之间的联系 是在当一个Image的操作跨越了多个对象的时候(一个块会被拆成多个对象进行操作,Ceph本质是对象存储),在每个对象中产生一个ObjectRequest 请求,用于在各自的对象中各自处理。


这里写图片描述

根据该函数调用关系图,一一展开看卡各个函数,其中用写流程来看,读流程操作类似。

1) rbd_aio_write 处于Librbd里面,librbd 是对外提供rbd接口的库,所以其中包含了较多的rbd请求。rbd的请求都是从这里开始的。

extern "C" int rbd_aio_write(rbd_image_t image, uint64_t off, size_t len,
                 const char *buf, rbd_completion_t c)
{
  librbd::ImageCtx *ictx = (librbd::ImageCtx *)image;
  librbd::RBD::AioCompletion *comp = (librbd::RBD::AioCompletion *)c;
  tracepoint(librbd, aio_write_enter, ictx, ictx->name.c_str(), ictx->snap_name.c_str(), ictx->read_only, off, len, buf, comp->pc);

  bufferlist bl;
  bl.push_back(create_write_raw(ictx, buf, len));
  //调用ImageRequestWQ<I>::aio_write  来生成ImageRequest对象
  ictx->io_work_queue->aio_write(get_aio_completion(comp), off, len,
                                 std::move(bl), 0);
  tracepoint(librbd, aio_write_exit, 0);
  return 0;
}

2) ImageRequestWQ::aio_write 处于 ImageRequestWQ中,

template <typename I>
void ImageRequestWQ<I>::aio_write(AioCompletion *c, uint64_t off, uint64_t len,
                  bufferlist &&bl, int op_flags,
                  bool native_async) {
  CephContext *cct = m_image_ctx.cct;
  ZTracer::Trace trace;
  if (m_image_ctx.blkin_trace_all) {
    trace.init("wq: write", &m_image_ctx.trace_endpoint);
    trace.event("init");
  }

  c->init_time(util::get_image_ctx(&m_image_ctx), AIO_TYPE_WRITE);
  ldout(cct, 20) << "ictx=" << &m_image_ctx << ", "
                 << "completion=" << c << ", off=" << off << ", "
                 << "len=" << len << ", flags=" << op_flags << dendl;

  if (native_async && m_image_ctx.event_socket.is_valid()) {
    c->set_event_notify(true);
  }

  if (!start_in_flight_io(c)) {
    return;
  }

  RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
  if (m_image_ctx.non_blocking_aio || writes_blocked()) {
  //将写请求 的 ImageRequest 加入队列中
    queue(ImageRequest<I>::create_write_request(
            m_image_ctx, c, {{off, len}}, std::move(bl), op_flags, trace));
  } else {
    c->start_op();
    //直接调用ImageRequest<I>::aio_write处理写请求
    ImageRequest<I>::aio_write(&m_image_ctx, c, {{off, len}},
                   std::move(bl), op_flags, trace);
    finish_in_flight_io();
  }
  trace.event("finish");
}

3)在2)中加入队列里面的额请求,都会用process来进行处理,最后调用ImageRequest::send 函数来处理

template <typename I>
void ImageRequestWQ<I>::process(ImageRequest<I> *req) {
  CephContext *cct = m_image_ctx.cct;
  ldout(cct, 20) << "ictx=" << &m_image_ctx << ", "
                 << "req=" << req << dendl;

  req->send();//调用处理

  finish_queued_io(req);
  if (req->is_write_op()) {
    finish_in_flight_write();
  }
  delete req;

  finish_in_flight_io();
}

4) 对于2)中直接调用该函数处理的,最后也是直接调用ImageWriteRequest::send()处理

template <typename I>
void ImageRequest<I>::aio_write(I *ictx, AioCompletion *c,
                                Extents &&image_extents, bufferlist &&bl,
                                int op_flags,
                const ZTracer::Trace &parent_trace) {
  ImageWriteRequest<I> req(*ictx, c, std::move(image_extents), std::move(bl),
                           op_flags, parent_trace);
  req.send();//处理
}

5)类 ImageWriteRequest 继承自 AbstractImageWriteRequest 所以最后调用的是,AbstractImageWriteRequest的send_request()函数。并且,后面调用的函数都类似,都是在ImageWriteRequest类的函数中调用AbstractImageWriteRequest对应的函数。因此直接看AbstractImageWriteReques对应的函数。

ImageRequest.cc

template <typename I>
void AbstractImageWriteRequest<I>::send_request() {
  I &image_ctx = this->m_image_ctx;
  CephContext *cct = image_ctx.cct;

  RWLock::RLocker md_locker(image_ctx.md_lock);

  bool journaling = false;

  AioCompletion *aio_comp = this->m_aio_comp;
  uint64_t clip_len = 0;
  ObjectExtents object_extents;
  ::SnapContext snapc;
  {
    // prevent image size from changing between computing clip and recording
    // pending async operation
    RWLock::RLocker snap_locker(image_ctx.snap_lock);
    if (image_ctx.snap_id != CEPH_NOSNAP || image_ctx.read_only) {
      aio_comp->fail(-EROFS);
      return;
    }

    for (auto &extent : this->m_image_extents) {
      if (extent.second == 0) {
        continue;
      }

      // map to object extents
      //!!!!!!!将image 分片,块->对象的映射(块存储 的image 依然要用对象存储来处理)
      Striper::file_to_extents(cct, image_ctx.format_string, &image_ctx.layout,
                               extent.first, extent.second, 0, object_extents);
      clip_len += extent.second;
    }

    snapc = image_ctx.snapc;
    journaling = (image_ctx.journal != nullptr &&
                  image_ctx.journal->is_journal_appending());
  }

  int ret = prune_object_extents(object_extents);
  if (ret < 0) {
    aio_comp->fail(ret);
    return;
  }

  if (!object_extents.empty()) {
    uint64_t journal_tid = 0;
    aio_comp->set_request_count(
      object_extents.size() + get_object_cache_request_count(journaling));
      //将分成的对象 分别生成 ObjectRequests 对象 调用send_object_requests 各个对象各自处理
    ObjectRequests requests;
    send_object_requests(object_extents, snapc,
                         (journaling ? &requests : nullptr));

    if (journaling) {
      // in-flight ops are flushed prior to closing the journal
      assert(image_ctx.journal != NULL);
      journal_tid = append_journal_event(requests, m_synchronous);
    }

    if (image_ctx.object_cacher != NULL) {
      send_object_cache_requests(object_extents, journal_tid);
    }
  } else {
    // no IO to perform -- fire completion
    aio_comp->unblock();
  }

  update_stats(clip_len);
  aio_comp->put();
}

6) AbstractImageWriteRequest::send_object_requests 用于发送请求,如果 journaling 则 将请求加入队列中 否则直接 调用AbstractObjectWriteRequest::send() 发送请求。

template <typename I>
void AbstractImageWriteRequest<I>::send_object_requests(
    const ObjectExtents &object_extents, const ::SnapContext &snapc,
    ObjectRequests *object_requests) {
  I &image_ctx = this->m_image_ctx;
  CephContext *cct = image_ctx.cct;

  AioCompletion *aio_comp = this->m_aio_comp;
  for (ObjectExtents::const_iterator p = object_extents.begin();
       p != object_extents.end(); ++p) {
    ldout(cct, 20) << "oid " << p->oid << " " << p->offset << "~" << p->length
                   << " from " << p->buffer_extents << dendl;
    C_AioRequest *req_comp = new C_AioRequest(aio_comp);
    //ObjectRequestHandle是ObjectRequest的父类
    ObjectRequestHandle *request = create_object_request(*p, snapc,
                                                            req_comp);

    // if journaling, stash the request for later; otherwise send
    //如果 journaling 则 将请求加入队列中 否则直接 调用AbstractObjectWriteRequest<I>::send() 
    if (request != NULL) {
      if (object_requests != NULL) {
        object_requests->push_back(request);
      } else {
        request->send();
      }
    }
  }
}

7)AbstractObjectWriteRequest::send()先处理object_map相关 ,然后调用AbstractObjectWriteRequest<I>::pre_write_object_map_update(),然后最终调用AbstractObjectWriteRequest<I>::write_object()发送请求,将请求发送到librados处理。

ObjectRequest.cc

template <typename I>
void AbstractObjectWriteRequest<I>::send() {
  I *image_ctx = this->m_ictx;
  ldout(image_ctx->cct, 20) << this->get_op_type() << " " << this->m_oid << " "
                            << this->m_object_off << "~" << this->m_object_len
                            << dendl;
  {
    RWLock::RLocker snap_lock(image_ctx->snap_lock);
    if (image_ctx->object_map == nullptr) {
      m_object_may_exist = true;
    } else {
      // should have been flushed prior to releasing lock
      assert(image_ctx->exclusive_lock->is_lock_owner());
      m_object_may_exist = image_ctx->object_map->object_may_exist(
        this->m_object_no);
    }
  }

  if (!m_object_may_exist && is_no_op_for_nonexistent_object()) {
    ldout(image_ctx->cct, 20) << "skipping no-op on nonexistent object"
                              << dendl;
    this->async_finish(0);
    return;
  }
  //最终调用 该函数
  pre_write_object_map_update();
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
template <typename I>
void AbstractObjectWriteRequest<I>::pre_write_object_map_update() {
  I *image_ctx = this->m_ictx;

  image_ctx->snap_lock.get_read();
  if (image_ctx->object_map == nullptr || !is_object_map_update_enabled()) {
    image_ctx->snap_lock.put_read();
    write_object();
    return;
  }

  if (!m_object_may_exist && m_copyup_enabled) {
    // optimization: copyup required
    image_ctx->snap_lock.put_read();
    copyup();
    return;
  }

  uint8_t new_state = this->get_pre_write_object_map_state();
  ldout(image_ctx->cct, 20) << this->m_oid << " " << this->m_object_off
                            << "~" << this->m_object_len << dendl;

  image_ctx->object_map_lock.get_write();
  if (image_ctx->object_map->template aio_update<
        AbstractObjectWriteRequest<I>,
        &AbstractObjectWriteRequest<I>::handle_pre_write_object_map_update>(
          CEPH_NOSNAP, this->m_object_no, new_state, {}, this->m_trace, this)) {
    image_ctx->object_map_lock.put_write();
    image_ctx->snap_lock.put_read();
    return;
  }

  image_ctx->object_map_lock.put_write();
  image_ctx->snap_lock.put_read();
  write_object();
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
template <typename I>
void AbstractObjectWriteRequest<I>::handle_pre_write_object_map_update(int r) {
  I *image_ctx = this->m_ictx;
  ldout(image_ctx->cct, 20) << "r=" << r << dendl;

  assert(r == 0);
  write_object();
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
template <typename I>
void AbstractObjectWriteRequest<I>::write_object() {
  I *image_ctx = this->m_ictx;
  ldout(image_ctx->cct, 20) << dendl;

  librados::ObjectWriteOperation write;
  if (m_copyup_enabled) {
    ldout(image_ctx->cct, 20) << "guarding write" << dendl;
    write.assert_exists();
  }

  add_write_hint(&write);
  add_write_ops(&write);
  assert(write.size() != 0);

  librados::AioCompletion *rados_completion = util::create_rados_callback<
    AbstractObjectWriteRequest<I>,
    &AbstractObjectWriteRequest<I>::handle_write_object>(this);
  int r = image_ctx->data_ctx.aio_operate(
    this->m_oid, rados_completion, &write, m_snap_seq, m_snaps,
    (this->m_trace.valid() ? this->m_trace.get_info() : nullptr));
  assert(r == 0);
  rados_completion->release();
}

猜你喜欢

转载自blog.csdn.net/csnd_pan/article/details/78728743