记一次存储系统 IOPS 翻倍的性能优化
导读
近年来,高性能设备日新月异。200G 网卡、全闪 NVMe 存储等等设备越来越普及。
硬件设备性能提升的同时,软件也需要跟上趋势,做出优化提升。
通过一系列的参数调优、代码优化,我们最终将 4k 随机读的 IOPS 性能提升了 92%,几乎翻倍。
下边,分享一下我们在优化过程中做的一些工作,以及部分经验教训。
1. 分析问题
在做优化之前,我们需要先有明确的目标。所以,我们问了自己这样三个问题:
做什么?
性能优化,本次的优化重点是数据读写性能,结果以 4K 随机读写 IOPS 作为衡量指标。
为什么做?
随着硬件性能的提升,目前的软件架构已经开始碰到一些瓶颈。为了让客户有更好的体验,让硬件发挥更高的价值,软件层面的优化是必要的。
如何做?
添加日志,通过明确的数据确定目前的瓶颈在哪里,针对瓶颈做调整;
测试不同 CPU 负载、I/O 负载下的性能差异,不同参数对于 CPU 使用的影响;
回顾代码,找到资源临界点;
多了解外界目前有哪些新的技术、好的方法可以应用学习。
2. 新技术的应用尝试:io_uring
2019 年,Linux5.1 引入了异步 I/O 框架 io_uring,能够显著加速 I/O 密集型应用。
最近几年,它的应用越来越广泛:ceph、gluster-fs、fio 都进行了引入,阿里也在 nginx 上做了替代 epoll 进行网络 I/O 的尝试。
在一些文章中,我们看到部分测试中 io_uring 和同步读写的性能差距可以达到 70%之多。
所以,引入 io_uring 成了我们优化尝试的首选方案。
初步测试验证
经过一段时间的调研,我们参考官方代码以及 github 上的一些样例,我们写出了第一版 demo。
初步测试,demo 使用 io_uring 的性能比使用同步读写的性能提高了一倍以上。
此时,我们认为 io_uring 就是我们本次工作的关键了。
适配测试验证
为了保证 io_uring 在我们的框架中也可以达到相似的结果,我们在第一版 demo 的基础上,根据我们的软件架构做了线程模型、读写模型的修改调整,完成了第二版 demo。
测试后发现,io_uring 和同步读写的性能差异微乎甚微。部分情况下有一些提升,部分情况下反而有所下降。
事实证明,世界上没有银弹,也没有万灵药。
也许 io_uring 是一个很好的答案,是未来我们需要的答案,但当下它不是我们需要的答案。
3. 测试、分析与优化
反思之后,我们决定先把重点放回我们的系统,深入了解目前的情况。
为此,我们做了三件事情:
参数调优:
通过测试不同的参数,我们发现之前对于工作线程数量的参数设计出现了问题。
相比于之前分析的至少需要 8 个工作线程,4 个工作线程的情况下反而性能有了 30%左右的提升。
也由此出发,我们分析代码后发现了多线程竞争中的一个瓶颈,并对此进行了第一次优化。
日志统计分析:
针对任务流的一些关键节点,我们增加/更详细的记录了时间日志,明确了每个流程的耗时。
有了精准的时间,我们后边的代码优化也就有了更明确的依据,可以清楚的知道那些调整是有效的,哪些调整虽然提升单任务性能但是不能提高整体性能等等。
代码分析:
有了数据依据,我们回归到了代码中。分析后,我们发现了以下几类问题:
历史上可以有效工作的代码,在不断改动中偏离了原始功能。
曾经不是瓶颈的节点,由于硬件性能提升/其他流程的优化成为了新的瓶颈。
一些为了修改 bug 的临时代码,成为了瓶颈。
在上述三个过程的不断迭代中,我们进行了大量的开发/测试/分析工作。
最终,我们得到了下边的结果:
通过参数调优,调整线程数、线程比例等方式,提升了大约 39%的性能。
再经过代码优化,性能提升了大约 92%。
4. 代码优化的邮政系统模型
我们的系统中,主要包括这么几个重要角色:
消息队列:存放消息,等待工作线程处理。
存储设备:用于数据存储,不同的任务可以通过最终写入的存储设备做区分。
连接线程:负责接收消息,放入工作队列中。
工作线程:负责处理消息,从工作线程中读取。
直接介绍整个过程,会比较繁琐,难以理解。所以,我们用现实中一个很相似的模型来介绍:邮政系统。
下边,我们用以下指代来描述整个过程:
信件:消息
邮筒:消息队列
邮局:硬件分区
分拣员:连接线程
邮递员:工作线程
原版模型
A 市有一个大的分拣中心,里边有 X 个分拣员。
分拣员每收到一批批信件,并根据信封上的地址分给某一个邮局,放入邮筒。
有 Y 个邮局,每个邮局有 1 个邮筒。
而每个邮局,会有 Z 个邮递员,负责去送信。
瓶颈一:邮筒记录表
为了保证邮筒里的信件数量没有问题,邮筒外放了一个本子。每当有人要放入/取出一封信时,需要拿到本子,去修改计数。
此时,如果很多分拣员和邮递员需要去同一个邮筒修改计数,就会有很多人在等待,也就产生了瓶颈一。
优化 1:针对这个问题,我们增加了邮筒的数量。分拣员会轮流给邮局的某一个邮筒中放入信件,而每个邮递员也只要去处理某一个固定邮筒的信件即可。
优化 2:登记的方式,可以修改为取号的方式。分拣员更新可以取的最大号码,邮递员更新已经取出的最后号码(读写锁分离)。两者的工作便可以同步进行。
瓶颈二:人员比例问题
A 市一共可以雇佣 30 个员工(系统资源限制)。
通过观察每封信的记录,我们发现有很多邮递员处于等待的状态,而等待的邮递员不断去看邮箱是否有信件,或者邮件来了几个人争抢,反而降低了整体效率。
优化:测试不同比例,直到有一个合理的比例,分拣信件的数量和邮递员可以处理的数量达到平衡。
瓶颈三:登记电子表格
在邮政系统中,有一个大的电子表格,用于登记所有信件的状态。
开始送信,送信结束后,都要做一下查询、登记,登记时每次需要下载,修改,上传。
为了保证数据不被 2 个人同时修改出现错乱,只有一个人可以做修改。
虽然登记速度很快,但是当信件越来越多后,一个电子表格早晚会出现有人要等待的情况,于是产生了瓶颈二。
优化:将电子表格,根据邮局拆分成多个;查询、登记分开,多个人一起查询并不会有数据错乱。
5. 经验小结
没有银弹或者万灵药,一切问题的解决方案要从问题本身出发。
实践数据才是真实的结果,正确的理论在实际运用中可能会遇到各种问题。
理论计算得出的参数,要实际验证才行。
定期去回顾一下代码是有必要的,长时间修改后开发人员对于代码的预期和实际情况很可能以及有了差异。
性能优化的核心是减少冲突,提高资源使用率。将问题迁移为更好理解的方式,用物流、水管等等方式去思考性能问题,会明了很多。
版权声明: 本文为 InfoQ 作者【Vincent】的原创文章。
原文链接:【http://xie.infoq.cn/article/817b78bd9f79e72428da37d08】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论