连接配置与客户端生命周期

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

isOpenisReady

健康检查通常更关心“能否完成一次 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 能在一个连接上高效复用普通命令,大多数应用不需要为了性能创建大量连接。

需要独占连接的典型场景:

这时才使用 duplicate()createClientPool()

键前缀

Node-Redis 支持 keyPrefix

const client = createClient({
  keyPrefix: "nloop:",
});

调用 set("user:1", "...") 实际会写入 nloop:user:1

注意事项:

初学阶段手动统一键名通常更直观。