别再吹Node.js了:单线程不是神,你只会写异步垃圾

🔑 关键词:Node.js性能,事件循环,异步编程,Deno,Bun

📖 摘要:从一个线上事故说起,聊聊Node.js真正强大的生态和真正的性能边界。

有些话憋了很久,想写出来。我搞坏过一个订单系统,那是一个线上排班功能,需要把几十个工人的班次和休息规则做匹配。当时图省事,写了三层for循环穷举所有组合。并发一上来,整个进程的CPU直接飙到100%,所有HTTP请求都像中了定身术一样卡住。那次事故让我彻底明白:不是Node慢,是我在单线程里干了CPU密集的混账事。见过太多人把事件循环当银弹,最后却写出一坨等待彼此的异步垃圾——他们连I/O密集和CPU密集都分不清,前者是Node的地盘,后者是Node的坟场。

图片

事件循环看上去很美:一个线程应付几万个连接,非阻塞I/O,就像端着咖啡在机场优雅地处理所有登机牌。可现实里呢?一旦你在热点路径上写一个同步文件读取,或者频繁调用JSON.parse,线程就像堵了茅坑一样,后面所有请求都得排队闻臭。更扯的是,async/await虽然把回调地狱变成了线性代码,但它并没有改变事件循环的模型。很多人以为await会让出线程,实际上它只是把状态机拆成若干快照,内存开销一点没少,甚至更高。我用一个老项目对比过,同样五千个并发WebSocket,Node端维持连接吃了大概230MB内存,而用Go写的服务端只用了120MB。没什么丢人的,模型不同罢了,但如果这些数字你从来不知道,总有一天会被运维凌晨三点的电话吵醒。

图片

在喷Node不懂性能之前,先界定它到底适合干什么。我自己折腾过Bun、Deno,还给它们提过issue。Bun确实快,启动快了接近两倍,内置打包器也让人眼前一亮,但你敢拿它跑核心生产服务吗?那套用Zig重写的东西一旦崩溃就整个进程直接退出,没有成熟的inspector,查一次内存泄漏让你找到怀疑人生。Deno更激进,想取消node_modules,把权限系统做到极致,但你让刚入门的新手去写--allow-net,他们第一反应不是安全,是麻烦。说句实在话,Node最大的资本不是运行时,而是那个什么都能搜到的npm仓库,是几百万前人替你踩过的坑。这种底蕴,才是Deno和Bun一时半会儿追不上的。

图片

所以回到那句被用烂的话:Node.js到底适合什么?我的体会是,它适合高并发、低运算的I/O代理层,比如API网关、聊天推送、代理转发、BFF。在这些位置,事件循环的长处能发挥得淋漓尽致,又不会因为一次恶意请求把CPU耗尽。如果你要处理视频转码、复杂权限计算、机器学习推理,请去用Go、Java或C++,别让Node背这个锅。我到现在每天还在写Node,但这个分享写得很纠结,因为我越来越讨厌有人用“高性能”给Node贴金。Node只是个工具,它的价值在于让你用更少的时间把业务跑起来,而不是把你包装成异步大师。最后说一句得罪人的:Node.js最强的是生态,最弱的是“你以为你懂了”。真正伤害你的往往是那个阻塞事件循环的业务代码——这才是这篇最想让你记住的。

图片

🏷️ 标签: