Bun.redis真正的强项

先看一个真实场景:100 万条 Redis 键需要处理,CPU 密集 + I/O 密集并存,怎么破?用 Bun.redis + Worker,4 核机器跑出 4 倍加速,单线程能撑 4 万 QPS。

⚠️ 环境要求 :Bun ≥ 1.3.14,Redis ≥ 6.0 (Bun.redis 默认用 RESP3 协议握手,需要 Redis 6+ 的 HELLO 命令支持)。Redis 5 及以下会报 ERR unknown command 'HELLO'


100 万条键的"压榨"实验

假设你的 Redis 里有 100 万个用户画像(Hash 结构),现在要做一次全量数据加工

bash 复制代码
// 每个 user 是一个 Hash,有 name、age、tags 等几十个字段
// 任务:取出 → 解析 → 加密敏感字段 → 写回

朴素版本(单线程串行)------ 别想跑完

bash 复制代码
const redis = new Bun.RedisClient("redis://localhost:6379");
const keys = await redis.keys("user:*"); // 100 万

for (const key of keys) {
  const data = await redis.hgetall(key);     // I/O
  const encrypted = encrypt(JSON.stringify(data)); // CPU
  await redis.hset(key, { data: encrypted });      // I/O
}
// 预估耗时:3-5 小时
// 事件循环 100% 阻塞,HTTP 服务直接卡死

进阶版本:自动 Pipeline(不用 Worker)------ I/O 起来了,CPU 还是瓶颈

bash 复制代码
// 先把所有 key 一次性取出来(自动 pipeline,1 次 RTT)
const pipeline = [];
for (let i = 0; i < 1000; i++) pipeline.push(redis.hgetall(`user:${i}`));
const datas = await Promise.all(pipeline); // 1ms 拿到

// 但 encrypt() 在主线程跑
for (const data of datas) {
  encrypt(JSON.stringify(data)); // 卡住事件循环
}
// 预估耗时:40-60 分钟(I/O 快,CPU 慢)
// 加密 10 万次时,HTTP P99 飙到 5 秒

终极版本:Worker + Bun.redis ------ 4 核满载

bash 复制代码
// main.ts
// ⚠️ Worker 是 Bun 的全局对象,无需 import
const redis = new Bun.RedisClient("redis://localhost:6379");
const keys = await redis.keys("user:*");
const chunks = chunkArray(keys, 4); // 分 4 份

const workers = chunks.map(() => new Worker(new URL("./encrypt-worker.ts", import.meta.url)));
const results = await Promise.all(
  workers.map((w, i) => {
    w.postMessage(chunks[i]);
    return new Promise((resolve) => (w.onmessage = (e) => resolve(e.data)));
  })
);
console.log(`处理完成:${results.reduce((a, b) => a + b, 0)} 条`);
// 预估耗时:10-15 分钟(4 倍加速)
// 事件循环全程畅通,HTTP 服务不受影响
bash 复制代码
// encrypt-worker.ts
// ✅ Bun.RedisClient 是全局;也可以 import { RedisClient } from "bun"
declare var self: Worker;

self.onmessage = async (e) => {
  const redis = new Bun.RedisClient("redis://localhost:6379");
  let count = 0;

  // 在独立线程里跑 CPU 密集逻辑,不阻塞主事件循环
  for (const key of e.data) {
    const data = await redis.hgetall(key);
    const encrypted = encrypt(JSON.stringify(data));
    await redis.send("HSET", [key, "data", encrypted]); // 见下文 Streams 同款 send
    count++;
  }

  self.postMessage(count);
};

| 版本 | 耗时 | CPU 利用率 | 事件循环阻塞 | | :-- | :-- | :-- | :-- | | 朴素串行 | 3-5 小时 | 25%(单核) | 完全卡死 | | 自动 Pipeline(单线程) | 40-60 分钟 | 25%(单核) | 加密时卡死 | | Worker + Bun.redis(4 核) | 10-15 分钟 | 100%(4 核满载) | 完全畅通 |

这就是 Bun.redis 真正的强项:不是"某一项特别强",而是"能让你把多线程 + 高并发 + 自动 Pipeline 这三件事用最少的代码做出来"。

下面我们一项一项拆开看。


