由一次slow-request浅谈Ceph scrub原理
作者:耿纪超&陈剑飞
本文由京东数科基础云团队原创,转载请获得授权
1.问题分析
某晚19:13左右接到手机slow-request告警:个别请求响应时间比较高,有50s左右,之后登陆上机器查看集群状态,见下图:
图1 Ceph健康状态
集群状态是OK的,仅发现了有两个pg正在做deep-scrub(Ceph静默检查程序,主要用来检查pg中对象数据不一致,本文后续章节有详细介绍),这两个pg属于业务数据pool(对象元数据、对象数据、日志等数据是存储在不同的pool中的),另外,发现运行scrub的时间段是23:00~06:00。
图2 Ceph配置文件
报警时间和scrub的运行时间是对得上的,从pg对应的osd日志上也能确认这一点:
图3 Ceph osd日志
可以看到rocksdb正在进行compacting,说明业务写请求比较多。
所以可确定本次slow-request的原因:大量的用户写入操作导致rocksdb进行compacting,加上deep-scrub进一步引发底层IO资源的竞争,最终导致用户请求超时。
2.问题解决
当时紧急处理方法就是将deep-scrub关掉,后续慢请求就不再出现了。但是deep-scrub直接影响数据的一致性,不能一直关掉,我们优化的思路就是控制deep-scrub的速度和调整其运行时间,本文后续章节会有详细说明,这里就不再展开。
Scrub主要是为了检查磁盘数据的静默错误,在英文中被称为:Silent Data Corruption,大家都知道硬盘最核心的使命是正确的读取和写入数据,在读、写失败的情况下及时抛出异常,但是在某些场景下,写入成功,读取的时候才发现数据已经损坏,这就是静默错误,一般静默错误产生原因有这几种:
硬件错误
传输过程信噪干扰
软件bug
固件bug
Ceph的scrub类似于文件系统的 fsck,对于每个 pg,Ceph生成所有对象的列表,并比较每个对象多个副本,以确保没有对象丢失或数据不一致。Ceph的scrub主要分两种:
(1)Scrub:对比对象各个副本的元数据来检查元数据的一致性;
(2)Deep scrub:检查对象各个副本数据内容是否一致,耗时长,占用IO资源多;
scrub 对于数据一致性十分重要,但是由上文可知,它会对集群的性能会带来一些负面的影响,主要是会和业务IO竞争资源。下面首先分析下scrub的基本原理,然后介绍具体的优化方案。
1.Scrub参数说明
在分析代码前,首先说明一下Ceph比较重要的概念和一些常用的参数。
以上是Ceph中重要的组件和概念,下面是scrub常用的参数:
下面介绍scrub的详细流程,scrub是一个生产者消费者模型,生产者生成scrub job,消费者负责消费scrub job。
2.Scrub任务的产生
生产者由定时任务触发,具体流程如下:
图4 生产者流程
主要流程说明如下:
(1)首先判断osd正在执行scrub的pg数是否大于osd_max_scrubs,如果大于则返回;
(2)是否达到pg的预期scrub时间,如果没达到则返回,预期的scrub时间是由上次scrub的时间、osd_scrub_min_interval、osd_scrub_interval_randomize_ratio参数决定;
(3)判断当前时间是否大于deadline,如果小于,则判断是否在osd_scrub_begin_hour和osd_scrub_end_hour,如果处于则判断集群负载是否在osd_scrub_load_thredhold之下,如果不满足则等待时间再重试。如果当前时间大于deadline,则不会判断时间和负载,强制执行scrub任务,到这一步仍然是osd_scrub_min_interval和osd_scrub_max_interval起作用;
(4)一个scrub任务最后会经过判断,从而决定这个scrub任务到底是scrub还是deepscrub,接下来我来分析一下这个判断流程;
(5)在主osd判断deep scrub的时间有没有超过deep_scrub_interval,如果超过,这个任务会是deep scrub;
scrubber.time_for_deep =ceph_clock_now() >=
info.history.last_deep_scrub_stamp+ deep_scrub_interval;
(6)如果没过期,这时osd_deep_scrub_randomize_ratio这个参数会起作用:
deep_coin_flip = (rand()% 100) < cct->_conf->osd_deep_scrub_randomize_ratio* 100;
scrubber.time_for_deep= (scrubber.time_for_deep || deep_coin_flip);
(7)首先判断osd正在执行scrub的pg数是否大于osd_max_scrubs,如果大于则返回;
(8)之后就是具体将任务加到队列,这里是用统一的数据结构表示scrub和deep scrub任务;
(9)获取deep scrub和scrub的标志位,如果设置了no deep scrub或者no scrub,则不执行相应任务。
从代码中看到的几个细节:
(1)预期的scrub时间,是由 last_scrub_time + min_interval + random_postpone_time,从而错开了pg的开始时间,这里起到了消峰的作用,并且随着系统的运行,这个时间是会错开的:
sched_time += scrub_min_interval;
double r = rand() / (double)RAND_MAX;
sched_time += scrub_min_interval *cct->_conf->osd_scrub_interval_randomize_ratio * r;
deadline += scrub_max_interval;
(2)last_scrub_time + osd_scrub_max_interval作为deadline,所以如果osd_scrub_max_interval设置的不对,就会导致系统在业务的正常时间出现deep scrub和scrub,并且不会受到load thredhold的限制;
(3)osd_deep_scrub_randomize_ratio这个参数会把普通的scrub任务变成deep scrub任务,但是只要max interval设置的合理,是有均衡deep scrub任务的作用的。
3.Scrub任务的消费
消费者是由线程池控制,具体流程如下:
图5 消费者流程
scrub 读取对象的元数据信息,检查对象是否一致
deep scrub 读取对象的数据并做checksum来检查数据是否一致
(4)校验信息统一放到ScrubMap中,发起者通过比较ScrubMap中的信息,判断对象是否一致,不一致的信息会上报给monitor。
从流程也可以看出几个细节:
(1)chunky scrub里面的object会被锁住,写请求受到影响;
(2)osd_scrub_sleep是控制两次chunkyscrub的间隔,从而会拉长一次scrub(这里包括deepscrub 和 scrub)的时间,睡眠是通过定时器实现的。
了解了scrub的原理,下面从如下两个方面来进行介绍scrub优化方案,一种是调整Ceph的相关参数,一种是自研的scrub调度策略。
1.参数优化
首先,可以解决之前的几个迷惑的问题:
(1)针对正在执行的scrub任务,即便时间超过配置的osd_scrub_end_hour 后,仍然会执行,新的任务在OSD::sched_scrub()开始时 OSD::scrub_time_permit返回 false不会执行;
(2)如果osd_scrub_max_interval配置的不合理,则会导致scrub任务的deadline超出,那么就会导致在规定时间外的任意时间出现scrub/deep scrub,从而影响业务IO;
(3)一个scrub任务到底是deep还是普通的scrub,和osd_deep_scrub_interval还有osd_deep_scrub_randomize_ratio参数有关,超过osd_deep_scrub_interval的一定是deep,否则按照osd_deep_scrub_randomize_ratio对应的概率转换成deep;
(4)不管是deep还是shallow scrub任务,执行的逻辑都是一样的,函数也一样,状态机也一样,唯一不同的是ScrubMap如果是deep会额外的请求CRC校验值。
2.调度优化
主要的逻辑说明:
(1)通过rados连接ceph集群,如果连接失败则返回对应错误信息;
(2)进入循环主流程,判断当前日期是否是特殊日期,例如618,双11,如果是则睡眠一定时间继续循环;
(3)检查执行时间和最大任务数是否满足执行条件,如果不满足,则睡眠等待下一轮检查,一般都会设置deep scrub时间范围为晚上23点到第二天早上7点;
(4)通过pg dump获取当前正在执行scrub的pg信息;
(5)如果当前执行scrub的任务数大于所设置的maxscrubs,则睡眠一段时间继续循环
(6)对于非scrubbing状态的PG,按照last_deep_scrub_stamp从远及近排序,作为备选PG组;
(7)循环检查备选PG,对满足以下条件的PG执行deep scrub操作:
PG的last_deep_scrub_stamp在1周之前;
PG的主osd不处于scrubbing状态;
当前deep scrub任务小于最大任务数;
(8)如果当前deep scrub任务达到最大任务数,跳出循环。
(9)睡眠等待下一轮检查。
关注技术说,我们只凭技术说话!
点在看会少个bug哦👇