签到成功

知道了

CNDBA社区CNDBA社区

Ceph源码解析:读写流程

2016-12-25 12:54 4432 0 转载 Ceph
作者: Expect-乐

一、OSD模块简介

1.1 消息封装:在OSD上发送和接收信息。

cluster_messenger -与其它OSDs和monitors沟通 
client_messenger -与客户端沟通

1.2 消息调度

Dispatcher类,主要负责消息分类

1.3 工作队列:

1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。http://www.cndba.cn/Expect-le/article/1650

1.3.2 PeeringWQ: 处理peering任务,运行在op_tp线程池。

1.3.3 CommandWQ:处理cmd命令,运行在command_tp。

1.3.4 RecoveryWQ: 数据修复,运行在recovery_tp。

1.3.5 SnapTrimWQ: 快照相关,运行在disk_tp。

1.3.6 ScrubWQ: scrub,运行在disk_tp。

1.3.7 ScrubFinalizeWQ: scrub,运行在disk_tp。

1.3.8 RepScrubWQ: scrub,运行在disk_tp。

1.3.9 RemoveWQ: 删除旧的pg目录。运行在disk_tp。

1.4 线程池:

有4种OSD线程池:

1.4.1 op_tp: 处理ops和sub ops

1.4.2 recovery_tp:处理修复任务http://www.cndba.cn/Expect-le/article/1650

1.4.3 disk_tp: 处理磁盘密集型任务

1.4.4 command_tp: 处理命令

1.5 主要对象:

ObjectStore *store;

OSDSuperblock superblock; 主要是版本号等信息

OSDMapRef  osdmap;

1.6 主要操作流程: 参考文章

1.6.1 客户端发起请求过程

1.6.2 op_tp线程处理数据读取

1.6.3 对象操作的处理过程

1.6.4 修改操作的处理

1.6.5 日志的写入

1.6.6 写操作处理

1.6.7 事务的sync过程

1.6.8 日志恢复

1.7 整体处理过程图

 http://www.cndba.cn/Expect-le/article/1650

                      ba55bd649e315fc12986a2410b00b080

二、客户端写入数据大致流程及保存形式

2.1 读写框架

                                             image

                  imageimage

2.2 客户端写入流程

在客户端使用 rbd 时一般有两种方法:

  • 第一种 是 Kernel rbd。就是创建了rbd设备后,把rbd设备map到内核中,形成一个虚拟的块设备,这时这个块设备同其他通用块设备一样,一般的设备文件为/dev/rbd0,后续直接使用这个块设备文件就可以了,可以把 /dev/rbd0 格式化后 mount 到某个目录,也可以直接作为裸设备使用。这时对rbd设备的操作都通过kernel rbd操作方法进行的。 

  • 第二种是 librbd 方式。就是创建了rbd设备后,这时可以使用librbd、librados库进行访问管理块设备。这种方式不会map到内核,直接调用librbd提供的接口,可以实现对rbd设备的访问和管理,但是不会在客户端产生块设备文件。

应用写入rbd块设备的过程:

  1. 应用调用 librbd 接口或者对linux 内核虚拟块设备写入二进制块。下面以 librbd 为例。

  2. librbd 对二进制块进行分块,默认块大小为 4M,每一块都有名字,成为一个对象

  3. librbd 调用 librados 将对象写入 Ceph 集群

  4. librados 向主 OSD 写入分好块的二进制数据块 (先建立TCP/IP连接,然后发送消息给 OSD,OSD 接收后写入其磁盘)

  5. 主 OSD 负责同时向一个或者多个次 OSD 写入副本。注意这里是写到日志(Journal)就返回,因此,使用SSD作为Journal的话,可以提高响应速度,做到服务器端对客户端的快速同步返回写结果(ack)。

  6. 当主次OSD都写入完成后,主 OSD 向客户端返回写入成功。

  7. 当一段时间(也许得几秒钟)后Journal 中的数据向磁盘写入成功后,Ceph通过事件通知客户端数据写入磁盘成功(commit),此时,客户端可以将写缓存中的数据彻底清除掉了。

  8. 默认地,Ceph 客户端会缓存写入的数据直到收到集群的commit通知。如果此阶段内(在写方法返回到收到commit通知之间)OSD 出故障导致数据写入文件系统失败,Ceph 将会允许客户端重做尚未提交的操作(replay)。因此,PG 有个状态叫 replay:“The placement group is waiting for clients to replay operations after an OSD crashed.”。

                                                       

也就是,文件系统负责文件处理,librbd 负责块处理,librados 负责对象处理,OSD 负责将数据写入在Journal和磁盘中。

2.3 RBD保存形式

如下图所示,Ceph 系统中不同层次的组件/用户所看到的数据的形式是不一样的:

                         

  • Ceph 客户端所见的是一个完整的连续的二进制数据块,其大小为创建 RBD image 是设置的大小或者 resize 的大小,客户端可以从头或者从某个位置开始写入二进制数据。

  • librados 负责在 RADOS 中创建对象(object),其大小为 pool 的 order 决定,默认情况下 order = 22 此时 object 大小为 4MB;以及负责将客户端传入的二进制块条带化为若干个条带(stripe)。

  • librados 控制哪个条带由哪个 OSD 写入(条带 ---写入哪个----> object ----位于哪个 ----> OSD)

  • OSD 负责创建在文件系统中创建文件,并将 librados 传入的数据写入数据。

  Ceph client 向一个 RBD image 写入二进制数据(假设 pool 的拷贝份数为 3):

(1)Ceph client 调用 librados 创建一个 RBD image,这时候不会做存储空间分配,而是创建若干元数据对象来保存元数据信息。

(2)Ceph client 调用 librados 开始写数据。librados 计算条带、object 等,然后开始写第一个 stripe 到特定的目标 object。

(3)librados 根据 CRUSH 算法,计算出 object 所对应的主 OSD ID,并将二进制数据发给它。http://www.cndba.cn/Expect-le/article/1650

(4)主 OSD 负责调用文件系统接口将二进制数据写入磁盘上的文件(每个 object 对应一个 file,file 的内容是一个或者多个 stripe)。

(5)主 ODS 完成数据写入后,它使用 CRUSH 算啊计算出第二个OSD(secondary OSD)和第三个OSD(tertiary OSD)的位置,然后向这两个 OSD 拷贝对象。都完成后,它向 ceph client 反馈该 object 保存完毕。

                                                         

(6)然后写第二个条带,直到全部写入完成。全部完成后,librados 还应该会做元数据更新,比如写入新的 size 等。

完整的过程(来源):

                               

该过程具有强一致性的特点:

  • Ceph 的读写操作采用 Primary-Replica 模型,Client 只向 Object 所对应 OSD set 的 Primary 发起读写请求,这保证了数据的强一致性。

  • 由于每个 Object 都只有一个 Primary OSD,因此对 Object 的更新都是顺序的,不存在同步问题。

  • 当 Primary 收到 Object 的写请求时,它负责把数据发送给其他 Replicas,只要这个数据被保存在所有的OSD上时,Primary 才应答Object的写请求,这保证了副本的一致性。这也带来一些副作用。相比那些只实现了最终一致性的存储系统比如 Swift,Ceph 只有三份拷贝都写入完成后才算写入完成,这在出现磁盘损坏时会出现写延迟增加。

  • 在 OSD 上,在收到数据存放指令后,它会产生2~3个磁盘seek操作:

    • 把写操作记录到 OSD 的 Journal 文件上(Journal是为了保证写操作的原子性)。

    • 把写操作更新到 Object 对应的文件上。

    • 把写操作记录到 PG Log 文件上。

三、客户端请求流程(转的一只小江的博文,写的挺好的)

http://www.cndba.cn/Expect-le/article/1650

RADOS读对象流程

                                           image

RADOS写对象操作流程

                                             image

例子:

