ceph的数据存储之路(5) -----osd数据处理

osd的数据处理

当osd接管了来自rbd client发送的请求,在osd上进行解析,经过一系列的处理,最后保存到了osd的磁盘上。

 

上一节中讲述了rbdclient端是如何处理请求数据的,下面开始从osd接受数据开始进行讲述。

 

一、client如何与osd进行建立通信的呢?

1. osd是运行在服务器上的进程,进程的主体在ceph_osd.cc中的main函数开始,并且申请一些用于服务的线程。比如这里用于接客户端受连接的线程Accepter。如果配对连接成功后,会把连接成功的socket交给SimpleMessager来处理。

173427_5Mbn_2460844.png

  

2. 在ceph_osd.cc 中main函数中 会申请一个Messager 消息管理的类,该类是一个基类,最终会选择SimpleMessager类实例,SimpleMessager类中会有一个叫做Accepter的成员,会申请该对象,并且初始化。

然后在main中把它绑定一个固定的ip,这个ip根据配置文件来获取。如0577行

173514_4S7v_2460844.png

 

3.这个ip最终会绑定在Accepter中。然后在Accepter->bind函数中,会对这个ip初始化一个socket,并且保存为listen_sd。接着会启动Accepter->start(),这里会启动Accepter的监听线程,这个线程做的事情放在Accepter->entry()函数中。接下来看看这个函数中实现了什么。

173514_tPJe_2460844.png

0216:开始一个循环,线程不能结束。知道停止这个线程时。

0218:poll监听一个端口,等待客户端来进行连接。

0219:如果有客户端来连接时,如果来接成功则进行下面的处理

0222:msgr 是具体实现类为SimpleMessager,跟踪到实现的函数中去。

173514_Hbj6_2460844.png

0343:当接受到一个连接时,这时需要建立一个数据通道pipe,。

0344:告知pipe 处理socket为sd。也就是从这个sd读取数据。

0346:启动pipe的读线程,从sd中读取message信息。

0349:将pipe加入到SimpleMessager中的accepting_pipes,便于管理。

 

二、消息的传递

4. 接下来已经建立了pipe的通道,马上就要开始进行message的接受, 目前接收message的工作交给了pipe的reader线程。接下来看看pipe->reader()做了什么事情。

pipe->reader()中会先 read_message(),将传递的消息解析出来,然后把这个消息放入in_q->fast_dispatch(m)中处理,继续发送到OSD::ms_fast_dispatch中处理,在ms_fast_dispatch中会将message 转化为OpRequestRef op,后续直接对这个op进行处理。继续经过dispatch_session_waiting()、dispatch_op_fast()、handle_op()。handle_op()中开始将这个处理由OSD转化为PG的处理。

173514_SXHT_2460844.png

8502:根据message的信息可以获得pg_id。

8503:再根据pg_id可以恢复出pool的id。也就是pool的序号。

8508:根据当然获取到的osdmap重新修正这个pg_id。

8512:根据pg_id信息创建 pg的信息结构spg_t pgid。

 

三、消息的处理

5.上面已经根据message中的信息恢复出pg和pool的相关信息。这些信息对于osd组织数据有很大的帮助。接下来开始要交给PG进行处理了。

173515_UbSE_2460844.png

8568:根据pg_id在osd->pg_map中查找pg。这里要说明下PG是一个基类,由ReplicatedPG类实现。这里的PG实际上使用的就是继承类的方法。

8573:找到PG以后,这时将这个op交给PG进行继续处理。已经从OSD处理的message转化为了PG处理的op。由这里开始,最终添加到OSDService->op_wq队列,该队列是由OSD->ShardedOpWQ队列初始化,实际上是保存到了OSD-> 的ShardedOpWQ  ,然后保存在结构ShardData中等待被处理,然后唤醒处理这个队列的线程,线程处理函数OSD::ShardedOpWQ::_process调用到OSD::dequeue_op()、pg->do_request()、ReplicatedPG::do_request()、ReplicatedPG::do_op()。接下来看看do_op中是如何处理的。

173515_uBn9_2460844.png

 

1809:这里要创建一个OpContext结构,该结构会接管message中的所有ops的操作,ops的操作就是客户端将rbd请求拆分成object的请求。

 

四、形成事务Transaction

1810:获取一个transaction,因为pgbackend类被ReplicatedBackend类实现,所以get_transaction的实现由ReplicatedBackend::get_transaction() 完成,申请一个叫做RPGTransaction 然后交给了ctx->op_t。这里想要说明的是,申请了一个事物transaction,下面就要处理这个Transaction需要与我们的op组合了吧,在一个transaction中完成这些ops的操作,看看他们是如何关联的execute_ctx(),。

 

