ceph L版本相对于J版本在scrub机制上做的优化

1. 优化改动点

2. 关键数据结构

3. scrub流程变化

1. 优化改动点

主要解决deep scrub对业务影响的3个具体问题:

  • 对象数据较大的情况做deep scrub对业务的影响
  • 对象存储的omap数据较大的情况做deep scrub对业务的影响
  • scrub过程阻塞客户端的I/O

因此为了减少上述3种问题对业务的影响,L版本做了如下修改

  • 每次deep scrub读取对象的data可以按照条带进行,参考配置参数osd_deep_scrub_stride,默认是512字节
  • 每次deep scrub读取对象的omap的kv个数最多为osd_deep_scrub_keys,默认1024个key
  • 为了记录上述分段scrub的过程,新增了ScrubMapBuilder结构体
  • 每个副本将scrub过程拆分了多段;比如objects_list_range完成之后,pg会重新加入scrub队列等待下一次调度;再比如当某个对象的omap读取条数超过一定的数量之后,pg也会重新加入scrub队列等待重新调度
  • 从副本接收到scrub请求处理由以前的replica_scrub直接处理改为放到scrub状态机中处理,同时添加了一个scrub状态为Scrubber::BUILD_MAP_REPLICA
  • 支持scrub抢占

2. 关键数据结构

2.1 支持scrub分段执行

为了支持scrub分段执行即打断了scrub操作的原子性,L版本新增了ScrubMapBuilder数据结构,该结构主要是为了记录scrub的中间状态,比如scrub中断后下一次从那个对象那个offset以及那个omap key开始遍历查询等
ScrubMapBuilder结构体说明:

元素说明
deep标记是否为deep scrub,实际上该结构主要是为了优化deep scrub实现的
vector<hobject_t> lschunk scrub对象列表
size_t pos = 0标记要scrub的对象
int64_t data_pos = 0标记当前正在deep scrub的对象读取data的offset,当进行下一个object的deep scrub操作时候会清0
string omap_pos标记当前正在deep scrub的对象读取omap的offset,当进行下一个object的deep scrub操作时候会清0
int ret = 0
bufferhash data_hash, omap_hash标记当前正在deep scrub的对象已读取的数据和omap
uint64_t omap_keys = 0对象的key条数
uint64_t omap_bytes = 0对象的keyvalue的大小

scrub对每个对象遍历的结果是记录在ScrubMap结构体中的;
ScrubMap结构体说明如下:

元素说明
object该成员主要是记录对象所在副本scrub结果信息,比如attrs、omap_digest、data_digest以及对象的读取attrs、data状态
map<hobject_t,object> objects此次参与的scrub对象列表
eversion_t valid_through本次参与scrub最新的pglog版本号
2.2 支持scrub抢占的设计

scrub支持抢占需要解决以下4个问题:

  • 未实现抢占功能前,scrub过程是原子性的,从INACTIVE到WAIT_REPLICAS状态过程都是持有pg锁的并且所有对象数据都是已经读取完成的,因此为了实现抢占功能需要破坏原子性
  • 抢占会导致副本间数据校验时出现不一致
  • 抢占触发的时机
  • 允许抢占的条件
2.2.1 scrub原子性破坏
  • 将scrub过程进行分段进行,比如遇到大data以及大omap的对象时,每次scrub只读取部分数据并记录当前scrub的中间状态,然后退出等待重新调度
2.2.2 scrub抢占防止出现校验不一致的问题解决方法
  • 在scrub正在进行的过程中,有客户端的I/O抢占进来了,那么有可能主pg上更新了数据,从副本的数据还未更新,导致两边scrub读取的数据不一致,那么就会出现inconsisent的状态,对于这种抢占的scrub需要重新触发新的NEW_CHUNCK并放弃上一次scrub的结果
2.2.3 scrub抢占条件

1)抢占只能发生在抢占时间窗内,并且允许抢占才可以;

抢占时间窗

在这里插入图片描述
在这两个scrub状态之间内都是可以发生抢占的,但一般的主要发生的抢占是在调用build_scrub_map_chunk过程中,因此该过程有可能不是原子性的;

2)抢占触发的时机:
osd在处理客户端的写请求调用write_blocked_by_scrub会判断如果当前的写object正在做scrub,并且允许抢占,则客户端的I/O则可以被处理