#!/usr/bin/env python 
import sys,rados,rbd 
def connectceph(): 
      cluster = rados.Rados(conffile = '/root/xuyanjiangtest/ceph-0.94.3/src/ceph.conf') 
      cluster.connect() 
      ioctx = cluster.open_ioctx('mypool') 
      rbd_inst = rbd.RBD() 
      size = 4*1024**3 #4 GiB 
      rbd_inst.create(ioctx,'myimage',size) 
      image = rbd.Image(ioctx,'myimage') 
      data = 'foo'* 200 
      image.write(data,0) 
      image.close() 
      ioctx.close() 
        cluster.shutdown() 
  
if __name__ == "__main__": 
        connectceph()

1. 首先cluster = rados.Rados(conffile = 'ceph.conf'),用当前的这个ceph的配置文件去创建一个rados,这里主要是解析ceph.conf中中的集群配置参数。然后将这些参数的值保存在rados中。

2. cluster.connect() ,这里将会创建一个radosclient的结构,这里会把这个结构主要包含了几个功能模块:消息管理模块Messager,数据处理模块Objector,finisher线程模块。

3. ioctx = cluster.open_ioctx('mypool'),为一个名字叫做mypool的存储池创建一个ioctx ,ioctx中会指明radosclient与Objector模块,同时也会记录mypool的信息,包括pool的参数等。

4. rbd_inst.create(ioctx,'myimage',size) ,创建一个名字为myimage的rbd设备,之后就是将数据写入这个设备。

5. image = rbd.Image(ioctx,'myimage'),创建image结构,这里该结构将myimage与ioctx 联系起来,后面可以通过image结构直接找到ioctx。这里会将ioctx复制两份,分为为data_ioctx和md_ctx。见明知意,一个用来处理rbd的存储数据,一个用来处理rbd的管理数据。

流程图:

                            143540_OSPk_2460844http://www.cndba.cn/Expect-le/article/1650

1. image.write(data,0),通过image开始了一个写请求的生命的开始。这里指明了request的两个基本要素 buffer=data 和 offset=0。由这里开始进入了ceph的世界,也是c++的世界。

由image.write(data,0)  转化为librbd.cc 文件中的Image::write() 函数,来看看这个函数的主要实现

ssize_t Image::write(uint64_t ofs, size_t len, bufferlist& bl)
{      ImageCtx *ictx = (ImageCtx *)ctx;     int r = librbd::write(ictx, ofs, len, bl.c_str(), 0);     return r;      }

2. 该函数中直接进行分发给了librbd::wrte的函数了。跟随下来看看librbd::write中的实现。该函数的具体实现在internal.cc文件中。

ssize_t write(ImageCtx *ictx, uint64_t off, size_t len, const char *buf, int op_flags)
{     Context *ctx = new C_SafeCond(&mylock, &cond, &done, &ret);   //---a     AioCompletion *c = aio_create_completion_internal(ctx, rbd_ctx_cb);//---b     r = aio_write(ictx, off, mylen, buf, c, op_flags);  //---c       while (!done)            cond.Wait(mylock);  // ---d
}

---a.这句要为这个操作申请一个回调操作,所谓的回调就是一些收尾的工作,信号唤醒处理。

---b。这句是要申请一个io完成时 要进行的操作,当io完成时,会调用rbd_ctx_cb函数,该函数会继续调用ctx->complete()。

---c.该函数aio_write会继续处理这个请求。

---d.当c句将这个io下发到osd的时候,osd还没请求处理完成,则等待在d上,直到底层处理完请求,回调b申请的 AioCompletion, 继续调用a中的ctx->complete(),唤醒这里的等待信号,然后程序继续向下执行。

3.再来看看aio_write 拿到了 请求的offset和buffer会做点什么呢?