一、强项全景图

| 维度 | Bun.redis | ioredis / node-redis | | :-- | :-- | :-- | | 安装 | ✅ 零依赖,内置 | ❌ 需 npm i,版本依赖地狱 | | 性能 | ⭐⭐⭐⭐⭐ Zig/Rust 内核 | ⭐⭐⭐⭐ JS + libuv | | Pipeline | ✅ 自动启用 | ❌ 需手动 .pipeline() | | TypeScript | ✅ 原生类型 | ⚠️ 需 @types/ioredis | | API 设计 | 类 fetch / Promise 风格 | 链式 .then() | | 冷启动 | 几乎零开销 | 多 2-5ms 解析依赖 | | 集群 / Pub-Sub / Stream | ✅ 全支持 | ✅ 支持但需额外配置 | | 多线程(Worker) | ✅ postMessage 快 400 倍 | ⚠️ 几百微秒序列化 |

一句话总结:你写的是 Bun 代码,Bun.redis 就像 fetchBun.file 一样自然,不用想"装哪个包"。


二、强项一:零依赖,原生集成

Node.js 生态里,Redis 客户端是"重资产":

bash 复制代码
# Node.js
npm install ioredis        # 还要装 @types/ioredis
# 版本冲突?TypeScript 报错?锁文件爆炸?

Bun 直接给你:

bash 复制代码
// Bun - 零依赖
const redis = new Bun.RedisClient("redis://localhost:6379");
await redis.set("key", "value");

没有 node_modules,没有版本冲突,没有 Cannot find module 'ioredis'。这在 Serverless、边缘计算、Docker 镜像里收益巨大------镜像小、冷启动快、依赖干净。


三、强项二:Zig/Rust 内核,性能碾压

Bun.redis 用 Zig/Rust 直接实现 RESP 协议解析,绕过 libuv,直接系统调用。

官方基准(GET/SET 场景)

| 客户端 | ops/sec | 延迟 P99 | 内存占用 | | :-- | :-- | :-- | :-- | | Bun.redis | ~280,000 | 0.3ms | 基准 | | ioredis | ~190,000 | 0.6ms | +40% | | node-redis | ~170,000 | 0.8ms | +50% |

关键点:

  • • 协议解析在原生代码里跑,不消耗 JS 线程时间

  • • TCP socket 用 epoll / kqueue / io_uring 直接管理

  • • 零拷贝 buffer 传递,JS 端拿到的是 TypedArray,不复制

压测代码

bash 复制代码
import { Bench } from "tinybench";

const bench = new Bench({ time: 5000 });

const redis = new Bun.RedisClient("redis://localhost:6379");

bench
  .add("Bun.redis SET", async () => {
    await redis.set(`k:${Math.random()}`, "value");
  })
  .add("Bun.redis GET", async () => {
    await redis.get("k:0.5");
  });

await bench.run();
console.table(bench.table());

四、强项三:自动 Pipeline,最强隐藏特性

这是 Bun.redis 最被低估 的强项------它默认就是 pipeline ,不用 .pipeline() 也不用 .multi()

1000 个请求对比

ioredis(需要手动 pipeline):

bash 复制代码
const redis = new Redis();

const pipeline = redis.pipeline();
for (let i = 0; i < 1000; i++) {
  pipeline.get(`key:${i}`);
}
const results = await pipeline.exec();

Bun.redis(自动 pipeline):

bash 复制代码
const redis = new Bun.RedisClient("redis://localhost:6379");

const promises = [];
for (let i = 0; i < 1000; i++) {
  promises.push(redis.get(`key:${i}`));
}
const results = await Promise.all(promises);

底层发生了什么?

性能差距:

| 模式 | 网络往返次数 | 耗时 | | :-- | :-- | :-- | | 串行 await(无 pipeline) | 1000 次 | ~3000ms | | ioredis 手动 pipeline | 1 次 | ~3ms | | Bun.redis 自动 pipeline | 1 次 | ~2ms |

1000 倍加速,就这么来的。


五、强项四:TypeScript 原生一等公民

不用 @types/xxx,不用 declare module,类型推断是顶级的:

bash 复制代码
const redis = new Bun.RedisClient("redis://localhost:6379");

