连接配置与客户端生命周期
Redis 客户端应被视为应用级资源,而不是请求级临时对象。
推荐的单例连接模块
import { createClient } from "redis";
const redisUrl: string = process.env.REDIS_URL ?? "redis://localhost:6379";
export const redisClient = createClient({
url: redisUrl,
socket: {
connectTimeout: 5_000,
reconnectStrategy(retries: number): number {
const jitter: number = Math.floor(Math.random() * 200);
const delay: number = Math.min(2 ** retries * 50, 2_000);
return delay + jitter;
},
},
});
redisClient.on("error", (error: Error) => {
console.error("Redis error", error);
});
redisClient.on("reconnecting", () => {
console.warn("Redis 正在重连");
});
export async function connectRedis(): Promise<void> {
if (!redisClient.isOpen) {
await redisClient.connect();
}
}
服务启动时调用一次 connectRedis(),路由中复用 redisClient。
isOpen 与 isReady
isOpen:底层 Socket 是否打开,包括正在连接或重连的状态。isReady:客户端是否已经准备好执行命令。
健康检查通常更关心“能否完成一次 Redis 操作”,因此 PING 比单独读取布尔值更有说服力:
async function checkRedis(): Promise<boolean> {
if (!redisClient.isReady) {
return false;
}
try {
return (await redisClient.ping()) === "PONG";
} catch {
return false;
}
}
不要在每个业务请求中都执行 PING,它本身也是一次网络请求。
URL 与分离参数
常见 URL 格式:
redis[s]://[[username][:password]@][host][:port][/database]
也可以使用分离参数:
const client = createClient({
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD,
database: 0,
socket: {
host: process.env.REDIS_HOST ?? "localhost",
port: Number(process.env.REDIS_PORT ?? 6379),
},
});
如果环境变量可能不存在,启用了 exactOptionalPropertyTypes 的项目不应显式传入 undefined。更简单的做法是优先使用完整的 REDIS_URL。
TLS
连接云 Redis 或跨不可信网络时通常需要 TLS:
const client = createClient({
url: process.env.REDIS_URL,
socket: {
tls: true,
servername: "redis.example.com",
},
});
不要在生产环境为了省事设置 rejectUnauthorized: false,这会削弱证书校验。
离线队列
客户端尚未就绪或暂时断线时,命令可能进入内部队列。这样能提高短暂故障的容忍度,但也可能让请求持续堆积。
对低延迟 API,可以考虑:
const client = createClient({
disableOfflineQueue: true,
});
启用后,未连接时命令会更快失败。选择取决于业务:
- 可以短暂等待:保留队列。
- 必须快速失败并降级:禁用离线队列。
是否需要连接池
Redis 与传统关系数据库不同。Node-Redis 能在一个连接上高效复用普通命令,大多数应用不需要为了性能创建大量连接。
需要独占连接的典型场景:
- 阻塞命令,例如
BLPOP。 - RESP2 下的 Pub/Sub。
WATCH这类依赖连接状态的操作。- 长时间任务不能阻塞普通命令时。
这时才使用 duplicate() 或 createClientPool()。
键前缀
Node-Redis 支持 keyPrefix:
const client = createClient({
keyPrefix: "nloop:",
});
调用 set("user:1", "...") 实际会写入 nloop:user:1。
注意事项:
SCAN返回的键仍带前缀。- 将返回的键再次传给有前缀的客户端,可能发生重复前缀。
MATCH模式不会自动加前缀。- Pub/Sub 频道不属于键空间,不会加前缀。
初学阶段手动统一键名通常更直观。