int aio_write(ImageCtx *ictx, uint64_t off, size_t len, const char *buf,            AioCompletion *c, int op_flags)
{       //将请求按着object进行拆分       vector<ObjectExtent> extents;       if (len > 0)        {          Striper::file_to_extents(ictx->cct, ictx->format_string,                         &ictx->layout, off, clip_len, 0, extents);   //---a       }        //处理每一个object上的请求数据       for (vector<ObjectExtent>::iterator p = extents.begin(); p != extents.end(); ++p)        {            C_AioWrite *req_comp = new C_AioWrite(cct, c); //---b            AioWrite *req = new AioWrite(ictx, p->oid.name, p->objectno, p- >offset,bl,….., req_comp);     //---c            r = req->send();    //---d       }
}

根据请求的大小需要将这个请求按着object进行划分,由函数file_to_extents进行处理,处理完成后按着object进行保存在extents中。file_to_extents()存在很多同名函数注意区分。这些函数的主要内容做了一件事儿,那就对原始请求的拆分。

一个rbd设备是有很多的object组成,也就是将rbd设备进行切块,每一个块叫做object,每个object的大小默认为4M,也可以自己指定。file_to_extents函数将这个大的请求分别映射到object上去,拆成了很多小的请求如下图。最后映射的结果保存在ObjectExtent中。

                              

原本的offset是指在rbd内的偏移量(写入rbd的位置),经过file_to_extents后,转化成了一个或者多个object的内部的偏移量offset0。这样转化后处理一批这个object内的请求。

4. 再回到 aio_write函数中,需要将拆分后的每一个object请求进行处理。

---b.为写请求申请一个回调处理函数。

---c.根据object内部的请求,创建一个叫做AioWrite的结构。

---d.将这个AioWrite的req进行下发send().

5. 这里AioWrite 是继承自 AbstractWrite ,AbstractWrite 继承自AioRequest类,在AbstractWrite 类中定义了send的方法,看下send的具体内容.

int AbstractWrite::send()  {      if (send_pre())           //---a
}

#进入send_pre()函数中

bool AbstractWrite::send_pre()
{
      m_state = LIBRBD_AIO_WRITE_PRE;   // ----a       FunctionContext *ctx =    //----b            new FunctionContext( boost::bind(&AioRequest::complete, this, _1));       m_ictx->object_map.aio_update(ctx); //-----c
}

---a.修改m_state 状态为LIBRBD_AIO_WRITE_PRE。

---b.申请一个回调函数,实际调用AioRequest::complete()

---c.开始下发object_map.aio_update的请求,这是一个状态更新的函数,不是很重要的环节,这里不再多说,当更新的请求完成时会自动回调到b申请的回调函数。

6. 进入到AioRequest::complete() 函数中。

void AioRequest::complete(int r)
{     if (should_complete(r))   //---a
}

---a.should_complete函数是一个纯虚函数,需要在继承类AbstractWrite中实现,来7. 看看AbstractWrite:: should_complete()

