记一次设置norecover标记导致ceph集群业务中断的问题分析

时间:2024-04-03 22:30:34

0. 问题现象

1. 理解norecover的含义

2. 问题分析

3. 结论

【注】本文是基于Hammer版本分析的,jewel版本也会存在同样的问题

0. 问题现象

记一次设置norecover标记导致ceph集群业务中断的问题分析

  • 集群设置了norecover标志后,osd上出现大量的slow op,而这些op都是处于获取对象读写锁失败的状态,进而导致osd的client msg流控资源耗尽,导致业务中断;

  • 重启osd之后马上又出现上面获取对象读写锁失败的现象

【注】
在扩减容,故障恢复以及均衡过程中,当发现部分OSD快被写爆的时候,会暂时设置norecover标志,以便减缓集群恢复的压力

1. 理解norecover的含义

1.1 norecover标记作用

  • norecover标记是记录在osdmap中;
  • osd接收到osdmap发现有设置norecover标志,则会将recovery_tp线程pause掉,进而会导致pg无法继续进行recovery或者backfill恢复;

那么是否意味着业务就不可用了呢,其实不是!ceph osd针对恢复对象的访问做了优化,为了尽快响应业务,当发现当前对象还没开始恢复,则osd的osd_op_tp线程会主动负责起该对象的恢复工作,因此即使设置了norecover的标志也不应该会导致业务完全中断不可用了;正确的理解norecover标志应该是:norecover标志只会影响需要通过recovery_tp线程恢复的pg即通过peering触发的恢复,但不影响客户端触发的恢复流程

2. 问题分析

首先怀疑是因为设置norecover恢复暂停导致恢复对象的读锁一直被占用,导致上层业务io获取不了对象写锁;但是do_op走到获取对象读写锁失败的前提是,该对象已经恢复完了;为啥不会是对象正在写过程中还没完成导致后面的写操作获取写锁失败呢?这是因为对osd对象来说,写写之间以及读读之间是可以并发执行的,但是读写不行,因此这里只有一种可能就是对象恢复完了,但是没有释放读锁导致写锁无法获取。下面继续分析什么场景会出现读锁无法释放的情况

2.1 恢复过程分析

2.1.1 主pg发起恢复请求过程:

记一次设置norecover标记导致ceph集群业务中断的问题分析记一次设置norecover标记导致ceph集群业务中断的问题分析

从上面的流程可以看出:

  • 当恢复是从副本pg对象时在构造PushOp之前会先获取对象的读锁;
  • 当恢复是主副本pg对象时构造PullOp是不需要先获取对象的读锁的;

2.1.2 主pg接收从副本恢复请求响应的处理

记一次设置norecover标记导致ceph集群业务中断的问题分析
从上面的流程可以看出:

  • 当恢复主pg的对象时候,在处理从副本发来的数据时候先去获取对象的读锁,然后注册异步回调函数中再释放读锁,而这个异步回调是在recovery_tp线程中处理的;因此一旦recovery_tp线程被暂停住后,对象的读锁一直占用,所以do_op中再次对该对象加写锁会一直失败;

  • 当恢复的是从副本pg的时候,在构造push_op之前已经加了对象的读锁,当push请求响应回来的时候会直接释放对象读锁,因此如果恢复的是从副本对象数据不会导致对象锁无法释放的情况

3. 结论

  • 获取对象读写锁失败是因为设置norecover标志导致recovery_tp线程暂停,进而无法处理主pg恢复过程中注册的异步回调函数;
  • 解决办法:当osdmap设置norecover标志后,pg在做完peering之后判断是否可以进行恢复,如果不行则不加入recovery队列,这样就不会造成recovery_tp线程暂停了