// 完整类型提示
// 传对象会直接 TS 报错 + 运行时 TypeError,不会偷偷 toString
await redis.set("user:1", "Alice"); // 必须是 string | number | Buffer

const result = await redis.get("user:1");
// result: string | null  ← 明确告诉你可能为 null

// 批量 mget:注意是变长参数(不是数组)
const users = await redis.mget("user:1", "user:2", "user:3");
// users: (string | null)[]

// 哈希操作(hset 支持多种形式:单字段、变长对、对象)
await redis.hset("user:1", { name: "Alice", age: "30" });
// 等价于:
//   await redis.hmset("user:1", ["name", "Alice", "age", "30"]);
//   await redis.hset("user:1", "name", "Alice", "age", "30");

const user = await redis.hgetall("user:1");
// user: { name: string, age: string, ... }(实际是带 null 原型的对象)

链式 API 也有完整类型

bash 复制代码
// ZADD 返回添加的元素数量
const added: number = await redis.zadd("scores", 100, "alice", 200, "bob");

// ZRANGEBYSCORE 返回数组
const top: string[] = await redis.zrangebyscore("scores", 0, 1000);

六、强项五:现代 API 设计

Bun.redis 的 API 借鉴了 fetch / Promise 的现代风格,比 ioredis 的链式调用清爽得多。

连接管理

bash 复制代码
// 简单连接
const redis = new Bun.RedisClient("redis://localhost:6379");

// URL 携带认证
const redis2 = new Bun.RedisClient("redis://:password@host:6379/0");

// TLS + 集群
const redis3 = new Bun.RedisClient("rediss://cluster.example.com:6380", {
  tls: { rejectUnauthorized: true },
});

// 带连接选项(注意:没有 reconnect 字段,是 autoReconnect + maxRetries)
const redis4 = new Bun.RedisClient("redis://localhost:6379", {
  connectionTimeout: 5000,    // 连接超时(毫秒)
  idleTimeout: 30000,         // 空闲超时
  autoReconnect: true,        // 自动重连(默认 true)
  maxRetries: 10,             // 最大重试次数
  enableAutoPipelining: true, // 自动 Pipeline(默认 true)
});

Pub/Sub(原生支持)

bash 复制代码
const sub = new Bun.RedisClient("redis://localhost:6379");
await sub.subscribe("news", (message, channel) => {
  console.log(`[${channel}] ${message}`);
});

// 发布
const pub = new Bun.RedisClient("redis://localhost:6379");
await pub.publish("news", "Hello world");

Streams(事件流)

bash 复制代码
const redis = new Bun.RedisClient("redis://localhost:6379");

// ⚠️ Bun.redis 没有 xadd/xread 便捷方法,要用 send 走原始协议
// 生产者
const id = await redis.send("XADD", ["events", "*", "type", "click", "user", "alice"]);

// 消费者
const stream = await redis.send("XREAD", ["COUNT", "10", "STREAMS", "events", "0"]);
console.log(stream);

事务 / Lua 脚本

bash 复制代码
// ⚠️ Bun.redis 没有 multi()/eval() 便捷方法,要用 send
// MULTI/EXEC
await redis.send("MULTI", []);
await redis.send("SET", ["a", "1"]);
await redis.send("INCR", ["b"]);
const results = await redis.send("EXEC", []); // ["OK", 1]
console.log(results);

// EVAL
const result = await redis.send(
  "EVAL",
  ["return redis.call('GET', KEYS[1])", "1", "mykey"]
);

七、强项六:自动重连 + 集群感知

自动重连(开箱即用)

bash 复制代码
const redis = new Bun.RedisClient("redis://localhost:6379", {
  // ⚠️ 选项名是 autoReconnect / maxRetries,不是 reconnect
  autoReconnect: true, // 断线自动重连,无需自己写重试逻辑
  maxRetries: 10,      // 最多重试 10 次(默认)
  // 内部用 50ms 起步、每次翻倍的指数退避,封顶 2 秒
});

// 你只管 await,断网恢复后自动继续
const value = await redis.get("key");

// 还可以监听连接事件
redis.onconnect = () => console.log("connected");
redis.onclose = (err) => console.log("disconnected:", err);
console.log(redis.connected);       // boolean
console.log(redis.bufferedAmount);  // 当前缓冲字节数

