12k
All articles

使用 Bun 内置 Redis 客户端

Bun 内置 Redis 客户端指南:连接、类型化命令、Pub/Sub、自动流水线、限流,以及何时改用 ioredis。

OpenReplay Team
OpenReplay Team
使用 Bun 内置 Redis 客户端

Bun 内置了一个 Redis 客户端,可直接通过 import { redis, RedisClient } from "bun" 导入——因此对于大多数缓存、会话、发布/订阅和限流场景,你无需再添加 ioredisnode-redis。Bun 的原生 Redis 客户端提供基于 Promise 的 API,内置连接管理、完整类型响应和 TLS 支持,并兼容 Redis 7.2 及以上版本。本指南专门介绍该内置客户端——而非运行在 Bun 上的 ioredis,后者才是大多数”Bun + Redis”教程中实际演示的内容。

这一区别至关重要。现有 Bun + Redis 教程中有一种常见模式:执行 bun add ioredis 并调用 new Redis()——那是第三方库,而非运行时 API。以下所有内容均使用 Bun 本身内置的 API。

核心要点

  • 通过 import { redis, RedisClient } from "bun" 导入客户端;默认 redis 实例依次读取 REDIS_URLVALKEY_URL,若均未设置则回退至 redis://localhost:6379
  • 客户端在第一条命令执行前不会建立连接,自动对命令进行流水线处理,并以指数退避方式重连——初始延迟 50ms,最大延迟 2000ms,默认重试 10 次。
  • 截至 Bun 1.3,客户端将 66 条 Redis 命令封装为类型化方法;redis.send(command, argsArray) 可执行任何未封装的命令——这也是目前执行 MULTI/EXEC 的方式。
  • 发布/订阅会独占一个连接,因此需调用 .duplicate() 获取第二个连接用于发布消息或执行其他命令。
  • 截至 Bun 1.3.x,内置客户端不支持 Redis Cluster 或 Sentinel;如有需要,请使用 node-redisioredis

使用内置 Redis 客户端建立连接

最快捷的方式是使用默认的 redis 导出,它会从环境变量中懒加载连接信息。默认情况下,客户端依次从 REDIS_URLVALKEY_URL 读取连接信息,若均未设置则默认使用 redis://localhost:6379。在发出第一条命令之前,不会建立任何 socket 连接。

import { redis, RedisClient } from "bun";

// 默认客户端——从环境变量读取 REDIS_URL / VALKEY_URL
await redis.set("hello", "world");
const result = await redis.get("hello");

// 使用显式 URL 创建自定义客户端
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");

对于应用级全局访问,使用默认 redis 实例;当需要独立连接、不同配置项或不同数据库时,自行构造 RedisClientBun Redis 文档列出了所有支持的 URL 格式,包括用于 TLS 的 rediss://redis+tls://,以及用于 Unix socket 的 redis+unix://

连接管理由客户端自动处理:

  • 在执行命令之前不会建立连接;第一条命令触发连接建立,此后连接保持开启,直到调用 client.close()
  • 连接断开时,客户端会自动恢复:从 50ms 延迟开始,每次尝试翻倍,最大重连延迟为 2000ms,最多重试 maxRetries 次(默认 10 次)。
  • 断线期间发出的命令,在 enableOfflineQueue 为 true(默认值)时会进入队列,为 false 时则立即被拒绝。

这些行为与你在 ioredis 中需要手动配置的完全一致。

核心操作与 send() 回退机制

客户端为常用 Redis 命令提供类型化方法,并将响应自动转换为原生 JavaScript 值,省去大部分手动解析工作:

  • 整数响应返回为 JavaScript number;
  • 批量字符串和简单字符串返回为 string;
  • null 批量字符串返回为 null
  • 数组返回为 JavaScript 数组。

此外还有命令级别的类型强制转换:EXISTS 返回 boolean 而非 number,SISMEMBER 同样返回 boolean。

可通过 options 对象调整任意配置:

const client = new RedisClient("redis://localhost:6379", {
  connectionTimeout: 5000,     // 默认 10000 ms
  autoReconnect: true,         // 默认 true
  maxRetries: 10,              // 默认 10
  enableOfflineQueue: true,    // 默认 true
  enableAutoPipelining: true,  // 默认 true
  tls: true,
});
// 字符串与过期时间
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // 秒
const ttl = await redis.ttl("session:123");

// 计数器
await redis.set("counter", "0");
await redis.incr("counter");
await redis.decr("counter");

