> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Redis

> 使用 Bun 的原生 Redis 客户端，提供基于 Promise 的 API

<Note>Bun 的 Redis 客户端支持 Redis 服务器 7.2 及以上版本。</Note>

Bun 的原生 Redis 客户端拥有基于 Promise 的 API，具备内置连接管理、完整类型化响应和 TLS 支持。

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { redis } from "bun";

// 设置一个键
await redis.set("greeting", "Hello from Bun!");

// 获取一个键
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"

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

// 检查键是否存在
const exists = await redis.exists("greeting");

// 删除一个键
await redis.del("greeting");
```

***

## 入门

要使用 Redis 客户端，首先需要创建一个连接：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { redis, RedisClient } from "bun";

// 使用默认客户端（从环境读取连接信息）
// 默认使用 process.env.REDIS_URL
await redis.set("hello", "world");
const result = await redis.get("hello");

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

默认情况下，客户端按优先顺序从以下环境变量读取连接信息：

* `REDIS_URL`
* `VALKEY_URL`
* 如果未设置，默认使用 `"redis://localhost:6379"`

### 连接生命周期

Redis 客户端会自动在后台管理连接：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 只有在执行命令时才会建立连接
const client = new RedisClient();

// 第一个命令发起连接
await client.set("key", "value");

// 后续命令连接保持打开
await client.get("key");

// 完成后显式关闭连接
client.close();
```

你也可以手动控制连接生命周期：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const client = new RedisClient();

// 显式连接
await client.connect();

// 执行命令
await client.set("key", "value");

// 完成后断开连接
client.close();
```

***

## 基本操作

### 字符串操作

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 设置一个键
await redis.set("user:1:name", "Alice");

// 获取一个键
const name = await redis.get("user:1:name");

// 以 Uint8Array 形式获取键
const buffer = await redis.getBuffer("user:1:name");

// 删除一个键
await redis.del("user:1:name");

// 检查键是否存在
const exists = await redis.exists("user:1:name");

// 设置过期时间（秒）
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // 1 小时后过期

// 获取剩余生存时间（秒）
const ttl = await redis.ttl("session:123");
```

### 数值操作

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 设置初始值
await redis.set("counter", "0");

// 递增 1
await redis.incr("counter");

// 递减 1
await redis.decr("counter");
```

### 哈希操作

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 设置哈希中的多个字段
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);

// 获取哈希中的多个字段
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]

// 获取哈希中的单个字段（直接返回值，不存在时返回 null）
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"

// 递增哈希中的数值字段
await redis.hincrby("user:123", "visits", 1);

// 递增哈希中的浮数字段
await redis.hincrbyfloat("user:123", "score", 1.5);
```

### 集合操作

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 向集合添加成员
await redis.sadd("tags", "javascript");

// 从集合移除成员
await redis.srem("tags", "javascript");

// 检查成员是否在集合中
const isMember = await redis.sismember("tags", "javascript");

// 获取集合的所有成员
const allTags = await redis.smembers("tags");

// 获取随机成员
const randomTag = await redis.srandmember("tags");

// 弹出（移除并返回）随机成员
const poppedTag = await redis.spop("tags");
```

***

## 发布/订阅

Bun 为 [Redis 发布/订阅](https://redis.io/docs/latest/develop/pubsub/)协议提供了原生绑定，于 Bun 1.2.23 中添加。

<Warning>
  Redis 发布/订阅是实验性功能。我们预计它会稳定，但仍欢迎反馈和改进建议。
</Warning>

### 基本用法

在 `publisher.ts` 中创建发布者：

```typescript publisher.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { RedisClient } from "bun";

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

writer.publish("general", "Hello everyone!");

writer.close();
```

在另一个文件 `subscriber.ts` 中创建订阅者：

```typescript subscriber.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { RedisClient } from "bun";

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

