受够了阿里云卡死,我写了个脚本从内核层解决它
如果你的 nodejs 项目在阿里云服务器部署过,那肯定碰过服务器卡死的问题。原因其实也不复杂,阿里云基础款 ECS 或轻量服务器的系统盘性能普遍比较有限,随机 IOPS 基线通常也就两三千这个级别(当然如果你是土豪,可以直接加钱换更牛逼的硬盘)。当你执行 npm install 或者 npm run build 的时候,这种场景本身就会产生大量小文件...

仓库地址: https://github.com/tageecc/aliyun-io-safe
如果你的 nodejs 项目在阿里云服务器部署过,那肯定碰过服务器卡死的问题。原因其实也不复杂,阿里云基础款 ECS 或轻量服务器的系统盘性能普遍比较有限,随机 IOPS 基线通常也就两三千这个级别(当然如果你是土豪,可以直接加钱换更牛逼的硬盘)。当你执行 npm install 或者 npm run build 的时候,这种场景本身就会产生大量小文件读写、解压、目录扫描、缓存写入和构建产物落盘,I/O 很容易一下子堆满,硬盘直接爆掉,导致服务器进入一种“没完全死,但基本没法操作”的状态。SSH 还能连着,但是命令不返回,系统响应越来越慢,你甚至在后台都不一定能正常关机,很多时候最后只能强制断电。
之前我的方案是,使用阿里云 Codeup 创建流水线,每次提交的时候在构建机里 install 和 build,然后打包推送到服务器上再解压,然后再去启动应用。
这个确实是一个比较规范的 CI/CD 流程,但是流程太长了,每次新建一个项目我都要去搞个流水线、搞部署,还是很繁琐。最关键的是代码仓库最好也一起托管到阿里云 Codeup,整个链路才比较顺手。不过因为 Codeup 给了免费构建机,基本上也能把流程跑通,所以虽然麻烦,但我也一直是这么部署的。
最近发布的项目比较频繁,而且代码库都在 GitHub 了,就花了点时间重新想了一下这个问题的本质,然后写了个脚本,打算从根本上解决这个卡死的问题。
我期望的流程其实很简单,也很符合个人项目的习惯:本地 git push,服务器 git pull,执行 npm install、npm run build,然后 npm start。直接把流水线和 CI/CD 去掉了,虽然可能不算特别规范,稳定性也不一定比完整流水线更强,但是它真的简单啊,非常适合我这种个人项目。
那具体是怎么做的呢?既然原理已经基本排查到了,简单来讲就是磁盘高读写的时候爆了,那么只要限制这个读写上限,基本就能解决卡死的问题。
那么思路其实也就很清晰了,就是别让磁盘请求在短时间内无限堆积,不要等到整个系统被 I/O 撑爆了才发现。这个脚本主要做了几件事:一是调整内核脏页相关参数,减少脏数据长时间堆在内存里再一次性回写;二是给根分区加上更适合这种场景的挂载参数,尽量减少一些没必要的额外写入;三是限制块设备的请求队列深度,避免低性能云盘在高压下把延迟越堆越高;四是把用户会话的 I/O 权重降下来,保证系统服务优先,尽量别出现你 SSH 连着但整台机器完全不响应的情况。
具体使用也非常方便,脚本传到服务器上之后,直接 sudo bash io-safe-auto.sh start 就可以了。它会自动探测当前根分区所在的物理盘,然后按磁盘容量生成一个相对保守的参数,把 sysctl、udev、systemd 这些配置一次性写好。你也可以随时执行 status 看当前状态,看看配置有没有生效。
为了安全,我还加了回滚操作,直接执行 sudo bash io-safe-auto.sh stop 就可以恢复默认配置。脚本在启用的时候会把原始状态记下来,回滚的时候优先按原始值恢复,尽量把改动控制在可回退的范围里,不会造成无法回滚只能重置机器的情况(重置解决一切bug,听懂掌声)。
目前我已经在好几个项目里用了,而且分别在阿里云 ECS 和轻量服务器上跑过,尤其轻量服务器这种 IOPS 更低的场景,感受会更明显。到现在稳定运行差不多一个月了,效果比我预期的还要好,所以就专门抽出来整理了一下,分享给大家。如果你也遇到过类似的问题,欢迎交流。