ioredis — Node 里同时覆盖单机、Sentinel 和 Cluster 的 Redis 客户端
已复核ioredis 是一个面向 Node.js 的 Redis 客户端。日常类比:像总机接线员——你只报命令和参数,它负责连哪台机器、掉线怎么重拨、一批命令怎么打包,以及回复按哪种表格交到你手里。
import Redis from "ioredis";
const redis = new Redis(); // 默认 localhost:6379await redis.set("user:1", "Alice");const name = await redis.get("user:1");固定 6.0.0 同时覆盖 standalone、Sentinel 与 Cluster。构造后默认立刻 connect();只有 lazyConnect: true 才会等到第一条命令或显式 connect()。
不理解 ioredis 的连接合同,下面这些事都没法解释:
- 为什么 v6 默认走 RESP3 线路,但回复形状默认仍按 RESP2 扁平化
- 为什么掉线后未完成命令会进队列,却不会无限等下去
- 为什么同一套
set/get能指向单机、Sentinel 主从或 Cluster 分片 - 为什么 bullmq 可以把 ioredis 当成 Redis backend 的默认驱动,却不再把它写成硬依赖
固定源码把一次调用拆成五层:
- 解析连接目标:端口/主机、URL 或
sentinels决定用 StandaloneConnector 还是 SentinelConnector;Cluster 是独立类。 - 握手与协议:默认
protocol: 3。replyMapping默认为"legacy",Map 仍是扁平数组、double 仍是字符串;要原生 RESP3 形状必须显式replyMapping: "resp3"。 - 命令排队:连接未 ready 时,默认
enableOfflineQueue: true先把命令放进离线队列。 - 重连与重试上限:默认
retryStrategy是min(2^(times-1)*50, 5000)再加 0–199ms jitter;maxRetriesPerRequest默认 20,超过就抛MaxRetriesPerRequestError。 - 批量与脚本:
pipeline()把多条命令一次写出;multi()/exec()走事务;defineCommand绑定 Lua。himportFieldsets是实验接口,源码写明需要 Redis 8.10+。
案例 1:显式保留 v5 线路
Section titled “案例 1:显式保留 v5 线路”const redis = new Redis({ host: "127.0.0.1", port: 6379, protocol: 2});v6 breaking change 是“默认 RESP3”。旧代码如果依赖 RESP2 线路而不是只依赖 legacy 回复形状,应显式钉 protocol: 2。
案例 2:Cluster 读路由
Section titled “案例 2:Cluster 读路由”import { Cluster } from "ioredis";
const cluster = new Cluster( [{ host: "127.0.0.1", port: 7000 }], { scaleReads: "master", maxRedirections: 16 });await cluster.set("user:1", "Alice");默认 scaleReads 是 "master",maxRedirections 是 16。MOVED/ASK 会改写目标节点,但重定向次数有上限。
案例 3:Pipeline 与事务不是同一件事
Section titled “案例 3:Pipeline 与事务不是同一件事”const pipe = redis.pipeline();pipe.set("a", "1");pipe.incr("a");const replies = await pipe.exec();
const tx = redis.multi();tx.set("b", "1");tx.incr("b");await tx.exec();Pipeline 只保证一次写出多条命令;MULTI/EXEC 才是 Redis 事务边界。两者都不能代替服务端 Lua 的多键原子性。
- 把默认 RESP3 当成回复形状也变了:默认
replyMapping仍是"legacy"。要对象型 Map / 数值 double,必须显式"resp3";"resp3"配protocol: 2会在构造期抛错。 - 把无限等待当成默认:
maxRetriesPerRequest默认是 20,不是null。bullmq 的 Redis 连接路径会要求把它改成null。 - 以为
lazyConnect是默认:默认会立即建连。测试或短命令工具如果没处理早期error,会看到未监听的连接失败。 - 把 README 的“新项目推荐 node-redis”写成性能结论:那是上游维护立场,本轮没有跑对比。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 已经在用 ioredis 的 Node 服务,需要单机 / Sentinel / Cluster 同一套 API
- 需要离线队列、ready check、自定义 Lua 或 pipeline
- 给 bullmq Redis backend 提供 client 实例
不适用:
- Node < 20,或 Redis < 6.2——这是 v6 矩阵,不是建议值
- 只要标准 node-redis / Web 客户端,且团队准备跟上游推荐走
- 需要已验证的 hash-field expiration / Redis 8 新面,但不能只靠 README 推断当前实现覆盖
固定版本边界
Section titled “固定版本边界”- 本文绑定
redis/ioredis@8ed29465...,npm / tag /gitHead均为6.0.0。 engines.node为>=20.0.0。默认protocol: 3、replyMapping: "legacy"、maxRetriesPerRequest: 20。- 本文未安装依赖、连接 Redis、运行上游测试或测量吞吐,状态保持
UNVERIFIED。
- 线路协议和回复形状是两层合同——RESP3 默认不等于业务代码突然拿到 Map 对象。
- 重连策略必须带上限——jitter 只平滑重拨,
maxRetriesPerRequest决定命令何时失败。 - 连接拓扑是构造期选择——standalone / Sentinel / Cluster 不是运行时自动升级。
- 队列库会改写客户端默认——BullMQ 需要
maxRetriesPerRequest: null,不能直接套 ioredis 默认值。
new Redis()不改选项,HGETALL 在固定 6.0.0 默认会返回普通对象还是扁平数组?- 连接断开后第 21 次仍未恢复,默认会一直排队还是抛错?
new Redis({ protocol: 2, replyMapping: "resp3" })能建起来吗?
检查点:
- 扁平数组。默认
replyMapping是"legacy"。 - 抛
MaxRetriesPerRequestError;默认上限是 20。 - 不能。
"resp3"只允许和protocol: 3一起用。
- 固定源码:redis/ioredis —— 本文绑定提交
8ed2946504a36ae9b1e186b9dccc56afcd046d78 - 升级说明:Upgrading from v5 to v6
- 默认选项:lib/redis/RedisOptions.ts
- bullmq —— 默认 Redis backend 仍可走 ioredis,但 v6 把它降成 optional peer
- redis —— 服务端数据结构与 Lua,决定客户端能表达什么