NOTE
Bun 的 Redis 客户端支持 Redis 服务器 7.2 及以上版本。Bun 提供了用于处理 Redis 数据库的原生绑定,具有现代化、基于 Promise 的 API。该接口设计简单且高性能,具有内置连接管理、完全类型化的响应和 TLS 支持。
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 客户端,你首先需要创建连接:
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_URLVALKEY_URL- 如果未设置,默认为
"redis://localhost:6379"
连接生命周期
Redis 客户端自动在后台处理连接:
// 在执行命令之前不会建立连接
const client = new RedisClient();
// 第一个命令初始化连接
await client.set("key", "value");
// 连接保持打开以进行后续命令
await client.get("key");
// 完成后显式关闭连接
client.close();你也可以手动控制连接生命周期:
const client = new RedisClient();
// 显式连接
await client.connect();
// 运行命令
await client.set("key", "value");
// 完成后断开连接
client.close();基本操作
字符串操作
// 设置键
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");数值操作
// 设置初始值
await redis.set("counter", "0");
// 递增 1
await redis.incr("counter");
// 递减 1
await redis.decr("counter");哈希操作
// 在哈希中设置多个字段
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);集合操作
// 添加成员到集合
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 发布/订阅 协议的原生绑定。Bun 1.2.23 新增
基本用法
要开始发布消息,你可以在 publisher.ts 中设置发布者:
import { RedisClient } from "bun";
const writer = new RedisClient("redis://localhost:6739");
await writer.connect();
writer.publish("general", "Hello everyone!");
writer.close();在另一个文件中,在 subscriber.ts 中创建订阅者:
import { RedisClient } from "bun";
const listener = new RedisClient("redis://localhost:6739");
await listener.connect();
await listener.subscribe("general", (message, channel) => {
console.log(`收到:${message}`);
});在一个 shell 中,运行你的订阅者:
bun run subscriber.ts在另一个 shell 中,运行你的发布者:
bun run publisher.tsNOTE
订阅模式接管 `RedisClient` 连接。具有订阅的客户端只能调用 `RedisClient.prototype.subscribe()`。换句话说,需要向 Redis 发送消息的应用程序需要一个单独的连接,可通过 `.duplicate()` 获取:import { RedisClient } from "bun";
const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
const subscriber = await redis.duplicate();
await subscriber.subscribe("foo", () => {});
await redis.set("bar", "baz");发布
发布消息通过 publish() 方法完成:
await client.publish(channelName, message);订阅
Bun RedisClient 允许你通过 .subscribe() 方法订阅频道:
await client.subscribe(channel, (message, channel) => {});你可以通过 .unsubscribe() 方法取消订阅:
await client.unsubscribe(); // 取消订阅所有频道。
await client.unsubscribe(channel); // 取消订阅特定频道。
await client.unsubscribe(channel, listener); // 取消订阅特定监听器。高级用法
命令执行和流水线
客户端自动流水线命令,通过批量发送多个命令并在响应到达时处理它们来提高性能。
// 命令默认自动流水线
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);要禁用自动流水线,你可以将 enableAutoPipelining 选项设置为 false:
const client = new RedisClient("redis://localhost:6379", {
enableAutoPipelining: false,
});原始命令
当你需要使用没有便捷方法的命令时,你可以使用 send 方法:
// 运行任何 Redis 命令
const info = await redis.send("INFO", []);
// LPUSH 到列表
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
// 获取列表范围
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);send 方法允许你使用任何 Redis 命令,即使是客户端中没有专用方法的命令。第一个参数是命令名称,第二个参数是字符串参数数组。
连接事件
你可以为连接事件注册处理程序:
const client = new RedisClient();
// 成功连接到 Redis 服务器时调用
client.onconnect = () => {
console.log("已连接到 Redis 服务器");
};
// 从 Redis 服务器断开连接时调用
client.onclose = error => {
console.error("已从 Redis 服务器断开连接:", error);
};
// 手动连接/断开连接
await client.connect();
client.close();连接状态和监控
// 检查是否已连接
console.log(client.connected); // 布尔值指示连接状态
// 检查缓冲的数据量(字节)
console.log(client.bufferedAmount);类型转换
Redis 客户端自动处理 Redis 响应的类型转换:
- 整数响应作为 JavaScript 数字返回
- 批量字符串作为 JavaScript 字符串返回
- 简单字符串作为 JavaScript 字符串返回
- 空批量字符串作为
null返回 - 数组响应作为 JavaScript 数组返回
- 错误响应抛出带有适当错误码的 JavaScript 错误
- 布尔响应 (RESP3) 作为 JavaScript 布尔值返回
- 映射响应 (RESP3) 作为 JavaScript 对象返回
- 集合响应 (RESP3) 作为 JavaScript 数组返回
特定命令的特殊处理:
EXISTS返回布尔值而不是数字(1 变为 true,0 变为 false)SISMEMBER返回布尔值(1 变为 true,0 变为 false)
以下命令禁用自动流水线:
AUTHINFOQUITEXECMULTIWATCHSCRIPTSELECTCLUSTERDISCARDUNWATCHPIPELINESUBSCRIBEUNSUBSCRIBEPUNSUBSCRIBE
连接选项
创建客户端时,你可以传递各种选项来配置连接:
const client = new RedisClient("redis://localhost:6379", {
// 连接超时(毫秒)(默认:10000)
connectionTimeout: 5000,
// 空闲超时(毫秒)(默认:0 = 无超时)
idleTimeout: 30000,
// 断开连接时是否自动重连(默认:true)
autoReconnect: true,
// 最大重连尝试次数(默认:10)
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",
// }
});重连行为
当连接丢失时,客户端自动尝试使用指数退避重连:
- 客户端从小延迟(50ms)开始,每次尝试加倍
- 重连延迟上限为 2000ms(2 秒)
- 客户端尝试重连最多
maxRetries次(默认:10) - 断开连接期间执行的命令:
- 如果
enableOfflineQueue为 true(默认)则排队 - 如果
enableOfflineQueue为 false 则立即拒绝
- 如果
支持的 URL 格式
Redis 客户端支持各种 URL 格式:
// 标准 Redis URL
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("redis+tls://localhost:6379");
// Unix socket 连接
new RedisClient("redis+unix:///path/to/socket");
// TLS over Unix socket
new RedisClient("redis+tls+unix:///path/to/socket");错误处理
Redis 客户端为不同场景抛出类型化错误:
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- 从服务器收到无效响应
示例用例
缓存
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;
}速率限制
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),
};
}会话存储
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 客户端用 Zig 实现,使用 Redis 序列化协议 (RESP3)。它高效管理连接并提供带指数退避的自动重连。
客户端支持命令流水线,这意味着可以发送多个命令而无需等待先前命令的回复。这在连续发送多个命令时显著提高性能。
限制和未来计划
我们计划在将来版本中解决的 Redis 客户端当前限制:
- 事务 (MULTI/EXEC) 目前必须通过原始命令完成
不支持的功能:
- Redis Sentinel
- Redis Cluster