集群模式

bash 复制代码
// 多个节点自动分片(用一个客户端连多个节点)
const cluster = new Bun.RedisClient([
  "redis://node1:6379",
  "redis://node2:6379",
  "redis://node3:6379",
]);

// 内部自动计算 slot、路由请求、处理节点故障
await cluster.set("user:1", "Alice");
const user = await cluster.get("user:1");

八、强项七:Worker 多线程,比 Node.js 快 400 倍

如果你真需要"计算 + Redis"两不误的多线程方案,Bun 的 Worker 是"最快的接缝"。

基础用法(回开放头那个例子)

bash 复制代码
// main.ts
const worker = new Worker("./worker.ts");
worker.postMessage({ keys: ["a", "b", "c", "d"] });
worker.onmessage = (e) => console.log("处理完成:", e.data);
bash 复制代码
// worker.ts
declare var self: Worker;

self.onmessage = async (e) => {
  const redis = new Bun.RedisClient("redis://localhost:6379");
  const results = [];

  // 在 Worker 线程里跑 CPU 密集逻辑
  for (const key of e.data.keys) {
    const value = await redis.get(key);
    results.push(transform(value)); // 假设 transform 很耗时
  }

  self.postMessage(results);
};

性能差距(Bun 1.3.14 vs Node.js 24.6.0)

postMessage 传 3MB 字符串:

| 运行时 | 耗时 | 内存峰值 | | :-- | :-- | :-- | | Bun 1.3.14 | 593 ns | 极少 | | Bun 1.2.21 | 326,290 ns | 多 | | Node.js 24.6.0 | 242,110 ns | 多 22 倍 |

关键原理 :JavaScriptCore 引擎里的字符串是线程安全的引用计数对象 ,Bun 干脆零拷贝传指针,根本不序列化。Node.js 还在用结构化克隆算法完整复制。

实战:通用 Worker Pool

bash 复制代码
type Task = { id: number; key: string };
type Result = { id: number; value: string | null };

export class RedisWorkerPool {
  private workers: Worker[] = [];
  private queue: Task[] = [];
  private busy = new Set<Worker>();
  private results: Result[] = [];
  private expected = 0;
  private onAllDone?: () => void;

  constructor(workerPath: string, size = 4) {
    for (let i = 0; i < size; i++) {
      const w = new Worker(workerPath);
      w.onmessage = (e) => this.handleResult(w, e.data);
      this.workers.push(w);
    }
  }

  process(tasks: Task[]): Promise<Result[]> {
    this.expected = tasks.length;
    this.results = [];
    this.queue.push(...tasks);
    this.dispatch();
    return new Promise((resolve) => {
      this.onAllDone = () => resolve(this.results);
    });
  }

  private dispatch() {
    while (this.queue.length > 0) {
      const idle = this.workers.find((w) => !this.busy.has(w));
      if (!idle) break;
      const task = this.queue.shift()!;
      this.busy.add(idle);
      idle.postMessage(task);
    }
  }

  private handleResult(worker: Worker, result: Result) {
    this.busy.delete(worker);
    this.results.push(result);
    if (this.results.length >= this.expected) {
      // 所有任务完成,触发外层 resolve
      this.onAllDone?.();
    } else {
      this.dispatch();
    }
  }

  terminate() {
    this.workers.forEach((w) => w.terminate());
  }
}

说明 :文章前一版 process() 里的 Promise 永远不会 resolve------handleResult 走到 "所有任务完成" 分支时只留了个空注释。这里加了 expected 计数 + onAllDone 回调,让 process() 能真正返回结果。

适用场景

| 场景 | 是否需要 Worker | | :-- | :-- | | 普通的 GET / SET / HGETALL | ❌ 不需要,async/await 够了 | | 批量 Pipeline 上万个 key | ❌ 不需要,Bun.redis 自己处理 | | 拿到数据后做复杂计算(JSON 解析、加密、聚合) | ✅ 用 Worker 避免阻塞 | | 多个独立任务并行处理(比如同时跑 5 个报表) | ✅ 一个任务一个 Worker | | 大数据量序列化 / 反序列化 | ✅ Worker 隔离 |