await listener.subscribe("general", (message, channel) => {
  console.log(`Received: ${message}`);
});
```

在一个终端中运行订阅者：

```bash terminal icon="terminal" theme={null}
bun run subscriber.ts
```

在另一个终端中运行发布者：

```bash terminal icon="terminal" theme={null}
bun run publisher.ts
```

<Note>
  订阅会接管 `RedisClient` 连接：有订阅的客户端只能调用
  `RedisClient.prototype.subscribe()`。要发送其他 Redis
  命令，需要使用 `.duplicate()` 创建单独连接：

  ```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
  import { RedisClient } from "bun";

  const redis = new RedisClient("redis://localhost:6379");
  await redis.connect();
  const subscriber = await redis.duplicate(); // [!code ++]

  await subscriber.subscribe("foo", () => {});
  await redis.set("bar", "baz");
  ```
</Note>

### 发布

使用 `publish()` 方法发布消息：

```typescript redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
await client.publish(channelName, message);
```

### 订阅

使用 `.subscribe()` 方法订阅频道：

```typescript redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
await client.subscribe(channel, (message, channel) => {});
```

使用 `.unsubscribe()` 方法取消订阅：

```typescript redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
await client.unsubscribe(); // 取消订阅所有频道
await client.unsubscribe(channel); // 取消订阅特定频道
await client.unsubscribe(channel, listener); // 取消订阅特定监听器
```

## 高级用法

### 命令执行与管道化

客户端默认自动进行管道化，通过批量发送多个命令并随响应到达时处理来提高性能。

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 默认自动管道化命令
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);
```

要禁用自动管道化，将 `enableAutoPipelining` 选项设置为 `false`：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const client = new RedisClient("redis://localhost:6379", {
  enableAutoPipelining: false, // [!code ++]
});
```

### 原始命令

使用 `send` 方法运行任何 Redis 命令，包括没有专用方法的命令。第一个参数是命令名称，第二个是字符串参数数组。

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 运行任何 Redis 命令
const info = await redis.send("INFO", []);

// LPUSH 到列表
await redis.send("LPUSH", ["mylist", "value1", "value2"]);

// 获取列表范围
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);
```

### 连接事件

你可以为连接事件注册处理函数：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const client = new RedisClient();

// 成功连接到 Redis 服务器时调用
client.onconnect = () => {
  console.log("Connected to Redis server");
};

// 从 Redis 服务器断开连接时调用
client.onclose = error => {
  console.error("Disconnected from Redis server:", error);
};

// 手动连接/断开
await client.connect();
client.close();
```

### 连接状态与监控

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 检查是否已连接
console.log(client.connected); // 表示连接状态的布尔值

// 检查缓冲数据量（字节）
console.log(client.bufferedAmount);
```

### 类型转换

客户端自动将 Redis 响应转换为 JavaScript 值：

* 整数响应作为 JavaScript 数字返回
* 批量字符串作为 JavaScript 字符串返回
* 简单字符串作为 JavaScript 字符串返回
* 空批量字符串作为 `null` 返回
* 数组响应作为 JavaScript 数组返回
* 错误响应抛出带有相应错误码的 JavaScript 错误
* 布尔响应（RESP3）作为 JavaScript 布尔值返回
* 映射响应（RESP3）作为 JavaScript 对象返回
* 集合响应（RESP3）作为 JavaScript 数组返回

特定命令的特殊处理：

* `EXISTS` 返回布尔值而不是数字（1 变为 true，0 变为 false）
* `SISMEMBER` 返回布尔值（1 变为 true，0 变为 false）

以下命令禁用自动管道化：

* `AUTH`
* `INFO`
* `QUIT`
* `EXEC`
* `MULTI`
* `WATCH`
* `SCRIPT`
* `SELECT`
* `CLUSTER`
* `DISCARD`
* `UNWATCH`
* `PIPELINE`
* `SUBSCRIBE`
* `PSUBSCRIBE`
* `UNSUBSCRIBE`
* `UNPSUBSCRIBE`

***

## 连接选项

