ceph源码阅读之AioCompletion回调机制分析


 

Aio即Async IO,AioCompletion即Async Io Completion,也就是Async IO完成时的回调处理制作,librados设计AioCompletion就是为了提供一种机制对Aio完成时结果码的处理。而处理函数则由使用者来实现。

设计

相关类设计


librados设计了两个类:AioCompletion、AioCompletionImpl
类的关系图如下:

 


从上面可以看出,AioCompletion是librados设计开放的库API,真正的设计逻辑AioCompletionImpl中。

IoctxImpl
该类是pool的上下文信息,一个pool对应一个IoctxImpl对象。librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。它的处理过程如下:
1)把请求封装成ObjectOperation 类(osdc 中的)
2)把相关的pool信息添加到里面,封装成Objecter::Op对像
3)调用相应的函数 objecter- >op_submit 发送给相应的OSD
4)操作完成后,调用相应的回调函数。

AioCompletionImpl
Aio即Async IO,AioCompletion即Async Io Completion,也就是Async IO完成时的回调处理制作,librados设计AioCompletion就是为了提供一种机制对Aio完成时结果码的处理。而处理函数则由使用者来实现。AioCompletion是librados设计开放的库API,真正的设计逻辑在AioCompletionImpl中。

对于AIoCompletion实例的使用都是引用的pc,即AioCompletionImpl,因此具体来说应该是如何包装AioCompletionImpl。这里提一下,librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。而AioCompletionImpl是IO操作的回调,因为对于AioCompletionImpl的包装设计在IoCtxImpl模块中

回调的设计


AioCompletionImpl设计了两种类型的回调,分别用两组成员来表示

(1)callback_complete callback_complete_arg

         普通的回调处理函数和参数

(2)callback_safe callback_safe_arg

          跟上面的区别就在于在设置和执行回调函数的时候,需要加锁/解锁

同步设计


通过使用线程条件允许使用者阻塞(进入线程条件等待)来等待Aio完成,一般都是执行回调函数之后,发送线程条件信号唤醒阻塞线程。

使用


获取AioCompletion实例


librados开放的创建AioCompletion实例API有两个

librados::AioCompletion *librados::Rados::aio_create_completion()
{
  AioCompletionImpl *c = new AioCompletionImpl;
  return new AioCompletion(c);
}
librados::AioCompletion *librados::Rados::aio_create_completion(void *cb_arg,
                                                                callback_t cb_complete,
                                                                callback_t cb_safe)
{
  AioCompletionImpl *c;
  int r = rados_aio_create_completion(cb_arg, cb_complete, cb_safe, (void**)&c);
  assert(r == 0);
  return new AioCompletion(c);
}


本质上都一样,只不过对于第二种,它允许使用者直接设置回调和回调参数。使用者也可以通过

librados::AioCompletion::AioCompletion::

系列的接口来设置相关的回调及属性。


举例子:
librbd/Utils.h里面定义了6个根据模版来创建不同的AioCompletion实例的接口

template <typename T>
librados::AioCompletion *create_rados_ack_callback(T *obj) {
  return librados::Rados::aio_create_completion(
    obj, &detail::rados_callback<T>, nullptr);
}
template <typename T, void(T::*MF)(int)>
librados::AioCompletion *create_rados_ack_callback(T *obj) {
  return librados::Rados::aio_create_completion(
    obj, &detail::rados_callback<T, MF>, nullptr);
}
...


还有在cls、journal等等其他地方中,总之,就是我们最开始说到的librados::AioCompletion的设计目的,再同librados交互的模块中,如果有需要用到对交互操作结果处理的地方,就需要通过librados::AioCompletion来处理结果码。
创建一个AioCompletion实例还不够,在ceph中,所有的结果码都是包含在Context上下文中,因此对于使用者来说,要将AioCompletion包装在一个上下文实例中,这样就可以在上下文结束的时候,将结果码直接传递给AioCompletion实例的回调函数来处理。

包装AioCompletionImpl的过渡Context


对于AIoCompletion实例的使用都是引用的pc,即AioCompletionImpl,因此具体来说应该是如何包装AioCompletionImpl。这里提一下,librados中所有关于IO操作的API都设计在librados::IoCtx中,接口的真正实现在IoCtxImpl中。而AioCompletionImpl是IO操作的回调,因为对于AioCompletionImpl的包装设计在IoCtxImpl模块中,比如:

