由一次slow-request浅谈Ceph scrub原理
一、背景介绍Ceph是一款开源分布式存储系统,其具有丰富的功能和高可靠、高扩展性,并且提供统一存储服务,既支持块存储RBD,也支持对象存储RadosGW和文件系统CephFS,被广泛应用在私有云等企业存储场景。在京东数科内部,Ceph也被广泛应用,用来支撑公司基础存储需求,并且支撑部分关键业务,随着数据量和集群规模逐渐扩大,在日常维护中,我们经常遇到各种异常情况,其中频次较多的就是慢请求slow-
一、背景介绍
Ceph是一款开源分布式存储系统,其具有丰富的功能和高可靠、高扩展性,并且提供统一存储服务,既支持块存储RBD,也支持对象存储RadosGW和文件系统CephFS,被广泛应用在私有云等企业存储场景。
在京东数科内部,Ceph也被广泛应用,用来支撑公司基础存储需求,并且支撑部分关键业务,随着数据量和集群规模逐渐扩大,在日常维护中,我们经常遇到各种异常情况,其中频次较多的就是慢请求slow-request,慢请求会导致性能抖动,直接影响集群的稳定性,需要谨慎对待。
前段时间,我们再一次遇到slow-request,问题比较典型,主要和scrub有关,现将该问题的定位过程以及scrub相关的原理优化整理如下,本文内容都是基于Luminous版本。
二、Slow-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的速度和调整其运行时间,本文后续章节会有详细说明,这里就不再展开。
三、Ceph scrub简述
Scrub主要是为了检查磁盘数据的静默错误,在英文中被称为:Silent Data Corruption,大家都知道硬盘最核心的使命是正确的读取和写入数据,在读、写失败的情况下及时抛出异常,但是在某些场景下,写入成功,读取的时候才发现数据已经损坏,这就是静默错误,一般静默错误产生原因有这几种:
- 硬件错误;
- 传输过程信噪干扰;
- 软件 bug;
- 固件 bug。
Ceph的scrub类似于文件系统的 fsck,对于每个 pg,Ceph生成所有对象的列表,并比较每个对象多个副本,以确保没有对象丢失或数据不一致。Ceph的scrub主要分两种:
- Scrub:对比对象各个副本的元数据来检查元数据的一致性;
- Deep scrub:检查对象各个副本数据内容是否一致,耗时长,占用IO资源多;
scrub 对于数据一致性十分重要,但是由上文可知,它会对集群的性能会带来一些负面的影响,主要是会和业务IO竞争资源。下面首先分析下scrub的基本原理,然后介绍具体的优化方案。
四、Ceph scrub原理
1.Scrub参数说明
在分析代码前,首先说明一下Ceph比较重要的概念和一些常用的参数。
组件名称 | 组件作用 |
---|---|
OSD | 英文全称是Object Storage Device,主要功能是存储数据、复制数据、平衡数据、恢复数据,为了数据可靠性,一般数据会有多个副本,分别存储在不同的OSD上。 |
Monitor | 负责监视Ceph集群,维护Ceph集群的健康状态。 |
PG | 英文全称是Placement Group,是用于放置object的一个载体,每个对象会通过CRUSH算法映射到某个PG,同时PG也会根据副本策略映射到一定数量的OSD上。 |
以上是Ceph中重要的组件和概念,下面是scrub常用的参数:
参数名称 | 参数作用 |
---|---|
osd_deep_scrub_interval | deep scrub周期,,单位是秒,默认是604800,也就是一周 |
osd_deep_scrub_randomize_ratio | Scrub变为deepScrub的概率,主要是为了防止deep scrub同时进行出现践踏,默认值0.15 |
osd_max_scrubs | 单个OSD做scrub的最大并发,默认值1 |
osd_scrub_begin_hour | Scrub每天开始的时间,单位小时,默认值0 |
osd_scrub_end_hour | Scrub每天结束的时间,单位小时,默认24 |
osd_scrub_begin_week_day | Scrub每周开始的时间,单位天,默认0 |
osd_scrub_end_week_day | Scrub每周开始的时间,单位天,默认7 |
osd_scrub_sleep | 两个PG Scrub OP间休息时间,默认0 |
osd_scrub_interval_randomize_ratio | 增加scrub调用时的随机性,防止大量scrub同时进行,默认值0.5 |
osd_scrub_max_interval | Scrub的最大间隔,默认一周 |
osd_scrub_min_interval | Scrub的最小间隔,默认一天 |
osd_scrub_load_threshold | 当单颗CPU的负载小于该值时允许进行scrub,默认值0.5 |
下面介绍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还是deep scrub,接下来我来分析一下这个判断流程;
(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被称为chunky scrub的原因;
(3)scrub的发起者即pg所在的主OSD,向其它副本OSD发起进行数据校验的消息,根据scrub的类型不同,需要校验的数据也不同:
- scrub 读取对象的元数据信息,检查对象是否一致
- deep scrub 读取对象的数据并做checksum来检查数据是否一致
(4)校验信息统一放到ScrubMap中,发起者通过比较ScrubMap中的信息,判断对象是否一致,不一致的信息会上报给monitor。
从流程也可以看出几个细节:
(1)chunky scrub里面的object会被锁住,写请求受到影响;
(2)osd_scrub_sleep是控制两次chunky scrub的间隔,从而会拉长一次scrub(这里包括deep scrub 和 scrub)的时间,睡眠是通过定时器实现的。
五、Ceph 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)睡眠等待下一轮检查。
六、参考文档
- Ceph官网:https://docs.ceph.com/docs/master/
- Ceph源码:https://github.com/ceph/ceph/tree/luminous
本文作者:京东数科 耿纪超&陈剑飞
文章来源:“京东数科技术说”微信公众号
原文链接: https://mp.weixin.qq.com/s/Iv9H6DDWscqFg_OkrLJmXA.
更多技术干货欢迎关注“京东数科技术说”微信公众号,我们只凭技术说话!
更多推荐
所有评论(0)