错误处理、重连与安全退出
“能连上 Redis”只是开始,生产代码还需要处理断线、超时、重连和进程退出。
必须监听 error
client.on("error", (error: Error) => {
console.error("Redis client error", {
name: error.name,
message: error.message,
});
});
Node.js 的 EventEmitter 对未监听的 error 事件有特殊处理,可能导致进程退出。仅在 connect() 外层写 try/catch 并不能代替事件监听。
常用连接事件
connect:开始建立连接。ready:客户端可以执行命令。reconnecting:连接断开后正在重试。end:连接已经关闭。error:出现错误。
事件适合记录状态变化,不要在每次事件里执行复杂业务逻辑。
重连策略
官方默认策略采用有上限的指数退避,并加入随机抖动。自定义示例:
const client = createClient({
socket: {
reconnectStrategy(retries: number, cause: Error): number | false {
console.warn("Redis reconnect", { retries, message: cause.message });
if (retries >= 10) {
return false;
}
const delay: number = Math.min(2 ** retries * 50, 2_000);
const jitter: number = Math.floor(Math.random() * 200);
return delay + jitter;
},
},
});
为什么需要抖动?如果大量应用实例同时断线,完全相同的重连间隔会让它们同时冲击 Redis。
请求级错误处理
缓存失败不一定等于整个接口失败:
async function readOptionalCache(key: string): Promise<string | null> {
try {
return await client.get(key);
} catch (error: unknown) {
console.error("读取缓存失败", error);
return null;
}
}
但不要对所有 Redis 用法都吞错:
- 缓存读取失败:可能回源数据库。
- 限流器失败:要决定 fail-open 还是 fail-closed。
- 幂等标记失败:继续处理可能产生重复订单。
- 分布式锁失败:不能假装已经获得锁。
错误策略必须由数据角色决定。
超时
连接超时限制“多久建立连接”,命令超时限制“单条命令等待多久”。当前官方配置支持默认命令超时:
const client = createClient({
socket: {
connectTimeout: 5_000,
},
commandOptions: {
timeout: 2_000,
},
});
具体配置以项目安装版本的类型声明和官方文档为准。
优雅退出
let closing: boolean = false;
async function shutdown(signal: NodeJS.Signals): Promise<void> {
if (closing) {
return;
}
closing = true;
console.log(`收到 ${signal},准备退出`);
try {
if (client.isOpen) {
await client.close();
}
} catch (error: unknown) {
console.error("Redis 关闭失败", error);
client.destroy();
}
}
process.once("SIGINT", () => {
void shutdown("SIGINT");
});
process.once("SIGTERM", () => {
void shutdown("SIGTERM");
});
真实 Express 服务还应先停止接收新请求,再等待正在执行的请求结束,最后关闭 Redis、数据库等资源。
可观测性建议
至少记录或监控:
- 连接与重连次数。
- 命令错误率和超时率。
- 缓存命中率。
- Redis 请求延迟。
- 服务端内存和淘汰键数量。
- 阻塞客户端、连接数量和慢命令。
日志中不要输出 Redis 密码、完整连接 URL 或敏感缓存值。