作者:李亚飞
Debugging is like trying to find a needle in a haystack, except the needle is also made of hay.
Debug调试就像是在大片的干草堆中找针相同,只不过针也是由干草制成的。
在软件开发的国际里,偶尔会呈现一些十分隐蔽的 Bug,这时候工程师们像探险家相同,需求深入代码的森林,寻找躲藏在其间的“鬼魂瑰宝”。前段时刻,我和我的团队也踏上了这样一段刺激、有趣的探险之旅。
最近繁忙的作业告一段落,我总算轻松下来了,想趁这个时机,跟咱们分享咱们的这次“旅途”。
01 引子
我是 ShowMeBug 的 CEO 李亚飞,是一个古老的 Ruby 工程师。因为 2019 年招聘工程师的噩梦阅历,我立志打造一个实在模仿作业场景的 IDE,用来完结八股文、算法横行的技能招聘时代。
这个云上的 IDE 引擎,我称之为轻协同 IDE 引擎——因为它不是为了繁杂重度的作业场景预备的,而是适应于大部分人的习惯、能快速上手了解、加载速度快、能协同(面试用)、低推迟感,让用户感触十分友爱 。
多环境发动与切换
为了到达秒级发动环境的功用要求,咱们规划了一套精巧的分布式文件体系架构,其核心是一个可以瞬间仿制很多小文件的写时仿制 (COW) 技能。IO 吞吐能到达几万人同时在线,功用绝对是它的一大优势。
咱们对此信心满满,可是没想到,很快就翻车了。
02 探险起程
2023 年 1 月,北方现已白雪皑皑,而深圳却仍难以感触到冬天的寒意。
我和我的团队在几回打开文件树的某个文件时,会显得有点慢——当时没有人在意,按照惯例思路,“网速”背了这个锅。过后咱们复盘才发现,这个看似微不足道的小问题,其实正是咱们开端这次探险之旅的起点。
1 月底,南方的寒意慢慢侵入。这时候咱们的轻协同 IDE 引擎现已开端连续支撑了 Vue2、Vue3、React、Django、Rails 等结构环境,一开端体现都很棒,加载和发动速度都很快。可是,跑了一段时刻,咱们开端发觉,线上环境就呈现单个环境(Rails 环境)发动要 20-30s 才干完结。
尽管其他环境依然坚持了极快的加载和发动速度,但敏锐的第六感告诉我,不可,这一定有什么猫腻,假如不立即行动,势必会对用户体验带来很不好的影响。所以,我开端组织团队排查眼前这个不起眼的问题,咱们的探险之旅正式开端。
03 初露期望
湿冷的冬季,夜已深,我和咱们的团队仍旧坐在电脑前苦苦探索,瑟瑟发抖。
探险之旅的第一站,便是老大难的问题:定位Bug。目前只要某一个环境发动很慢,其他的环境都体现不错。咱们想了许多方法都没有想理解为什么,甚至置疑这个环境的模板是不是有问题——但把代码放在本地发动,最多就2秒。
哎,太怪异了。咱们在这儿卡了至少一周时刻,不断追寻代码,分析日志文件,尝试各种计划,都没有弄清楚一个正常的程序发动为什么会慢。咱们一度陷入了疲惫和焦虑的心情中。
Debug 是种崇奉,只要坚信自己能找到 Bug,才有可能找到 Bug。
软件开发界一向有一个初级 Bug 规律:所有怪异的问题都来自一个初级原因。在这“山重水复疑无路”之际,咱们决议从头审视咱们的探险途径:为什么只要 Rails 更慢,其他并不慢?会不会只是一个十分微小的原因此导致?
这时候,恰好有一个架构师朋友来访,向咱们建议,可以用 perf 火焰图分析看看 Rails 的发动进程。
perf火焰图实例
当咱们用 perf 来分析时,惊讶地发现:本来 Rails 的发动要加载更多的文件! 紧接着,咱们又从头用了一个文件读写监控的东西:fatrace,经过它,咱们看到 Rails 每次发动需求读写至少 5000 个文件,但其他结构并不需求。
这才让咱们突然意识到,会不会是文件体系读写速度不及预期,导致了发动变慢。
04 Bug现身
为了搞清楚是不是文件体系读写速度的问题,我急需一个测验 IO 颤动的脚本。咱们开端估算一下,写好这个脚本需求好几个小时的时刻。
夜已深,研制同学都连续下班了。时刻紧迫!我想起了火爆全球的 ChatGPT,心想,不如让它写一个试试。
测验 IO 颤动的脚本
Cool,简直不需求改动就能用,把代码扔在服务器开跑,一测,公然发现问题:每一次文件读写都需求 10-20ms 才干完结 。实际上,一个优秀的磁盘 IO 读写时延应该在亚毫级,但这儿至少慢了 50 倍。Bingo,如同“鬼魂瑰宝”一般的分子级 Bug 逐渐显现,问题的根因现已确认:过慢的磁盘 IO 读写引发了一系列操作变慢,从而导致发动时刻变得十分慢 。
更庆幸的是,它还让咱们发现了偶尔打开文件树变慢的根本原因,这也是整个体系并发才能下降的元凶巨恶 。
05 迷雾追因
看到这儿,咱们可能会问,这套分布式文件体系难道一向这么慢,你们为什么在之前没有发现?
非也,早在项目开端的时候,这儿的时延是比较良好的,咱们没有特别注意这个 IOPS 功用指标,直到咱们后面才留意到,体系运转超越一个月时,IO 读写时延很简单就进入到卡顿的状态,体现便是文件体系地点主机 CPU 忽高忽低,重启就会暂时恢复。
此刻,探险之旅还没完毕。毕竟,这个“鬼魂瑰宝”周围仍旧笼罩着一层迷雾。
咱们持续用 fatrace(监控谁在读写哪个 IO)监控线上各个候选人答题目录的 IO读写情况,好家伙,咱们发现了一个意外的情况:简直每一秒都有一次全量的文件 stats 操作 (这是一个检测文件是否有特点改变的 IO 操作)!
也便是说,比如有 1000 个候选人正在各自的 IDE 中编码,每个候选人平均有 300 个文件,就会呈现每秒 30 万的 IO 操作数!
咱们赶紧去查资料,根据研讨数据显现,一个一般的 SSD 盘的 IOPS 最高也就到 2-3 万 。所以,咱们从头测验了自己分布式文件体系的 IOPS 才能,成果发现也是 2-3 万 。
那这肯定远远达不到咱们抱负中的才能等级。
这时,问题愈加清晰:某种未知的原因导致了很多的 IOPS 的需求,引发了 IO 读写时延变长,慢了大约几十倍。
06 挨近尾声
我和我的团队持续深究下去,问题现已变得十分清晰了:
本来,早在上一年 12 月,咱们上线一个监听文件增删的改变来告诉各端改写的功用。
最开端咱们选用事情监听 (fswatch event),因为跨了主机,所以存在 1-2s 的推迟。研制同学将其改为轮询实现的计划,从而引发了每秒扫描目录的 stats 行为。
当在百人以下访问时,IOPS 没有破万,还满足应对。但一旦访问量上千,便会引发 IO 变慢,从而导致体系呈现各种反常:间歇导致某些关键接口 QPS 变低,从而引发体系颤动。
跟着“鬼魂瑰宝”显露真身,这次分子级 Bug 的探险之旅也现已挨近尾声。团队大 呼:这进程实在太酸爽了!
07 技能无止境
每一个程序员在生长路上,都需求与 Bug 作足够的对抗,要么你勇于探索,深入代码的森林,快速定位,挖到越来越丰厚的“瑰宝”,然后纵情汲取到顶级的知识,最终成为高手;或者被它打趴下, 花费很多时刻都找不到问题的根源,成为芸芸众生中的一人。
当然,程序员的国际中,不单单是 Debug。
当我毕业 5 年之后,开端意识到技能的真实价值是解决真实的社会问题。前文中我说到,因为我发现技能招聘真是一个极端痛苦的事:特别花面试官的时刻,却又无法有效分析出候选人的技能才能,所以创建 ShowMeBug 来解决这个问题:用模仿实战的编程环境,解决科学评价人才的难度。
这个轻协同 IDE 技能从零开发,支撑协同文件树、完全自定义的文件编辑器、协同的控制台 (Console) 与终端 (Shell),甚至直接支撑 Ctrl+P 的文件树搜索,不只易于使用,又强大有力。
可是这还不行。要知道,寻求技能精进是咱们技能人的毕生寻求。关于这个轻协同IDE,咱们寻求三个零:零配置、零发动、零推迟。其间,零发动便是本文所寻求的极限:以最快的速度发动环境和切换环境。
因此,探险之旅完毕后,咱们进一步改进了此文件体系,设定 raid 的多磁盘冗余,选用高功用 SSD,同时从头制定了新磁盘架构参数,优化相关代码,最终大幅提升了分布式文件体系的稳定性与并发才能。
截止本文结束,咱们发动环境的平均速度为 1.3 秒,切换环境速度进入到亚秒级,仅需求 780ms。目前在全球范围的技能才能评价赛道 (TSA) 中,具备 1-2 年的领先性。
08 跋文
正当我打算完毕本文时,咱们内部的产品吐槽群信息闪烁,点开一看:嚯,咱们又发现了新 Bug。
立夏已至,咱们的探险之旅又即将开端。