7. 首先使用int result = prepare_transaction(ctx); 他会将 ops上要写入的数据全部都按着结构保存在transaction的data_bl,op_bl中。拿着这个transaction 就已经获得了全部的操作和数据,这也就是将ops与Transaction绑定的结果。因为primary osd不仅仅是要自己保存数据,其他的replica osd也要保存数据的副本,这里有primary osd将准备好的数据发送给replica osd。

 

五、osd完成统计处理repop

173515_FUG9_2460844.png

2523:申请repop,这个repop是用来管理发送给其他副本以及自己进行数据处理的统计,根据这个结构可知那些osd都完成了数据的读写操作,回调比不可少的。

2528:issue_repop()将操作率先发给其他副本,这里其实就是与其他副本通信,将ops发送给其他副本,其他副本操作完成时能够及时的回调找到repop,然后开始处理,在issue_repop中规定了所有osd完成时的on_all_commit与on_all_applied的操作,并且将提交Transaction,使用函数submit_transaction()。这里pgbackend->submit_transaction()函数中,pgbackend对象已经被ReplicatedBackend进行了继承,所以最终使用的是ReplicatedBackend::submit_transaction()。

2530:如果数据处理完成了,使用eval_repop()进行收尾的工作,将结果回调给客户端。

 

8. 接下来继续查看函数void ReplicatedBackend::submit_transaction()中的实现方式

173515_yiLr_2460844.png

0590:开始准备一个处理操作的结构InProgressOp,然后将申请的on_all_commit、on_all_applied等回调操作进行统计。

0594:开始统计所有需要applied的副本操作数量,等待副本操作完成回调时进行清除,使用该结构方便统计是不是所有的副本都完成了操作。

0598:作用同0594相同,区别在于统计commit的副本操作数量。

0620:issue_op将ops的信息封装成message发送给replica osd副本的。这个操作就是在封装message,这里就不再多说了。

173515_BKs6_2460844.png

0619:开始记录本端操作object的log。这个log对于数据的恢复存在至关重要的决定。

0624:开始统计本端的sync回调,主要用于快照和克隆等等的数据同步。

0625:开始注册本端的applied回调函数,这里回调后会直接向上回调all_applied()

0627:开始注册本端删除object的回调操作函数(这个暂时不用考虑)。

0629:开始注册本端的commit回调函数,这里回调后直接向上回调all_commited()。

0631:本端开始真正的处理请求。这个parent指的就是ReplicaPG -> queue_transaction,然后在进行最后osd->store->queue_transaction ,这里的store是ObjectStore 几经辗转最后调用到继承类FileStore::queue_transactions()开始处理。

 

六、文件与日志的处理

9.从这里开始的 接下来就全部的交给了FileStore来处理了。

173516_APGK_2460844.png

2071:在进行存储数据的时候 肯定是需要记录journal的,也就是当数据进行写入的时候需要写到journal中一份,当data数据失败的时候可以从journal中进行恢复。所以这里为了安全起见,一般都会采用journal的方式进行保存数据。

2082:如果选择的文件系统(如brfs)时,这种文件系统存在checkpoint机制时,可以采用的方法,可以并行的处理日志与data的写入操作。

2084:准备写入日志的操作_op_journal_transactions。

2086:准备写入数据的操作queue_op。

2088:当选择这条路时,这种文件系统不存在checkpoint机制,所以必须先写入日志,等待日志完成后才可以写入data。

2091:这时申请一个C_JournaledAhead的回调操作,这个操作会在日志完成之后进行回调处理处理时会将data写入磁盘。_op_journal_transactions()这里开始激发写入r日志的操作。

在_op_journal_transactions()中的处理操作如下图:

173516_zo44_2460844.png

0266:判断自然要进行写入journal的。

0271:循环处理Transaction,当然此时假设只有一个Transaction。

0274:解析每一个Transaction的操作。

0278:获取每个操作的数据长度。

0279:获取每个操作的数据段偏移。

0281:将数据操作打包到内存tbl中形成日志数据。

0283:提交日志数据,完成下发任务journal->submit_entry,这里的journal由继承类FileJournal 实现的,所以这里继续调用FileJournal::submit_entry()。

1614:将申请的写日志完成的回调oncommit添加到completions队列中,等待写日志完成后开始调用,这里指的是C_JournaledAhead。

1618:如果当前的写操作队列没有可以处理的日志操作时,写操作线程应该处于睡眠的状态,所以需要唤醒这个写线程。

1620:开始唤醒写日志的线程。

1622:将本次需要写入的buffer统计到写线程的队列writeq中。

接下来就交给了写日志的线程FileJournal::write_thread_entry()中开始处理。这个写操作的线程就是要把数据写入到文件当中,完成后调取completions中对应的成员进行回调,然后调到C_JournaledAhead->finish() ,再进入FileStore::_journaled_ahead()中,这个函数分为了两个部分,一部分是将data操作提交到op_wq队列,另外一个就是对于journal操作完成的回调处理。

173516_1k8f_2460844.png

2178:将操作的op添加到FileStore:: op_wq中,然后等待写数据的线程将数据写入文件。

2181:先解析出在日志完成之后需要回调的那些操作,形成to_queue队列。

2187:如果这个to_queue队列不是空的队列,则交给ondisk_finisher队列进行处理。

2185:将日志保存完成的回调ondisk交给ondisk_finisher,后续有finisher线程处理,这里的ondisk注册回调为C_OSD_OnOpApplied。

 

 

10. 上面说道op_wq队列,将写data的操作加入到了op_wq中之后,这里会触发写data的线程。

data的写操作线程OpWQ::_process(),再调到FileStore::_do_op()函数中,再到FileStore::_do_transactions()函数、FileStore::_do_transaction()函数调用。

 175318_lihV_2460844.png

2529:这里解析这个操作,肯定是一个OP_write的操作。

2531:解析文件目录。

2532:解析文件的名字。

2533:解析要写入文件的位置。

2534:解析要写入文件的数据长度。

2537:解析要写入文件的数据。

2541:根据上面解析的信息,开始调用FileStore::_write 将数据真正的写入文件中的正确位置。当完成写任务后开始进行逐级回调。

OpWQ::_process()操作 对应的就是 FileStore::_finish_op()。在FileStore::_finish_op中主要做的就剩下一件事儿了,就是将请求进行回调处理。

175413_J8A3_2460844.png

1998:开始回调sync的操作,暂时没有涉及到快照克隆等 不需要考虑。

2001:准备开始回调onreadable的参数,这里回调注册的为C_OSD_OnOpCommit。

2002:进行其他操作的回调处理。

 

七、请求的回调处理

11. 到目前为止已经完成了journal的写入操作和data的写入操作。他们分别对应的回调为C_OSD_OnOpApplied,C_OSD_OnOpCommit。他们做的事情差不多,接下来先看看C_OSD_OnOpApplied的操作是怎样的

C_OSD_OnOpApplied->finish()  调取pg->op_applied(),最后到ReplicatedBackend::op_applied()

173517_pvND_2460844.png

0667:开始删除在等待队列中的本端osd序号。这样表示本端已经处理完成了。

0670:检查是不是所有的等待在waiting_for_applied队列上的osd都完成了操作。这时可以进行完全回调。这里的on_applied注册的是C_OSD_RepopApplied回调。

0676:这里开始检查是不是所有的waiting_for_applied、waiting_for_commit队列中都已经处理完成则done成功,那时就可以删除in_progress_ops队列的ops了。

 

12. 接续C_OSD_RepopApplied 逐渐的想客户端进行调用。这里开始调用ReplicatedPG::repop_all_applied(),在这里会设置repop->all_applied 为ture。然后调用eval_repop进行处理。

173517_nehO_2460844.png

7571:查看是否存在请求需要回调。

7574:开始循环处理这里的请求,然后将请求发还给客户端。

7578:恢复开始接收到请求的MOSDOp的操作。

7579:建议对应的应答消息,这个应答消息与接受到的请求相对应。

7582:将请求发还给客户端。

 

13. 以上就是整理osd来处理数据写操作请求的全部内容和流程了。

 

 

总结:

1.这里的流程太多了就不再细说了,请参考下面的全部结构图。

2.osd 预先运行进程 ceph_osd.cc中的main开始。

3.osd结构中申请了管理消息的SimpleMessager模块,该模块中存在一个叫做Accepter模块,该模块专门用来监听端口等待客户端进行连接的。然后交给reader,这时reader将已经建立的连接中开始读取message。

4.将message转化为OpRequest,并且交到dispath队列中。

5.由message等信息 转化得知pool信息和PG的信息,然后把message交给PG进行处理。

6.将OpRequest添加到osdservice->op_wq队列中,该队列中会有处理的线程,线程会将继续处理这个OpRequest的。

7.交给ReplicaPG处理 创建repop 所有的applied与commit的管理。

8.解析出OpRequest中的所有op与data。把这些数据用一个叫做Transaction的结构进行管理。

9.有transaction的处理开始,将replica osd的操作发送给副本。

10.本端开始处理journal的写入操作,加入到writeq队列,该队列有独立线程完成写入的操作。

11.写journal完成后,再将要写入的data添加到op_wq中,该队列同样存在一个线程进行处理,将数据最终的保存到文件中。

 

175908_ZczD_2460844.png

175908_lnb1_2460844.png

175909_VD8Y_2460844.png

 

转载于:https://my.oschina.net/u/2460844/blog/534390

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值