struct C_aio_Complete : public Context {
struct C_aio_stat_Ack : public Context {
struct C_aio_stat2_Ack : public Context { 

等等。

为什么说是过渡Context?

(1)它不是真正执行AioCompletionImpl的地方
(2)它是负责同osdc交互的Context


每个过渡Context的设计都有些不同的处理逻辑,对于一个过渡Context,再这里我们关注两点: 1. 被谁调用。 2. Context的结束处理finish方法。即:

(1)被谁调用
每个包装AioCompletionImpl之后的Context实例会被封装到osdc/Objecter::Op.finisher中,每个同osdc交互的模块都要通过Objecter模块,而每个操作都会通过Op结构体来表示。每个Op都是同osd交互的基础操作,因此在objecter会处理每个同osd交互Op的返回,在处理osd返回的时候,会调用Op->finisher->complete,进而调用AioCompletionImpl包装Context的finish处理。这就是AioCompletionImpl包装Context被执行的地方。

(2)AioCompletionImpl过渡Context的结束处理函数
绝大部分封装的Context的结束处理函数finish方法中,都将AioCompletionImpl再包装在一个新的Context中,然后将这个新的Context放入finisher队列,而这个新的Context才是真正执行AioCompletionImpl的Context,因此等会儿可以关注下这个新的Context的结束处理函数。另外你可以发现这些新的Context都是设计在librados/AioCompletionImpl中,由此也可以知道,这个Context才是真正执行AioCompletionImpl的地方。

client->finisher.queue(new librados::C_AioComplete(c)); 

这里由提一下Finisher对Context的处理,在调用librados的时候,会申请一个librados::RadosClient实例,此时会初始化一个Finisher实例,这是一个处理Context实例的线程,在RadosClient::connect的时候,会启动Finisher线程。使用者可以将Context放入Finisher队列,之后,Finisher线程就从队列中拿出Context并执行(调用complete方法)。

接着就是新的Context的结束处理函数finish方法,也就是librads/AioCompletionImpl.h中定义的Context。在这个Context中的处理逻辑就相对简单点,基本就是根据前面对AioCompletionImpl的一些设置来调用回调处理,没什么特别的,最终都是调用回调处理函数

cb_complete(c, cb_complete_arg); 
cb_safe(c, cb_safe_arg); 


例子


了解完librados的AIO回调机制之后,我们以一个例子来理解下这个机制。
librbd的创建rbd镜像接口调用流:

librbd::rbd_create2 -> internal::create -> CreateRequest::send() -> CreateRequest::validate_pool()

template<typename I>
void CreateRequest<I>::validate_pool() {
  if (!m_cct->_conf->rbd_validate_pool) {
    create_id_object();
    return;
  }
  ldout(m_cct, 20) << this << " " << __func__ << dendl;
  using klass = CreateRequest<I>;
  librados::AioCompletion *comp =
    create_rados_ack_callback<klass, &klass::handle_validate_pool>(this);

  librados::ObjectReadOperation op;
  op.stat(NULL, NULL, NULL);

  int r = m_ioctx.aio_operate(RBD_DIRECTORY, comp, &op, &m_outbl);
  assert(r == 0);
  comp->release();
}


aio_operate先经过librados::IoCtx,再到librados::IoCtxImpl

int librados::IoCtxImpl::aio_operate(const object_t& oid,
                                     ::ObjectOperation *o, AioCompletionImpl *c,
                                     const SnapContext& snap_context, int flags)
{
  FUNCTRACE();
  OID_EVENT_TRACE(oid.name.c_str(), "RADOS_WRITE_OP_BEGIN");
  auto ut = ceph::real_clock::now();
  /* can't write to a snapshot */
  if (snap_seq != CEPH_NOSNAP)
    return -EROFS;
  Context *onack = new C_aio_Complete(c);
#if defined(WITH_LTTNG) && defined(WITH_EVENTTRACE)
  ((C_aio_Complete *) onack)->oid = oid;
#endif
  c->io = this;
  queue_aio_write(c);
  Objecter::Op *op = objecter->prepare_mutate_op(
    oid, oloc, *o, snap_context, ut, flags,
    onack, &c->objver);
  objecter->op_submit(op, &c->tid);
  return 0;
}


onack会被丢入Objecter::Op.onfinish,在处理Op回复的时候,会调用

void Objecter::handle_osd_op_reply(MOSDOpReply *m)
....
  // do callbacks
  if (onfinish) {
    onfinish->complete(rc);
  }


这个onfinish是过渡Context,即C_aio_Complete,因此下面看C_aio_Complete的finish方法

void librados::IoCtxImpl::C_aio_Complete::finish(int r)
{
  c->lock.Lock();
  c->rval = r;
  c->complete = true;
  c->cond.Signal();

  if (r == 0 && c->blp && c->blp->length() > 0) {
    if (c->out_buf && !c->blp->is_provided_buffer(c->out_buf))
      c->blp->copy(0, c->blp->length(), c->out_buf);
    c->rval = c->blp->length();
  }

  if (c->callback_complete ||
      c->callback_safe) {
    c->io->client->finisher.queue(new C_AioComplete(c));
  }
....


finisher线程取出item执行的Context是C_AioComplete,下面看下C_AioComplete的finish方法 

void finish(int r) {
    c->lock.Lock();
    c->rval = r;
    c->complete = true;
    c->lock.Unlock();
    rados_callback_t cb_complete = c->callback_complete;
    void *cb_complete_arg = c->callback_complete_arg;
    if (cb_complete)
      cb_complete(c, cb_complete_arg);
    rados_callback_t cb_safe = c->callback_safe;
    void *cb_safe_arg = c->callback_safe_arg;
    if (cb_safe)
      cb_safe(c, cb_safe_arg);
    c->lock.Lock();
    c->callback_complete = NULL;
    c->callback_safe = NULL;
    c->cond.Signal();
    c->put_unlock();
  }


这个回调即是创建librados::AioCompletion的时候制定的handle_validate_pool参数。

注意点


每个Op对应一个回调,比如创建块设备镜像会对应很多Op,第一个Op的处理是handle_validate_pool,在这个handle里面会再发送第二Op,并挂上第二个Op的回调handle_create_id_object,以此类推下去。

本博文的AioCompletion机制只是librados的机制,ceph中很多模块都设计有这种类似的机制,librbd里面也又AioCompltion模块,读者需要注意命名空间。
 

原文链接:

(17条消息) ceph源代码分析之librados:1. AioCompletion回调机制分析_hawkerou的博客-优快云博客

(17条消息) Ceph学习——Librados与Osdc实现源码解析_SEU_PAN的博客-优快云博客

### 回答1: ceph源码分析pdf是一本关于ceph分布式文件系统的源代码深度剖析的著作,通过详细分析ceph系统的架构、实现、核心代码等方面,帮助读者深入理解ceph系统的设计思路和工作原理。 本书主要分为三个部分,第一部分介绍了ceph系统的概述和架构,包括ceph系统的目标、特点、架构分层等;第二部分主要探讨了ceph系统的核心组件,包括RADOS、MDS、OSD等,深入剖析了它们的工作原理和实现方式;第三部分则是关于ceph系统的进阶主题,如分布式存储、数据一致性、故障处理等等。 本书的特点在于对ceph源代码的深度剖析,对每个核心组件都逐一分析了其关键代码,帮助读者了解ceph系统的实现方式。此外,本书也提供了一些示例代码,可以帮助读者更好地理解ceph的设计思路和实现细节。 总之,ceph源码分析pdf具有深入、全面、实用的特点,可以帮助ceph开发者和用户深入理解ceph系统的工作原理,快速掌握ceph的使用和开发技巧。 ### 回答2: Ceph是一个采用分布式架构的开源存储系统,具有高可用性、高可扩展性和高性能等优点。对于想要深入了解Ceph存储系统的开发者来说,ceph源码分析pdf是一个非常有价值的资源,可以帮助他们全面理解Ceph的架构和运行机制ceph源码分析pdf主要分为以下几部分: 1. Ceph的整体架构介绍 介绍Ceph的整体架构,包括Ceph的分布式存储、元数据管理、数据访问、数据一致性、数据恢复等方面。同时,还会涉及到Ceph的各种组件和组件之间的关系。 2. Ceph的核心代码解析 对Ceph的核心代码进行深度解析,包括存储服务、客户端库、管理工具等部分。这部分内容非常重要,可以帮助开发者全面了解Ceph的代码,并有助于他们进行相关的开发工作。 3. Ceph的性能优化 介绍Ceph的性能优化方法,包括IO性能优化、网络性能优化、CPU性能优化等方面。这对于想要将Ceph应用于实际场景并且需要进行性能优化的开发者来说非常有价值。 总之,ceph源码分析pdf是一个非常有价值的资源,可以帮助开发者全面了解Ceph存储系统的架构和运行机制,并深入了解Ceph的核心代码和性能优化方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值