查看原文
其他

由一次slow-request浅谈Ceph scrub原理

基础云平台团队 京东科技技术说 2022-03-15
Ceph

作者:耿纪超&陈剑飞

本文由京东数科基础云团队原创,转载请获得授权


01背 景 介 绍
Ceph是一款开源分布式存储系统,其具有丰富的功能和高可靠、高扩展性,并且提供统一存储服务,既支持块存储RBD,也支持对象存储RadosGW和文件系统CephFS,被广泛应用在私有云等企业存储场景。

在京东数科内部,Ceph也被广泛应用,用来支撑公司基础存储需求,并且支撑部分关键业务,随着数据量和集群规模逐渐扩大,在日常维护中,我们经常遇到各种异常情况,其中频次较多的就是慢请求slow-request,慢请求会导致性能抖动,直接影响集群的稳定性,需要谨慎对待。前段时间我们再一次遇到slow-request,问题比较典型,主要和scrub有关,现将该问题的定位过程以及scrub相关的原理优化整理如下,本文内容都是基于Luminous版本。


02Slow-request问题说明

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的速度和调整其运行时间,本文后续章节会有详细说明,这里就不再展开。


03Ceph scrub简述

Scrub主要是为了检查磁盘数据的静默错误,在英文中被称为:Silent Data Corruption,大家都知道硬盘最核心的使命是正确的读取和写入数据,在读、写失败的情况下及时抛出异常,但是在某些场景下,写入成功,读取的时候才发现数据已经损坏,这就是静默错误,一般静默错误产生原因有这几种:

  • 硬件错误

  • 传输过程信噪干扰

  • 软件bug

  • 固件bug

Ceph的scrub类似于文件系统的 fsck,对于每个 pg,Ceph生成所有对象的列表,并比较每个对象多个副本,以确保没有对象丢失或数据不一致。Ceph的scrub主要分两种:

(1)Scrub:对比对象各个副本的元数据来检查元数据的一致性;

(2)Deep scrub:检查对象各个副本数据内容是否一致,耗时长,占用IO资源多;


scrub 对于数据一致性十分重要,但是由上文可知,它会对集群的性能会带来一些负面的影响,主要是会和业务IO竞争资源。下面首先分析下scrub的基本原理,然后介绍具体的优化方案。


04Ceph 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   消费者流程


(1)由前文可知,scrub是已pg为单位的,而每个PG的scrub启动是由该PG所在的主OSD启动执行;
(2)在比较大的集群规模下,每个PG中可能承载了几十万的对象数,在进行scrub过程中会根据对象名的哈希值的部分作为提取因子,选择一部分对象进行校验,这部分被选中的对象称为chunky,这也是为什么ceph被称为chunkyscrub的原因;
(3)scrub的发起者即pg所在的主OSD,向其它副本OSD发起进行数据校验的消息,根据scrub的类型不同,需要校验的数据也不同:
  • scrub 读取对象的元数据信息,检查对象是否一致

  • deep scrub 读取对象的数据并做checksum来检查数据是否一致

(4)校验信息统一放到ScrubMap中,发起者通过比较ScrubMap中的信息,判断对象是否一致,不一致的信息会上报给monitor。


从流程也可以看出几个细节:

(1)chunky scrub里面的object会被锁住,写请求受到影响;

(2)osd_scrub_sleep是控制两次chunkyscrub的间隔,从而会拉长一次scrub(这里包括deepscrub 和 scrub)的时间,睡眠是通过定时器实现的。


05Ceph 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校验值。


其次,可以总结出参数调优的主要方向:
(1)首先,确定osd_scrub_max_interval,这个时间很重要,如果设置的太小就会导致业务在正常时间IO受到影响,并且此时osd_scrub_load_thredhold、begin hour、end hour都不会生效,osd_scrub_sleep时间生效。通过ceph pg dump --format json | jq -r '.pg_stats[] | [.last_clean_scrub_stamp ] | @csv' | sort –r命令查看线上环境,可以看到较多scrub任务在设置的scrub时间之外执行,所以这个参数需要调整,调整到一个月之后,没有出现类似现象;
(2)其次,osd_scrub_end_hour的设置,如果业务7点开始使用,那么如果设置成7点,可能最后一个任务没有执行完,deep scrub任务会持续到7点之后,具体取决于最后一个pg scrub的执行时间,那么这个值可以再提前一点;
(3)然后,osd_scrub_load_threshold的设置,这个值默认0.5,假如在begin hour和endhour之间,如果cpu高于这个值,那么是不会执行scrub的,这个值太小会导致在正常规定时间不能执行scrub,从而影响deadline,一旦超过deadline会出现第一种情况;
(4)bucket index对应的shard对象不宜过大,如果太大,这个对象在执行deep scrub的时候,会影响bucket级别的对象无法写入。这个参数通过修改osd_scrub_chunk_min,osd_scrub_chunk_max,可以缓解但是如果一个shard对象太多,仍然会比较严重;
(5)osd_scrub_sleep参数可以降低客户端在scrub时间内的感知,代价是增加了一次scrub任务的时间,所以如果修改这个参数,仍然需要确保一个osd_scrub_max_interval周期里,所有的pg能够被正确执行完scrub任务。


总结一下,总体的优化思路就是首先确保scrub任务不会在osd_scrub_begin_hour和 osd_scrub_end_hour之外的时间执行,其次就是在osd_scrub_begin_hour和osd_scrub_end_hour之间,尽可能减少业务的感知。


2.调度优化


由上文的分析可知,通过调整参数,可以解决一部分问题,但是如果某些参数设置的不合理,仍然会导致在scrub任务在非规定的时间内运行,影响正常的任务,scrub可控性仍然存在一些问题,并且在618和双11大促期间,需要完全避免执行scrub任务。针对这种情况,关闭使用ceph osd set noscrub;ceph osd set nodeep-scrub命令关闭了ceph的scrub机制,采用了自研程序进行scrub任务调度。 


图6  scrub调度流程


主要的逻辑说明:

(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)睡眠等待下一轮检查。



往期好文推荐
KuAI平台 | 模型在线推理系统的高可用设计
数字中国创新大赛-算法赛道冠军技术方案曝光!
他们凭什么成为京东数科最强研发战队?
独家解密:数科智能反欺诈平台如何为您保驾护航?
AutoML系列 | 04-AutoML系统中的元知识迁移应用
面对黑客攻击,京东数科WAF建设这样做!
京东金融云测平台方案揭秘
业务与系统的傲慢与偏见

关注技术说,我们只凭技术说话!



点在看会少个bug哦👇

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存