九、对比表:为什么值得切换?

| 维度 | Bun.redis | ioredis | node-redis | | :-- | :-- | :-- | :-- | | 安装体积 | 0 KB | ~150 KB | ~200 KB | | 冷启动开销 | < 0.1ms | 2-3ms | 3-5ms | | 吞吐量(pipeline) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | | 内存占用 | 极低 | 中 | 中 | | TypeScript | 原生 | 需 types | 需 types | | 自动 pipeline | ✅ | ❌ | ❌ | | Cluster 复杂度 | 低 | 中 | 中 | | postMessage(3MB 字符串) | 593 ns | --- | 242,110 ns | | 生态成熟度 | 新(1.0+) | 老牌(10 年) | 老牌(10 年) | | 文档 / Stack Overflow | 较少 | 极丰富 | 极丰富 |

迁移成本:

  • • API 90% 相似,常用命令(GET/SET/HSET/ZADD/EXPIRE)名字都一样

  • • 最大的差异:ioredis 的链式 → Bun.redis 的 async/await

  • • Redis Cluster、Pub/Sub、Stream 都能用


十、实战:3 行代码搭一个 Redis 缓存层

bash 复制代码
// cache.ts
const redis = new Bun.RedisClient("redis://localhost:6379");

export async function cache<T>(key: string, ttl: number, fetcher: () => Promise<T>): Promise<T> {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const fresh = await fetcher();
  // ⚠️ Bun.redis 的 set() 不支持选项对象,要走 send 设过期
  await redis.send("SET", [key, JSON.stringify(fresh), "EX", String(ttl)]);
  return fresh;
}

// 使用
const user = await cache("user:1", 60, () => db.user.find(1));

没有 ioredis,没有 @types/ioredis,没有版本警告------就这么简单。


十一、注意事项:Bun.redis 还不能用的场景

诚实地讲,Bun.redis 不适合所有人

| 场景 | 建议 | | :-- | :-- | | 需要在 Node.js 里跑 | ❌ 用 ioredis(API 完全不同) | | 用了 ioredis 的高级插件(哨兵、Cluster 复杂配置) | ⚠️ 评估迁移成本 | | 已有大量 ioredis 代码 | ⚠️ 一次性迁移不划算 | | Bun 版本 < 1.0 | ❌ Redis 客户端还不稳定 | | 团队不熟悉 Bun | ⚠️ 学习曲线 |


十二、总结

Bun.redis 真正的强项,归纳成 6 个词:

一句话: Bun.redis 不是一个"Redis 客户端库",而是 Bun 运行时的"内置基础设施" ------就像 fetchBun.fileBun.serve 一样,写 Bun 代码就该用它。

如果你想"榨干 CPU + Redis 的最后一点性能",那就把它丢进 Worker 吧------4 核机器,4 倍加速,仅此而已。


📖 参考资料

  • • Bun 官方文档 - Redis Client

  • • Bun 官方文档 - Workers

  • • Bun 1.3.14 postMessage 性能优化

  • • Bun 性能基准对比

  • • RESP 协议规范

相关推荐
用户938515635071 小时前
Node.js 路径与文件系统:从基础到异步进化之路
javascript·后端·面试
Cobyte2 小时前
23.Vue Vapor 组件 attrs 的实现
前端·javascript·vue.js
触底反弹2 小时前
🚀 Node.js path 和 fs 模块,从回调地狱到 async/await 的进化之路!
javascript·后端·node.js
绝世唐门三哥3 小时前
vue3中页面返回时刷新首页的处理方案
开发语言·前端·javascript
sugar__salt3 小时前
Node.js path 与 fs 核心模块完全指南 —— 从路径处理到异步流程控制
javascript·node.js
张元清4 小时前
React useObjectUrl Hook:预览文件与 Blob,不留内存泄漏(2026)
javascript·react.js
姑苏倾城客4 小时前
Flutter中,html 与 dart 桥接沟通
javascript·flutter·html
小粉粉hhh5 小时前
Node.js(五)——编写接口
前端·javascript·node.js
大意的百合6 小时前
Electron 应用如何上架微软商店:从 MSIX 打包到商店提交
javascript·microsoft·electron