// exists() 返回 boolean
const present = await redis.exists("session:123"); // true

// 哈希
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com"]);
const [name, email] = await redis.hmget("user:123", ["name", "email"]);

并非所有命令都有专属方法。截至 Bun 1.3,所有标准操作均已支持——包括哈希、列表和集合,共计 66 条命令。对于不在此范围内的命令,使用 send()send 方法可执行任意 Redis 命令,包括没有专属方法的命令;第一个参数为命令名称,第二个参数为字符串参数数组。

// 原始命令回退——命令名 + 字符串参数数组
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);

send() 也是目前执行事务的方式,这是客户端的主要 API 缺口,将在后文介绍。

使用 .duplicate() 实现发布/订阅

Redis 发布/订阅功能已受支持,但已订阅的连接无法执行其他操作。订阅操作会独占 RedisClient 连接:持有订阅的客户端只能调用 subscribe(),因此若要发送其他命令,需通过 .duplicate() 创建独立连接。订阅回调函数的参数顺序为:消息在前,频道在后。

import { RedisClient } from "bun";

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

// 用于发布消息和执行其他命令的第二个连接
const subscriber = await redis.duplicate();

await subscriber.subscribe("notifications", (message, channel) => {
  console.log(`[${channel}] ${message}`);
});

await redis.publish("notifications", "Hello from Bun!");

发布/订阅功能在 Bun 1.2.23 中加入,文档仍将其标记为实验性——实际使用中已较为稳定,但若依赖此功能,建议锁定 Bun 版本。使用 .unsubscribe() 取消订阅(不传参数则清除所有频道;传入频道名或监听器可精确取消)。

自动流水线

内置客户端默认开启流水线——无需手动启用。客户端自动对命令进行流水线处理,通过批量发送命令并按序处理响应来提升性能。同时发出多个 await 命令(例如通过 Promise.all),它们会作为一个批次发出,而非每条命令单独一次往返:

const [a, b, c] = await Promise.all([
  redis.get("user:1:name"),
  redis.get("user:2:name"),
  redis.get("user:3:name"),
]);

自动流水线正是 Bun 内置客户端与 ioredis 拉开性能差距的关键所在。Bun 将其 Redis 客户端描述为显著快于 ioredis,且批量越大优势越明显。Bun 在 1.2.9 版本发布说明中公布了可复现的性能数据:在 GET 基准测试中,批量为 10 时 Bun.redis 快 44.82%,批量为 100 时快 58.29%,批量为 1000 时快 85.39%——在最大批量下约为 1.85 倍,并非固定倍数。(Bun 在其 1.3 版本博客中另外发布了一份基准测试图表,显示内置客户端吞吐量超过 ioredis 的 7.9 倍——该数据为 Bun 官方公布,并非二手引用。)

部分命令因具有状态性而会禁用自动流水线,包括 AUTHINFOMULTIEXECWATCHSUBSCRIBESELECT。若要全局关闭流水线,设置 enableAutoPipelining: false

实战模式:缓存、会话与限流

以下模式在任何 Redis 客户端中均适用,此处完全基于内置 API 实现。可将其视为一个独立的小型服务模块。

旁路缓存(Cache-aside)。 优先查询 Redis,未命中时回退到数据库,然后将结果写入缓存并设置 TTL。

import { redis } from "bun";

async function getUser(userId: string) {
  const cacheKey = `user:${userId}`;
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  const user = await db.getUser(userId);       // 你的数据源
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);          // 1 小时
  return user;
}

对于写穿(write-through),在写入数据库的同一操作中同步更新缓存。对于 stale-while-revalidate,立即返回缓存值,并在 TTL 临近时在后台刷新。

带 TTL 的会话存储。 将会话存储为哈希,由 Redis 自动过期。

async function createSession(userId: number, data: object) {
  const sessionId = crypto.randomUUID();
  const key = `session:${sessionId}`;
  await redis.hmset(key, [
    "userId", String(userId),
    "created", String(Date.now()),
    "data", JSON.stringify(data),
  ]);
  await redis.expire(key, 86400); // 24 小时
  return sessionId;
}

基于有序集合的滑动窗口限流。 有序集合命令没有专属封装方法,因此这里正好使用 send()。删除窗口外的旧条目,统计剩余数量,然后添加当前请求。