创建客户端时，可以传递选项来配置连接：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
const client = new RedisClient("redis://localhost:6379", {
  // 连接超时时间（毫秒，默认：10000）
  connectionTimeout: 5000,

  // 空闲超时时间（毫秒，默认：0 = 无超时）
  idleTimeout: 30000,

  // 断开连接时是否自动重连（默认：true）
  autoReconnect: true,

  // 最大重连尝试次数（默认：20）
  maxRetries: 10,

  // 断开连接时是否将命令排队（默认：true）
  enableOfflineQueue: true,

  // 是否自动管线化命令（默认：true）
  enableAutoPipelining: true,

  // TLS 选项（默认：false）
  tls: true,
  // 或者，提供自定义 TLS 配置：
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   cert: "path/to/cert.pem",
  //   key: "path/to/key.pem",
  // }
});
```

### 重连行为

当连接丢失时，客户端会自动尝试使用指数退避重连：

1. 客户端以较小延迟（50ms）开始，每次尝试加倍
2. 重连延迟上限为 2000ms（2 秒）
3. 客户端最多尝试重连 `maxRetries` 次（默认：20）
4. 断开连接期间执行的命令：
   * 如果 `enableOfflineQueue` 为 true（默认），则排队等待
   * 如果 `enableOfflineQueue` 为 false，则立即拒绝

***

## 支持的 URL 格式

Redis 客户端支持各种 URL 格式：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 标准 Redis URL
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");

// 带认证
new RedisClient("redis://username:password@localhost:6379");

// 带数据库编号
new RedisClient("redis://localhost:6379/0");

// TLS 连接
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");

// Unix 套接字连接
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");

// Unix 套接字上的 TLS
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");
```

***

## 错误处理

Redis 客户端针对不同场景抛出类型化错误：

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
try {
  await redis.get("non-existent-key");
} catch (error) {
  if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
    console.error("到 Redis 服务器的连接已关闭");
  } else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
    console.error("认证失败");
  } else {
    console.error("意外错误:", error);
  }
}
```

常见错误码：

* `ERR_REDIS_CONNECTION_CLOSED` - 到服务器的连接已关闭
* `ERR_REDIS_AUTHENTICATION_FAILED` - 无法通过服务器认证
* `ERR_REDIS_INVALID_RESPONSE` - 收到来自服务器的无效响应

***

## 用例示例

### 缓存

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
async function getUserWithCache(userId) {
  const cacheKey = `user:${userId}`;

  // 首先尝试从缓存获取
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    return JSON.parse(cachedUser);
  }

  // 不在缓存中，从数据库获取
  const user = await database.getUser(userId);

  // 缓存 1 小时
  await redis.set(cacheKey, JSON.stringify(user));
  await redis.expire(cacheKey, 3600);

  return user;
}
```

### 限流

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
  const key = `ratelimit:${ip}`;

  // 递增计数器
  const count = await redis.incr(key);

  // 如果是窗口内的第一个请求，设置过期时间
  if (count === 1) {
    await redis.expire(key, windowSecs);
  }

  // 检查是否超过限制
  return {
    limited: count > limit,
    remaining: Math.max(0, limit - count),
  };
}
```

### 会话存储

```ts redis.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
async function createSession(userId, data) {
  const sessionId = crypto.randomUUID();
  const key = `session:${sessionId}`;

  // 存储会话并设置过期时间
  await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
  await redis.expire(key, 86400); // 24 小时

  return sessionId;
}

async function getSession(sessionId) {
  const key = `session:${sessionId}`;

  // 获取会话数据
  const exists = await redis.exists(key);
  if (!exists) return null;

  const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);

  return {
    userId: Number(userId),
    created: Number(created),
    data: JSON.parse(data),
  };
}
```

***

## 实现说明

Bun 的 Redis 客户端用 Rust 实现，使用 Redis 序列化协议（RESP3）。它会自动以指数退避重连，并将命令进行管道化，因此多个命令可以连续发送而无需等待前一个命令的回复。

## 局限性与未来计划

我们计划在未来版本中解决的局限性：

* 事务（MULTI/EXEC）需要通过原始命令实现

不支持的功能：

* Redis Sentinel
* Redis 集群