bool AbstractWrite::should_complete(int r)
{     switch (m_state)     {        case LIBRBD_AIO_WRITE_PRE:  //----a        {           send_write(); //----b

----a.在send_pre中已经设置m_state的状态为LIBRBD_AIO_WRITE_PRE,所以会走这个分支。

----b. send_write()函数中,会继续进行处理,

7.1.下面来看这个send_write函数

void AbstractWrite::send_write()
{       m_state = LIBRBD_AIO_WRITE_FLAT;   //----a       add_write_ops(&m_write);    // ----b       int r = m_ictx->data_ctx.aio_operate(m_oid, rados_completion, &m_write);
}

---a.重新设置m_state的状态为 LIBRBD_AIO_WRITE_FLAT。

---b.填充m_write,将请求转化为m_write。

---c.下发m_write  ,使用data_ctx.aio_operate 函数处理。继续调用io_ctx_impl->aio_operate()函数,继续调用objecter->mutate().

8. objecter->mutate()

ceph_tid_t mutate(……..)  {     Op *o = prepare_mutate_op(oid, oloc, op, snapc, mtime, flags, onack, oncommit, objver);   //----d     return op_submit(o);
}

---d.将请求转化为Op请求,继续使用op_submit下发这个请求。在op_submit中继续调用_op_submit_with_budget处理请求。继续调用_op_submit处理。

8.1 _op_submit 的处理过程。这里值得细看

ceph_tid_t Objecter::_op_submit(Op *op, RWLock::Context& lc)
{
    check_for_latest_map = _calc_target(&op->target, &op->last_force_resend); //---a     int r = _get_session(op->target.osd, &s, lc);  //---b     _session_op_assign(s, op); //----c     _send_op(op, m); //----d

}

----a. _calc_target,通过计算当前object的保存的osd,然后将主osd保存在target中,rbd写数据都是先发送到主osd,主osd再将数据发送到其他的副本osd上。这里对于怎么来选取osd集合与主osd的关系就不再多说,在《ceph的数据存储之路(3)》中已经讲述这个过程的原理了,代码部分不难理解。

----b. _get_session,该函数是用来与主osd建立通信的,建立通信后,可以通过该通道发送给主osd。再来看看这个函数是怎么处理的

9. _get_session

int Objecter::_get_session(int osd, OSDSession **session, RWLock::Context& lc)
{     map<int,OSDSession*>::iterator p = osd_sessions.find(osd);   //----a     OSDSession *s = new OSDSession(cct, osd); //----b     osd_sessions[osd] = s;//--c     s->con = messenger->get_connection(osdmap->get_inst(osd));//-d

}

----a.首先在osd_sessions中查找是否已经存在一个连接可以直接使用,第一次通信是没有的。

----b.重新申请一个OSDSession,并且使用osd等信息进行初始化。

---c. 将新申请的OSDSession添加到osd_sessions中保存,以备下次使用。

----d.调用messager的get_connection方法。在该方法中继续想办法与目标osd建立连接。

10. messager 是由子类simpleMessager实现的,下面来看下SimpleMessager中get_connection的实现方法

ConnectionRef SimpleMessenger::get_connection(const entity_inst_t& dest)
{     Pipe *pipe = _lookup_pipe(dest.addr);     //-----a     if (pipe)      {     }      else      {       pipe = connect_rank(dest.addr, dest.name.type(), NULL, NULL); //----b     }

}

----a.首先要查找这个pipe,第一次通信,自然这个pipe是不存在的。

----b. connect_rank 会根据这个目标osd的addr进行创建。看下connect_rank做了什么。

11. SimpleMessenger::connect_rank

Pipe *SimpleMessenger::connect_rank(const entity_addr_t& addr,  int type, PipeConnection *con,    Message *first)
{
    Pipe *pipe = new Pipe(this, Pipe::STATE_CONNECTING, static_cast<PipeConnection*>(con));      //----a     pipe->set_peer_type(type); //----b     pipe->set_peer_addr(addr); //----c     pipe->policy = get_policy(type); //----d     pipe->start_writer();  //----e     return pipe; //----f
}

----a.首先需要创建这个pipe,并且pipe同pipecon进行关联。

----b,----c,-----d。都是进行一些参数的设置。

----e.开始启动pipe的写线程,这里pipe的写线程的处理函数pipe->writer(),该函数中会尝试连接osd。并且建立socket连接通道。

目前的资源统计一下,写请求可以根据目标主osd,去查找或者建立一个OSDSession,这个OSDSession中会有一个管理数据通道的Pipe结构,然后这个结构中存在一个发送消息的处理线程writer,这个线程会保持与目标osd的socket通信。

12. 建立并且获取到了这些资源,这时再回到_op_submit 函数中

ceph_tid_t Objecter::_op_submit(Op *op, RWLock::Context& lc)
{
    check_for_latest_map = _calc_target(&op->target, &op->last_force_resend); //---a     int r = _get_session(op->target.osd, &s, lc);  //---b     _session_op_assign(s, op); //----c     MOSDOp *m = _prepare_osd_op(op); //-----d     _send_op(op, m); //----e
}

---c,将当前的op请求与这个session进行绑定,在后面发送请求的时候能知道使用哪一个session进行发送。

--d,将op转化为MOSDop,后面会以MOSDOp为对象进行处理的。

---e,_send_op 会根据之前建立的通信通道,将这个MOSDOp发送出去。_send_op 中调用op->session->con->send_message(m),这个方法会调用SimpleMessager-> send_message(m), 再调用_send_message(),再调用submit_message().在submit_message会找到之前的pipe,然后调用pipe->send方法,最后通过pipe->writer的线程发送到目标osd。

自此,客户就等待osd处理完成返回结果了。

http://www.cndba.cn/Expect-le/article/1650

                  151617_dNP0_2460844

1.看左上角的rados结构,首先创建io环境,创建rados信息,将配置文件中的数据结构化到rados中。

2.根据rados创建一个radosclient的客户端结构,该结构包括了三个重要的模块,finiser 回调处理线程、Messager消息处理结构、Objector数据处理结构。最后的数据都是要封装成消息 通过Messager发送给目标的osd。

3.根据pool的信息与radosclient进行创建一个ioctx,这里面包好了pool相关的信息,然后获得这些信息后在数据处理时会用到。

4.紧接着会复制这个ioctx到imagectx中,变成data_ioctx与md_ioctx数据处理通道,最后将imagectx封装到image结构当中。之后所有的写操作都会通过这个image进行。顺着image的结构可以找到前面创建并且可以使用的数据结构。

5.通过最右上角的image进行读写操作,当读写操作的对象为image时,这个image会开始处理请求,然后这个请求经过处理拆分成object对象的请求。拆分后会交给objector进行处理查找目标osd,当然这里使用的就是crush算法,找到目标osd的集合与主osd。

6.将请求op封装成MOSDOp消息,然后交给SimpleMessager处理,SimpleMessager会尝试在已有的osd_session中查找,如果没有找到对应的session,则会重新创建一个OSDSession,并且为这个OSDSession创建一个数据通道pipe,把数据通道保存在SimpleMessager中,可以下次使用。

7.pipe 会与目标osd建立Socket通信通道,pipe会有专门的写线程writer来负责socket通信。在线程writer中会先连接目标ip,建立通信。消息从SimpleMessager收到后会保存到pipe的outq队列中,writer线程另外的一个用途就是监视这个outq队列,当队列中存在消息等待发送时,会就将消息写入socket,发送给目标OSD。

8. 等待OSD将数据消息处理完成之后,就是进行回调,反馈执行结果,然后一步步的将结果告知调用者。

四、Ceph读流程

OSD端读消息分发流程

                                                   image

OSD端读操作处理流程

                                             image

总体流程图:

                                        4ac886ce405a7e638b2979b693802a8e

 

int read(inodeno_t ino, 
             file_layout_t *layout, 
             snapid_t snap, 
             uint64_t offset, 
             uint64_t len, 
             bufferlist *bl,   // ptr to data 
             int flags, 
             Context *onfinish, 
             int op_flags = 0)    --------------------------------Filer.h

Striper::file_to_extents(cct, ino, layout, offset, len, truncate_size, extents);//将要读取数据的长度和偏移转化为要访问的对象,extents沿用了brtfs文件系统的概念 
objecter->sg_read_trunc(extents, snap, bl, flags, truncate_size, truncate_seq, onfinish, op_flags);//向osd发起请求

对于读操作而言:

1.客户端直接计算出存储数据所属于的主osd,直接给主osd上发送消息。

2.主osd收到消息后,可以调用Filestore直接读取处在底层文件系统中的主pg里面的内容然后返回给客户端。具体调用函数在ReplicatedPG::do_osd_ops中实现。

CEPH_OSD_OP_MAPEXT||CEPH_OSD_OP_SPARSE_READ

r = osd->store->fiemap(coll, soid, op.extent.offset, op.extent.length, bl);

CEPH_OSD_OP_READ

r = pgbackend->objects_read_sync(soid, miter->first, miter->second, &tmpbl);http://www.cndba.cn/Expect-le/article/1650

五、Ceph写流程

OSD端写操作处理流程

                                   image

而对于写操作而言,由于要保证数据写入的同步性就会复杂很多:

1.首先客户端会将数据发送给主osd,

2.主osd同样要先进行写操作预处理,完成后它要发送写消息给其他的从osd,让他们对副本pg进行更改,

3.从osd通过FileJournal完成写操作到Journal中后发送消息告诉主osd说完成,进入5

4.当主osd收到所有的从osd完成写操作的消息后,会通过FileJournal完成自身的写操作到Journal中。完成后会通知客户端,已经完成了写操作。

5.主osd,从osd的线程开始工作调用Filestore将Journal中的数据写入到底层文件系统中。

写的逻辑流程图如图:

                     

http://www.cndba.cn/Expect-le/article/1650

从图中我们可以看到写操作分为以下几步: 
1.OSD::op_tp线程从OSD::op_wq中拿出来操作如本文开始的图上描述,具体代码流是

                     

    ReplicatePG::apply_repop中创建回调类C_OSD_OpCommit和C_OSD_OpApplied

    FileStore::queue_transactions中创建了回调类C_JournaledAhead

2.FileJournal::write_thread线程从FileJournal::writeq中拿出来操作,主要就是写数据到具体的journal中,具体代码流:

                

3.Journal::Finisher.finisher_thread线程从Journal::Finisher.finish_queue中拿出来操作,通过调用C_JournalAhead留下的回调函数FileStore:_journaled_ahead,该线程开始工作两件事:首先入底层FileStore::op_wq通知开始写,再入FileStore::ondisk_finisher.finisher_queue通知可以返回。具体代码流:

           

4.FileStore::ondisk_finisher.finisher_thread线程从FileStore::ondisk_finisher.finisher_queue中拿出来操作,通过调用C_OSD_OpCommit留下来的回调函数ReplicatePG::op_commit,通知客户端写操作成功

                                     

5.FileStore::op_tp线程池从FileStore::op_wq中拿出操作(此处的OP_WQ继承了父类ThreadPool::WorkQueue重写了_process和_process_finish等函数,所以不同于OSD::op_wq,它有自己的工作流程),首先调用FileStore::_do_op,完成后调用FileStore::_finish_op。

                

6. FileStore::op_finisher.finisher_thread线程从FileStore::op_finisher.finisher_queue中拿出来操作,通过调用C_OSD_OpApplied留下来的回调函数ReplicatePG::op_applied,通知数据可读。

                                    

具体OSD方面的源码逐句解析可以参考一只小江的博文

此文主要整理了参考资料里的ceph客户端读写流程,OSD端读写流程等,使用了参考资料的内容,如果侵犯到参照资料作者的权益,请联系我,我会及时删除相关内容。

参考资料:

http://blog.sina.com.cn/s/blog_c2e1a9c7010151xb.html

作者:ywy463726588  http://blog.csdn.net/ywy463726588/article/details/42676493

                             http://blog.csdn.net/ywy463726588/article/details/42679869

作者:刘世民(Sammy Liu)http://www.cnblogs.com/sammyliu/p/4836014.html

作者:一只小江 http://my.oschina.net/u/2460844/blog/532755

                   http://my.oschina.net/u/2460844/blog/534390?fromerr=PnkKCbYU

感谢以上作者无私的分享!


http://www.cndba.cn/Expect-le/article/1650

用户评论
* 以下用户言论只代表其个人观点,不代表CNDBA社区的观点或立场
Expect-乐

Expect-乐

关注

Without the continuous bitter cold, there can be no fragrant plum blossom

  • 336
    原创
  • 6
    翻译
  • 100
    转载
  • 41
    评论
  • 访问:1836575次
  • 积分:1957
  • 等级:核心会员
  • 排名:第4名
精华文章
    最新问题
    查看更多+
    热门文章
      热门用户
      推荐用户
        Copyright © 2016 All Rights Reserved. Powered by CNDBA · 皖ICP备2022006297号-1·

        QQ交流群

        注册联系QQ