当PG完成了Peering过程后,处于Active状态的PG就已经可以对外提供服务了。如果该PG的各个副本上有不一致的对象,就需要进行修复。Ceph的修复过程有两种:Recovery和Backfill,
这里我们讨论Recovery的过程。Peering的过程在代码实现上是通过Boost状态机在各种状态下触发各种事件来执行相应的行为。PG完成Peering过程后,就处于activate状态,
如果需要Recovery,就产生DoRecovery事件,触发修复操作。
整个recovery过程比较复杂,我将代码中比较常见的一些变量和函数进行补充说明,以后逐渐完善
head对象:也就是对象的原始对象,该对象可以进行写操作。
snap对象:对某个对象做快照后,通过cow机制copy出来的快照对象只能读,不能写。
RecoveryHandle: 同一组消息中恢复多个对象的一个结构体。
线程池ThreadPool的实现符合生产者-消费者模型,这个模型解除生产者消费者间的耦合关系,生产者可以专注处理制造产品的逻辑而不用关心产品的消费。Thread类是ThreadPool中的消费者,
它封装pthread API函数,对外提供Thread::entry()作为线程的入口函数。Thread只是对消费者的抽象,WorkThread才是ThreadPool的具体消费者,
它实现Thread接口函数,设置线程的入口函数为ThreadPool的worker()方法。WorkQueue_类在ThreadPool中代表产品接口,对外表现为一个队列,队列中存储待处理的数据元素。
消费者WorkThread对它的处理主要分成三个步骤:首先调用_void_dequeue()方法获取队列元素,然后通过_void_process()方法处理元素,最后使用_void_process_finish()方法进行收尾工作。
具体调用过程可以参看WorkTread的线程入口函数,也就是ThreadPool::work()方法。
PGQueable类使用了boost的variant类型去定义了成员变量 qvariant,variant类型支持多种方式访问,其中一种就是通过访问者模式来访问。
使用的boost库中的结构:boost::variant boost::static_visitor boost::optional boost::apply_visitor。
内部结构体 RunVis继承了boost::static_visitor,实现一个variant实例的访问器,且重载了不同类型的函数调用方法"()"
比如Recovery过程实现的重载操作符函数为:void PGQueueable::RunVis::operator()(const PGRecovery &op),该函数通过调用do_recovery()方法,进入到Recovery过程。
Ceph中bufferlist的设计还是有些复杂的,其中包含三个主要的内buffer::raw(bufferraw)、 buffer::ptr(bufferptr)和buffer::list(bufferlist)。这三个类都定义在common/buffer.h 中,
都是buffer类的内部类,而buffer类本身没有任何内容,只起到了一个命名空间的作用。
这三个类的职责各有不同:
buffer::raw:对应一段真实的物理内存,负责维护这段物理内存的引用计数nref和释放操作。
buffer::ptr:对应Ceph中的一段被使用的内存,也就是某个bufferraw的一部分或者全部。
buffer::list:表示一个ptr的列表(std::list),相当于将N个ptr构成一个更大的虚拟的连续内存。
常用集合数据类型的序列化已经由Ceph实现,位于include/encoding.h中,包括以下集合类型:
状态机进入Recoverying状态,将PGRecovery Op入ShardOpWq队列,当线程池中的线程取出一个任务出来后,
其工作队列的线程池的处理函数
process()
调用qi->run(osd, pg, tp_handle)函数,而该run函数实际上封装了boost::apply_visitor()方法,该方法可以获取原始的PGRecovery对象,
由于该对象重载了()操作符,该操作符会调用do_recovery()方法来执行实际的数据修复操作。接着调用ReplicatedPG::start_recovery_ops()开始recovery操作。
上面进行了三次恢复的操作,第一次恢复replicas副本,第二次恢复primary 副本,第三次恢复replicas副本。
如上所述start_recovery_ops函数会调用recovery_primary及recovery_replicas来修复主从副本。recovery_primary会检查所有OSD是否拥有该版本的对象,
如果有就加入到missing_loc记录该版本的位置信息,由后续修复继续来完成。同时会调用pgbackend->run_recovery_op()将PullOp信息或者PushOp信息封装的消息通过
send_push或者send_pulles发送给相关的OSD。当主OSD把对象推送给缺失该对象的从OSD后,从OSD将上述接受到的PUshOP信息通过调用函数handle_push来实现
数据写入工作,从而来完成该对象的修复。当主OSD给从OSD发起拉取对象的请求来修复自己缺失的对象时,将上述接受到的PullOp信息通过调用函数handle_pulls来进行对象的修复。
整个rocovery过程的流程图大致如下三幅图所示: