桔妹导读:Ceph 是国内出名的开源分布式存储系统,在工业界和学术界都有着重要的影响。Ceph 的架构和算法设计发表在国内零碎畛域顶级会议 OSDI、SOSP、SC 等上。Ceph 社区失去 Red Hat、SUSE、Intel 等大公司的大力支持。Ceph 是国内云计算畛域利用最宽泛的开源分布式存储系统,此外,Ceph 也广泛应用在文件、对象等存储畛域。Ceph 在滴滴也撑持了很多要害业务的运行。在 Ceph 的大规模部署和应用过程中,咱们发现了 Ceph 的一些性能问题。围绕 Ceph 的性能优化,咱们做了很多深入细致的工作。这篇文章次要介绍咱们通过调试剖析发现的 Ceph 在锁方面存在的问题和咱们的优化办法。
1. 背景
在撑持一些提早敏感的在线利用过程中,咱们发现 Ceph 的尾提早较差,当利用并发负载较高时,Ceph 很容易呈现提早的毛刺,对提早敏感的利用造成超时甚至解体。咱们对 Ceph 的尾提早问题进行了深入细致的剖析和优化。造成尾提早的一个重要起因就是代码中锁的应用问题,上面依据锁问题的类型别离介绍咱们的优化工作。本文假如读者已相熟 Ceph 的根本读写代码流程,代码的版本为 Luminous。
2. 持锁工夫过长
2.1 异步读优化
Ceph 的 osd 解决客户端申请的线程池为 osd_op_tp,在解决操作申请的时候,线程会先锁住操作对应 pg 的 lock。其中,解决对象读申请的代码如下图所示,在锁住对象所属 pg 的 lock 后,对于最罕用的多正本存储形式,线程会同步进行读操作,直到给客户端发送返回的数据后,才会开释 pg lock。
在进行读操作时,如果数据没有命中 page cache 而须要从磁盘读,是一个耗时的操作,并且 pg lock 是一个绝对粗粒度的锁,在 pg lock 持有期间,其它同属一个 pg 的对象的读写操作都会在加锁上期待,增大了读写提早,升高了吞吐率。同步读的另一个毛病是读操作没有参加流量管制。
咱们对线上集群日志的剖析也验证了上述问题,例如,一个日志片段如下图所示,图中列举了两个 op 的具体耗时信息,这两个 op 均为同一个 osd 的线程所执行,且操作的是同一个 pg 的对象。依据工夫程序,第一个 op 为 read,总耗时为 56ms。第二个 op 为 write,总耗时为 69ms。图中信息显示,第二个 op 解决的一个两头过程,即正本写的实现音讯在解决之前,在 osd 申请队列中期待了 36ms。联合上图的代码能够晓得,这 36ms 都是耗在期待 pg lock 上,因为前一个 read 操作持有 pg lock,而两个对象属于雷同 pg。
咱们的优化如下图所示,咱们创立了独立的读线程,负责解决读申请,osd_op_tp 线程只需将读申请提交到读线程的队列即可返回解锁,大大减少了 pg lock 的持有工夫。读线程实现磁盘读之后,将后果放到 finisher 线程的队列,finisher 线程从新申请 pg lock 后负责后续解决,这样将耗时的磁盘拜访放在了不持有 pg lock 的流程中,联合咱们在流量管制所做的优化,读写操作能够在对立的框架下进行流量管制,从而精准管制磁盘的利用率,免得磁盘拜访拥塞造成尾提早。
咱们用 fio 进行了异步读优化成果的测试,测试方法:对同一个 pool 的两个 rbd,一个做随机读,另一个同时做随机写操作,将 pg number 配置为 1,这样所有对象读写会落到同一个 osd 的同一个 pg。异步读优化后,随机写均匀提早降落了53%。下图为某业务的 filestore 集群异步读上线前后读吞吐率的数据,箭头所指为上线工夫,可见上线之后,集群承载的读操作的吞吐率减少了120%。
上述优化在应用 filestore 存储后端时获得了显著的成果,但在应用 bluestore 存储后端时,bluestore 代码中还存在持有 pg 粒度锁同步读的问题,具体见 BlueStore::read 的代码。咱们对 bluestore 的读也进行了异步的优化,这里就不具体介绍了。
3. 锁粒度过粗
3.1 object cache lock 优化
Ceph 在客户端实现了一个基于内存的 object cache,供 rbd 和 cephfs 应用。但 cache 只有一把大的互斥锁,任何 cache 中对象的读写都须要先取得这把锁。在应用写回模式时,cache flusher 线程在写回脏数据之前,也会锁住这个锁。这时对 cache 中缓存对象的读写都会因为获取锁而卡住,使读写提早减少,限度了吞吐率。咱们实现了细粒度的对象粒度的锁,在进行对象的读写操作时,只需获取对应的对象锁,无需获取全局锁。只有拜访全局数据结构时,才须要获取全局锁,大大增加了对象间操作的并行。并且对象锁采纳读写锁,减少了同一对象上读的并行。测试表明,高并发下 rbd 的吞吐率减少了超过20%。
4. 不必要的锁竞争
4.1 缩小 pg lock 竞争
Ceph 的 osd 对客户端申请的解决流程为,messenger 线程收到申请后,将申请放入 osd_op_tp 线程池的缓存队列。osd_op_tp 线程池的线程从申请缓存队列中出队一个申请,而后依据该申请操作的对象对应的 pg 将申请放入一个与 pg 一一对应的 pg slot 队列的尾部。而后获取该 pg 的 pg lock,从 pg slot 队列首部出队一个元素解决。可见,如果 osd_op_tp 线程池的申请缓存队列中间断两个申请操作的对象属于雷同的 pg,则一个 osd_op_tp 线程出队前一个申请退出 pg slot 队列后,获取 pg lock,从 pg slot 队列首部出队一个申请开始解决。另一个 osd_op_tp 线程从申请缓存队列出队第二个申请,因为两个申请是对应雷同的 pg,则它会退出雷同的 pg slot 队列,而后,第二个线程在获取 pg lock 时会阻塞。这升高了 osd_op_tp 线程池的吞吐率,减少了申请的提早。咱们的优化形式是保障任意时刻每个 pg slot 队列只有一个线程解决。因为在解决 pg slot 队列中的申请之前须要获取 pg lock,因而同一个 pg slot 队列的申请是无奈并行处理的。咱们在每个 pg slot 队列减少一个标记,记录以后正在解决该 pg slot 的申请的线程。当有线程正在解决一个 pg slot 的申请时,别的线程会跳过解决该 pg slot,持续从 osd_op_tp 线程池的申请缓存队列出队申请。
4.2 log lock 优化
Ceph 的日志零碎实现是有一个全局的日志缓存队列,由一个全局锁爱护,由专门的日志线程从日志缓存队列中取日志打印。工作线程提交日志时,须要获取全局锁。日志线程在获取日志打印之前,也须要获取全局锁,而后做一个替换将队列中的日志替换到一个长期队列。另外,当日志缓存队列长度超过阈值时,提交日志的工作线程须要睡眠期待日志线程打印一些日志后,再提交。锁的争抢和期待都减少了工作线程的提早。
咱们为每个日志提交线程引入一个线程部分日志缓存队列,该队列为经典的单生产者单消费者无锁队列。线程提交日志间接提交到本人的部分日志缓存队列,该过程是无锁的。只有队列中的日志数超过阈值后,才会告诉日志线程。日志线程也会定期轮询各个日志提交线程的部分日志缓存队列,打印一些日志,该过程也是无锁的。通过上述优化,根本防止了日志提交过程中因为锁竞争造成的期待,升高了日志的提交提早。测试在高并发日志提交时,日志的提交提早可升高靠近90%。
4.3 filestore apply lock 优化
对于 Ceph filestore 存储引擎,同一个 pg 的 op 须要串行 apply。每个 pg 有一个 OpSequencer(简称 osr),用于管制 apply 程序,每个 osr 有一个 apply lock 以及一个 op 队列。对于每个待 apply 的 op,首先退出对应 pg 的 osr 的队列,而后把 osr 加到 filestore 的负责 apply 的线程池 op_tp 的队列,简称为 apply 队列。op_tp 线程从 apply 队列中取出一个 osr,加上它的 apply lock,再从 osr 的队列里取出一个 op apply,逻辑代码如下图左所示。可见,每个 op 都会把其对应的 osr 退出到 apply 队列一次。如果多个 op 是针对同一个 pg 的对象,则这个 pg 的 osr 可能屡次退出到 apply 队列。如果 apply 队列中间断两个 osr 是同一个 pg 的,也就是同一个 osr,则前一个 op 被一个线程进行 apply 时,osr 的 apply lock 曾经加锁,另一个线程会在该 osr 的 apply lock 上阻塞期待,升高了并发度。
这个问题也体现在日志中。一个线上集群日志片段如下图,有两个 op_tp 线程 6700 和 5700,apply 队列里三个对象顺次来自 pg: 1.1833, 1.1833. 1.5f2。线程 6700 先拿到第一个对象进行 apply, 线程 5700 拿第二个对象进行 apply 时卡在 apply lock 上,因为两个对象都来自 pg 1.1833,直到 6700 做完才开始 apply。而 6700 拿到第三个对象,即 1.5f2 的对象进行 apply 即写 page cache 只用了不到 1ms,但理论 apply 提早 234ms,可见第三个对象在队列里期待了 233ms。如果 5700 不必期待 apply lock,则第二和第三个对象的 apply 提早能够大大缩短。
咱们优化后的逻辑代码如上图右所示,同一个 osr 只退出 apply 队列一次,勾销 apply lock,利用原子操作实现无锁算法。下面的算法能够进一步优化,在将一个 osr 出队之后,能够一次从它的队列中取 m(m>1)个 op 进行 apply,在 op apply 实现阶段,改为如果 atomic::fetch_sub(osr->queue_length, m) > m,则将 osr 从新入队以进步吞吐率。
咱们用 fio 进行了 apply lock 优化成果测试,办法为建两个 pool,每个 pool 的 pg number 为 1,每个 pool 一个 rbd, 对两个 rbd 同时进行随机写的操作,一个 pool 写入数据的量为 31k10k,另一个 pool 写入数据的量为 4k100k, 掂量所有申请 apply 的总耗时。优化前总耗时 434ks, 优化后总耗时 45ks,缩小89.6%。### !
团队介绍
滴滴云平台事业群滴滴云存储团队原隶属于滴滴根底平台部,现隶属于新成立的滴滴云事业部。团队承当着公司在线非结构化存储服务的研发,并参加运维工作。具体来说,团队承当了公司内外部业务的绝大部分的对象、块、文件存储需要,数据存储量数十 PB。团队技术气氛浓重,同时具备良好的用户服务意识,立足于用技术发明客户价值,业务上谋求极致。团队对于分布式存储、互联网服务架构、Linux 存储栈有着深刻的了解。
作者介绍
负责滴滴在线非结构化存储研发,曾任国防科技大学计算机学院副研究员,教研室主任,天河云存储负责人
延长浏览
内容编辑 | Charlotte
分割咱们 | DiDiTech@didiglobal.com
滴滴技术 出品