async function rateLimit(id: string, limit = 100, windowMs = 60_000) {
  const key = `ratelimit:${id}`;
  const now = Date.now();

  await redis.send("ZREMRANGEBYSCORE", [key, "0", String(now - windowMs)]);
  const count = Number(await redis.send("ZCARD", [key]));

  if (count >= limit) return { allowed: false, remaining: 0 };

  await redis.send("ZADD", [key, String(now), `${now}-${crypto.randomUUID()}`]);
  await redis.expire(key, Math.ceil(windowMs / 1000));
  return { allowed: true, remaining: limit - count - 1 };
}

由于客户端自动流水线,ZREMRANGEBYSCORE/ZCARD/ZADD/EXPIRE 这一序列会高效批量发送,无需手动创建 pipeline 对象。

局限性:何时仍应使用 ioredis 或 node-redis

内置客户端能覆盖上述常见场景,但截至 Bun 1.3.x,它存在一些已知缺口。事务(MULTI/EXEC)必须通过原始命令执行,Redis Sentinel 和 Redis Cluster 均不支持。如果你需要集群、基于 Sentinel 的故障转移,或 Redis Stack 模块(搜索、JSON、时序、概率数据结构),请使用功能完整的第三方库。Bun 官方跟踪该迁移进展的 issue 推荐 node-redis 作为新项目的首选客户端,ioredis 作为成熟的备选方案。1.3 版本博客提到,对集群、流和 Lua 脚本的支持正在开发中——在确认当前状态前,请务必查阅文档的”局限性”章节,因为这一领域仍在持续演进。

功能Bun 内置 redisnode-redis / ioredis
安装步骤无——随 Bun 内置bun add node-redis / ioredis
自动流水线默认开启可配置 / 手动
发布/订阅支持(实验性,通过 .duplicate()支持
事务(MULTI/EXEC)仅通过原始 send()原生 API
Redis Cluster不支持支持
Redis Sentinel不支持支持
Redis Stack 模块需使用第三方库node-redis 支持

有一个实现细节值得说明:文档将该客户端描述为原生编译(目前被描述为用 Rust 实现,这是 Bun 在 2026 年从 Zig 迁移后的结果)。这一细节不会影响你的任何应用代码——无论底层语言如何,API 接口完全一致。

对于缓存、会话、发布/订阅、计数器和限流场景,内置客户端已经足够,减少一个依赖本身就是实实在在的收益。一旦需要 Cluster、Sentinel、Redis Stack 或复杂事务流,安装 node-redis。建议从 import { redis } from "bun" 连接本地服务器开始,确认所需命令集均已覆盖,并锁定 Bun 版本,以免不断演进的 API 在生产环境中带来意外。

常见问题

如何使用 Bun 内置客户端执行 Redis 事务?

Bun 内置客户端没有原生的 MULTI/EXEC API,因此需要通过原始 send() 方法执行事务。依次调用 redis.send('MULTI', [])、各条入队命令,最后调用 redis.send('EXEC', [])。由于 MULTI、EXEC 和 WATCH 会禁用自动流水线,它们会单独发送而非批量处理。如果需要更完善的原生事务 API,node-redis 或 ioredis 均提供相应支持。

Bun 内置 Redis 客户端是否支持 Redis Cluster 或 Sentinel?

不支持。截至 Bun 1.3.x,内置客户端不支持 Redis Cluster 或 Redis Sentinel,文档的"局限性"章节对此有明确说明。如需分片集群、基于 Sentinel 的故障转移,或 Redis Stack 模块(如搜索、JSON、时序),请改用功能完整的第三方库。Bun 官方迁移 issue 推荐 node-redis 作为新项目的首选客户端,ioredis 作为成熟的备选方案。

使用 Bun 内置 redis 导入与在 Bun 上运行 ioredis 有何区别?

内置客户端是通过 import { redis, RedisClient } from 'bun' 访问的运行时 API,无需安装,默认开启命令流水线。运行 ioredis 则需要 bun add ioredis 并使用 new Redis(),这是一个拥有独立连接管理机制的第三方库。大多数"Bun + Redis"教程实际上默默使用的是 ioredis。内置客户端零依赖;而 ioredis 提供了内置客户端所缺少的原生 Cluster、Sentinel 和事务 API。

Bun Redis 客户端断线期间发出的命令会怎样处理?

由于 enableOfflineQueue 默认为 true,断线期间发出的命令会进入队列,待连接恢复后重新执行。客户端使用指数退避策略自动重连:从 50ms 延迟开始,每次尝试翻倍,最大延迟 2000ms,默认重试 10 次。将 enableOfflineQueue 设为 false,可在断线时立即拒绝命令,而非将其缓冲。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.