bool PG::write_blocked_by_scrub(const hobject_t& soid)
{
  if (soid < scrubber.start || soid >= scrubber.end) {
    return false;
  }
  if (scrub_can_preempt) {
    if (!scrub_preempted) {
      dout(10) << __func__ << " " << soid << " preempted" << dendl;
      scrub_preempted = true;
    } else {
      dout(10) << __func__ << " " << soid << " already preempted" << dendl;
    }
    return false;
  }
  return true;
}

而这里的scrub_can_preempt的值就是在chunky_scrub在scrub抢占时间窗内被设置的,该值取决于scrubber.preempt_left的值即该值大于0说明还可以被抢占;

scrub_can_preempt = scrubber.preempt_left > 0;

那么scrub_can_preempt什么设置为false呢?

当主pg的scrub状态变为WAIT_REPLICAS则会将scrub_can_preempt设置为false,因此可以抢占的时间窗为NEW_CHUNCK–>WAIT_REPLICAS;

3. scrub关键流程变化

3.1 scrub过程
PrimaryA B C INACTIVE NEWCHUNK BUILD_MAP _request_scrub_map BUILD_MAP_REPLICA requeue_scrub build_scrub_map_chunk done _request_scrub_map BUILD_MAP_REPLICA requeue_scrub build_scrub_map_chunk done WAIT_REPLICAS CEPH_OSD_OP_SCRUB_MAP CEPH_OSD_OP_SCRUB_MAP COMPARE_MAPS FINISH INACTIVE PrimaryA B C
3.2 scrub抢占发生时,主pg状态机变化过程:

在这里插入图片描述

3.3 build_scrub_map_chunk的过程

在这里插入图片描述
从上面的过程我们可以知道,有三种情况会导致scrub过程被中断,分别为

  • 开始获取scrub对象列表时,返回-EINPROGRESS等待chunky_scrub将该pg重新加入scrub队列,待下一次被调度,代码如下
void PG::chunky_scrub(ThreadPool::TPHandle &handle) {
    ...
    case PG::Scrubber::BUILD_MAP:
    ...
    ret = build_scrub_map_chunk(
	  scrubber.primary_scrubmap,
	  scrubber.primary_scrubmap_pos,
	  scrubber.start, scrubber.end,
	  scrubber.deep,
	  handle);
    if (ret == -EINPROGRESS) {
	  requeue_scrub();
	  done = true;
	  break;
	}
}
  • 遍历对象时读取数据如果对象大小大于osd_deep_scrub_stride则会分多次处理,该过程返回-EINPROGRESS等待chunky_scrub将该pg重新加入scrub队列,待下一次被调度
  • 遍历对象时读取数据如果对象omap的数量大于osd_deep_scrub_keys则会分多次处理,该过程返回-EINPROGRESS等待chunky_scrub将该pg重新加入scrub队列,待下一次被调度

注意:重新加入队列被调度意味会释放pg锁

3.4 scrub抢占的一致性校验异常处理

发生了抢占如何保证scrub校验不会出错,因为能够抢占意味着scrub期间对象数据有可能发生了更改,那么ceph时如何处理这种情况的呢?

刚才上面我们说过scrub最有可能被中断的阶段是build_scrub_map_chunk即PG::Scrubber::BUILD_MAP状态,那么当发生抢占时pg的scrub状态机时如何处理的?如下代码所示:

// build my own scrub map
	if (scrub_preempted) {
	  dout(10) << __func__ << " preempted" << dendl;
	  scrubber.state = PG::Scrubber::BUILD_MAP_DONE;
	  break;
	}
	

scrub_preempted该值是在调用write_blocked_by_scrub进行设置为true,意味着当前scrub被抢占了。如果被抢占了,scrub状态机进入PG::Scrubber::BUILD_MAP_DONE状态,然后等待所有的从副本的ScrubMap结果返回,最终重新进入PG::Scrubber::NEW_CHUNK状态,该状态会将从副本返回的ScrubMap清除掉;

  case PG::Scrubber::NEW_CHUNK:
	scrubber.primary_scrubmap = ScrubMap();
	scrubber.received_maps.clear();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值