为什么Node.js的net模块只能跑1.3GB/s?完整解析turbo-net:底层高性能TCP网络库

为什么Node.js的net模块只能跑1.3GB/s?完整解析turbo-net:底层高性能TCP网络库

【免费下载链接】turbo-net Low level TCP library for Node.js 【免费下载链接】turbo-net 项目地址: https://gitcode.com/gh_mirrors/tu/turbo-net

Node.js 里写 TCP 服务,net 模块是首选,但它在高吞吐场景下只能跑到 1.3GB/s。而 turbo-net 这个底层高性能 TCP 网络库,通过直接驱动 libuv、复用缓冲区、支持散列写入,把同样的 Echo 服务吞吐提升到 3.4GB/s,内存占用反而从约 100MB 降到约 35MB。本文用通俗的语言带你搞清楚:差距从哪来,以及 turbo-net 是如何做到的。

📊 先看实测:同样的 Echo 服务,性能差 2.6 倍

项目内置了两个可直接运行的对照示例:

实现吞吐内存占用示例文件
Node.js 原生 net 模块1.3GB/s约 100MBexamples/echo-classic.js
turbo-net3.4GB/s约 35MBexamples/echo-turbo.js

💡 Echo 服务是网络库的"体检项目":数据原样读进来再写回去,没有任何业务逻辑,因此吞吐差异几乎全部来自网络层本身的开销。

想自己验证?克隆仓库即可运行:

git clone https://gitcode.com/gh_mirrors/tu/turbo-net

🔍 为什么 net 模块"只跑一半"?两个隐藏开销

net 模块好用,是因为它站在两层"通用设施"之上,而这两层在极致吞吐场景下都会产生成本:

1. 高频内存分配:每次读数据都"新买一块地皮"

使用 net 时,每当触发 data 事件,底层都会新分配一个 Buffer 把数据放进去。数据流越大、越快,内存分配器(V8 堆 + 系统 malloc)就越忙。对比 examples/echo-classic.js 可以看到,经典写法完全依赖框架自动分配的缓冲区——每秒几 GB 的数据流意味着海量的临时 Buffer 出生又死亡,垃圾回收压力巨大。这也是为什么经典示例要吃掉约 100MB 内存。

2. Stream 封装的间接层

net 的 Socket 是基于 Node.js 的 Stream 体系构建的:数据经过流的事件循环分发、背压控制、编码转换等通用机制。这些机制对"通用性"很友好,但对"极致速度"来说,都是额外的函数调用和数据搬运。

🚀 turbo-net 的三板斧:把中间层全部拿掉

turbo-net 的核心思想很简单:跳过 Stream 层,让 JS 代码直接与 libuv(Node.js 底层的 C 网络库)对话

① 缓冲区由你自己提供、反复复用

turbo-net 的 read 接口要求你传入一个 Buffer,数据直接写进这块内存(见 src/turbo_net.c 中的 turbo_net_tcp_read)。读完后回调返回,你再决定用同一块 Buffer 继续读。

官方示例 examples/echo-turbo.js 就是这个套路:预分配几块 Buffer 在"读 → 写"之间循环使用,全程几乎零新分配——这就是内存只有 35MB 的原因。

② 请求队列复用,避免反复创建对象

每次读写请求在 C 层需要一个句柄(uv_write_t)。turbo-net 用了一个巧妙的环形请求队列 lib/queue.js:请求对象一次性预创建,用完即回收,通过位掩码(mask)实现 O(1) 的入队出队。热路径上不再有任何对象分配。

③ writev 散列写入:一次系统调用发多段数据

TCP 一次发送由多段 Buffer 组成时(比如"协议头 + 正文"),普通写法要么拼接成新 Buffer(一次拷贝),要么发多次系统调用。turbo-net 的 writev 接口(lib/connection.js)直接把多段 Buffer 交给 C 层做 scatter-gather write(见 src/turbo_net.c 的双 Buffer 快速路径 turbo_net_tcp_write_two),一次系统调用搞定,零拷贝。

架构一览

整个项目结构非常精简,一眼能看完整条链路:

模块作用
src/turbo_net.cC 原生层,通过 N-API 直接调用 libuv 的 TCP 接口
lib/binding.js加载预编译二进制(node-gyp-build)
lib/server.jsTCP 服务端封装(listen、connection 事件)
lib/connection.js连接封装(read / write / writev / end / close)
lib/queue.js读写请求的环形复用队列
lib/lookup.js域名解析,纯 IP 直接跳过 DNS
examples/3 个可直接运行的示例
test/服务端、连接、读写的完整测试用例

⚡ 五分钟上手:写出 3.4GB/s 的 Echo 服务

npm install turbo-net

它保留了与 net 相似的 API 风格,迁移成本很低(入口在 index.js):

const turbo = require('turbo-net')

const server = turbo.createServer(function (socket) {
  socket.read(Buffer.alloc(32 * 1024), function onread (err, buf, read) {
    if (err) throw err
    socket.write(buf, read, function (err) {
      if (err) throw err
      socket.read(buf, onread) // 复用同一个 Buffer 继续读
    })
  })
})

server.listen(8080)

常用 API 与 net 的对照:

turbo-net对应 net 用法说明
turbo.createServer(fn)net.createServer(fn)创建服务端,支持 allowHalfOpen 选项
server.listen(port, cb)同名还支持 server.address() 查询实际端口
turbo.connect(port, host)net.connect客户端连接,支持 DNS 域名
socket.read(buf, cb)socket.on('data')关键差异:读入你提供的 Buffer
socket.write(buf, len, cb)socket.write可指定写入长度
socket.writev(buffers, cb)多段 Buffer 一次写入
socket.end() / close()同名半关闭 / 全关闭

📌 注意:read 的回调参数是 (err, buffer, bytesRead),当 bytesRead 为 0 时表示对端已关闭可读方向。

🤔 turbo-net 适合谁?

适合:

  • 文件同步 / P2P 传输 / 存储引擎等大流量、低消息频率场景
  • 已经熟悉 net 模块、想压榨最后一滴性能的开发者
  • 用 C++ 扩展写底层、需要绕过 Stream 层的框架开发者(它同时支持 N-API 与 N-API 宏,跨 Node 版本稳定)

可能不需要:

  • 普通 Web 后端、WebSocket 网关等以 HTTP 为主的业务——http/net 的性能早已够用
  • 追求"完全零配置"的场景:缓冲区管理需要你显式编写(这也是它快、也更"底层"的原因)

✅ 总结

  • Node.js net 模块的 1.3GB/s 上限,主要卡在高频内存分配Stream 通用层的间接开销
  • turbo-net 通过自管缓冲区复用、直连 libuv、writev 散列写入三板斧,把 Echo 吞吐做到 3.4GB/s,内存还降低约 65%;
  • API 与 net 一脉相承,几行代码即可替换,是 Node.js 生态中少见的、面向极致吞吐的底层 TCP 网络库。

如果你的业务瓶颈在"数据搬得不够快",不妨跑一下 examples/echo-turbo.js 亲自感受一下这台"涡轮增压"。

【免费下载链接】turbo-net Low level TCP library for Node.js 【免费下载链接】turbo-net 项目地址: https://gitcode.com/gh_mirrors/tu/turbo-net

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值