每秒处理 129 万个请求!Bun.js 性能迎来大幅增长!

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

近期,Bun 发布了 v1.1.25 版本,并高调宣布在最新的性能测试中,每秒可以处理 129 万个 HTTP 请求,我们一起来看看这个版本究竟做了哪些改进吧。

a0d04b9ccea69ebe1676524c5ee2c2bf.png

node:cluster 支持

Bun 现在支持 node:cluster API。

通过使用这个 API,你可以在同一个端口上运行一组 Bun workers,从而实现更高的吞吐量和利用率。对于拥有多个 CPU 核心的机器来说,这是在生产环境中进行负载均衡的最佳选择。

下面是一个工作原理的示例:

  • 主要的 worker 会创建 n 个子 worker,一般数量与 CPU 核心数相同;

  • 每个子 worker 都会监听相同的端口(使用 reusePort);

  • 传入的 HTTP 请求会在子 worker 之间进行负载均衡分配处理。

import cluster from "node:cluster";
import http from "node:http";
import { cpus } from "node:os";
import process from "node:process";

if (cluster.isPrimary) {
  console.log(`主进程 ${process.pid} 正在运行`);

  // 根据 CPU 核心数创建 N 个子进程
  for (let i = 0; i < cpus().length; i++) {
    cluster.fork();
  }

  cluster.on("exit", (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 退出`);
  });
} else {
  // 请求会由子进程池中的工作进程处理,而不是主进程
  http
    .createServer((req, res) => {
      res.writeHead(200);
      res.end("hello world\n");
    })
    .listen(3000);

  console.log(`工作进程 ${process.pid} 已启动`);
}

这个示例代码可以与 node:http API 以及 Bun.serve() API 一起使用。

import cluster from "node:cluster";
import { cpus } from "node:os";

if (cluster.isPrimary) {
  // 主进程逻辑:根据 CPU 核心数创建子进程
  for (let i = 0; i < cpus().length; i++) {
    cluster.fork();
  }
} else {
  // 工作进程逻辑:使用 Bun 框架提供的 API 运行服务器
  Bun.serve({
    port: 3000, // 监听的端口号
    fetch(request) {
      return new Response(`你好,来自工作进程 ${process.pid}`);
    },
  });
}

注意,目前 reusePort 只在 Linux 系统上有效。在 Windows 和 macOS 上,操作系统无法像预期那样对 HTTP 连接进行负载均衡处理。

开始支持 V8 公开 C++ API

Bun 现在支持了 V8 的公开 C++ API,这使得像 cpu-features 这样的软件包可以在 Bun 中正常工作。

这是一个值得注意的变化,因为 Bun 不是像 Node.js 一样构建在 V8 之上。相反,Bun 是构建在 JavaScriptCore 上的,JavaScriptCoreSafari 使用的 JavaScript 引擎。

这意味着官方需要实现一个自己的 C++ 翻译层,将 V8 的 API 与 JavaScriptCore 进行对接。

#include "v8.h"
#include "V8Primitive.h"
#include "V8MaybeLocal.h"
#include "V8Isolate.h"

namespace v8 {

enum class NewStringType { /* ... */ };

class String : Primitive {
public:
    enum WriteOptions { /* ... */ };

    BUN_EXPORT static MaybeLocal<String> NewFromUtf8(Isolate* isolate, char const* data, NewStringType type, int length = -1);
    BUN_EXPORT int WriteUtf8(Isolate* isolate, char* buffer, int length = -1, int* nchars_ref = nullptr, int options = NO_OPTIONS) const;
    BUN_EXPORT int Length() const;

    // 将本地字符串对象转换为JavaScriptCore的JSString对象
    JSC::JSString* localToJSString()
    {
        return localToObjectPointer<JSC::JSString>();
    }
};
}

这是一项非常艰巨的工程,JavaScriptCore 和 V8 以不同的方式表示 JavaScript 值。V8 使用了移动式、并发的垃圾收集器,并且有明确的句柄作用域,而 JavaScriptCore 使用了非移动式的并发垃圾收集器,它会扫描堆栈内存(类似于隐式句柄作用域)。

在此之前,如果你尝试导入一个使用这些API的软件包,你会得到如下的错误提示:

console.log(require("cpu-features")());
dyld[94465]: missing symbol called
fish: Job 1, 'bun index.ts' terminated by signal SIGABRT (Abort)

现在,像 cpu-features 这样的包可以导入并在 Bun 中运行。

$ bun index.ts
{
  arch: "aarch64",
  flags: {
    fp: true,
    asimd: true,
    // ...
  },
}

为什么要支持 V8 的内部 API 么?

最初 Bun 并没有打算支持这些 API,但是在发现许多受欢迎的软件包依赖于这些 API 后,开始决定加以支持,这些软件包包括:

  • cpu-features

  • node-canvas@v2

  • node-sqlite3

  • libxmljs

  • 以及许多使用nan的软件包

在这个版本中,只有cpu-features从上述列表中受到支持,其他 API 正在努力支持中。

使用 @aws-sdk/client-s3 实现的 S3 上传速度提高了5倍

Bun 修复了 node:http 客户端实现中的一个 bug,这使得上传到 S3 的速度提高了5倍。

ad9b29aa13cad74ab71b0498d94e1417.png

独立可执行文件中的 Worker

Bun 的单文件独立可执行文件现在支持绑定 Workernode:worker_threads

// main.ts

console.log("Hello from main thread!");

new Worker("./my-worker.ts");
// my-worker.ts

console.log("Hello from another thread!");

要使用 worker 编译独立可执行文件,可以将文件入口传递给 bun build --compile 命令:

bun build --compile ./main.ts ./my-worker.ts

这将在生成的可执行文件中将 my-worker.tsmain.ts 作为独立的入口点进行打包。

使用 OMGJIT 在 Windows 上实现更快的 WebAssembly

Windows 上的 WebAssembly 现在支持 JavaScriptCore 的优化即时编译器 (JIT),称为 OMGJIT

d2b0d739d4b0d279e5563fa7990666b1.png

Node.js兼容性改进

execa 现在可以正常工作了。

Bun 修复了一个不能正确支持 EventTarget的setMaxListenersbug。这个问题影响到了像 execa 这样的包,会导致错误报 undefined is not a function

import { execa } from "execa";

const { stdout } = await execa`echo "test"`

如果你遇到类似的错误,现在已经得到修复:

91 |    const controller = new AbortController();
92 |    setMaxListeners(Number.POSITIVE_INFINITY, controller.signal);
      ^
TypeError: undefined is not a function
      at node:events:101:30
      at spawnSubprocessAsync (/node_modules/execa/lib/methods/main-async.js:92:2)
      at execaCoreAsync (/node_modules/execa/lib/methods/main-async.js:26:32)
      at index.mjs:3:26

问题已修复:在调用 destroy() 关闭 TCP 连接后,与 node:net 的连接出现挂起

也是一个 bug 修复,在调用 destroy() 关闭 TCP 连接后,进程没有始终正确退出,因为事件循环仍然处于活动状态。这有时会导致像 postgres 这样的包无限期地挂起。

import net from "node:net";

const server = net.createServer((socket) => {
  // 当socket连接成功时触发connect事件
  socket.on("connect", (data) => {
    socket.destroy();
    // 这会销毁连接,
    // 但是事件循环仍然处于活动状态
  });
});

server.listen(3000);

65da83594f83078a57b414ee214b6b7e.png

往期推荐

腾讯写码6年,我总结的技术人核心竞争力

10df5677d9167a3a00d1b23585947e41.png

前端构建系统浅析

3fe93f7c1a9ed84e0e2050fb25560cf3.png

100 个太多,但这  9 个 css 属性你必须要知道!

7329b1f0c91d514a9dabc7b91657f988.png


最后

  • 欢迎加我微信,拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

273f18055bbfcd02c419958bd4e2229f.jpeg

074b50822e379957df1ad84865c1c79b.png

点个在看支持我吧

60715f7bd76c7d1812ef56856990bb4a.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值