Ceph Recovery分析

来源:互联网 发布:双十一淘宝实时营业额 编辑:程序博客网 时间:2024/06/04 17:42

  • 1.ceph recovery 概述

当PG完成了Peering过程后,处于Active状态的PG就已经可以对外提供服务了。如果该PG的各个副本上有不一致的对象,就需要进行修复。Ceph的修复过程有两种:Recovery和Backfill,

这里我们讨论Recovery的过程。Peering的过程在代码实现上是通过Boost状态机在各种状态下触发各种事件来执行相应的行为。PG完成Peering过程后,就处于activate状态,

如果需要Recovery,就产生DoRecovery事件,触发修复操作。

2.相关变量及函数

整个recovery过程比较复杂,我将代码中比较常见的一些变量和函数进行补充说明,以后逐渐完善

  • peer_missing:每个osd需要恢复的object集合 ,保存在peer_missing结构中。
  • needs_recovery_map:本次recovery过程所有需要恢复的object,保存在needs_recovery_map中。
  • missing_loc:这些需要恢复的object可以从哪些osd上拉取数据,保存在 missing_loc结构中。
  • clone_overlap:保存本次clone对象和上次clone对象(或者head对象)的overlap的部分,也就是重叠的部分。
  • head对象:也就是对象的原始对象,该对象可以进行写操作。

  • snap对象:对某个对象做快照后,通过cow机制copy出来的快照对象只能读,不能写。

  • snap_seq或者seq:快照序号,每次做snapshot操作系统都分配一个相应快照序号,该快照序号在后面的写操作中发挥重要作用。
  • snapdir对象:当head对象被删除后,仍然有snap和clone对象,系统自动创建一个snapdir对象,来保存SnapSet信息
  • ObjectContext可以说是对象在内存中的一个管理类,保存了一个对象的上下文信息
  • RecoveryHandle: 同一组消息中恢复多个对象的一个结构体。

  • clone_overlap:写操作导致的和最新的克隆对象重叠的部分
  • interval_set:区间集合,一般由多对(start,len)区间集合构成。
  • insert,intersection_of,union_of,用于对interval_set类型变量进行插入,求交集,并集。
  • encode:ceph中的序列化基本步骤:选获取源目标的大小,将大小先序列化到bufferlist中(整型除外), 然后再把真实数据追加到bufferlist中
  • decode: 反序列化。

3.线程池与工作队列

线程池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()方法。

4. Ceph PGQueable

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过程。

5.Bufferlist及序列化

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中,包括以下集合类型:

  • pair, triple
  • list, set, vector, map, multimap
  • hash_map, hash_set
  • deque
集合类型的序列化方法皆为基于泛型(模板类)的实现方式,适用于所有泛型派生类。
复杂数据类型的序列化数据类型的序列化实现包括两部分:
  1. 在类型内部现实encode方法,
  2. 将类型内部的encode方法重定义为全局方法。

6.Recovery 流程

状态机进入Recoverying状态,将PGRecovery Op入ShardOpWq队列,当线程池中的线程取出一个任务出来后,其工作队列的线程池的处理函数
process()调用qi->run(osd, pg, tp_handle)函数,而该run函数实际上封装了boost::apply_visitor()方法,该方法可以获取原始的PGRecovery对象,
由于该对象重载了()操作符,该操作符会调用do_recovery()方法来执行实际的数据修复操作。接着调用ReplicatedPG::start_recovery_ops()开始recovery操作。
  1.  获取主osd上的missing 统计结果。
  2. 获取主osd上缺失并且需要恢复的object数量。
  3. 获取定位缺失数据中 缺少数据源的object数量。
  4. 如果 主osd上丢失的object 数量 与 无法定位数据源的object数量相同。也就是主osd上丢失的object暂时无法恢复。
  5. 开始先恢复replicas osd上的数据。
  6. 直到 replicas osd上数据全都恢复完毕,或者无数据可以恢复时。
  7. 开始进行 primary osd上的数据恢复。
  8. 如果前面两项都进行了数据恢复,但是仍然有object没有被恢复,则再次恢复replicas上缺失的object。

 

上面进行了三次恢复的操作,第一次恢复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过程的流程图大致如下三幅图所示: