Redis 客户端连接与故障处理
本页面向需要把 Redis 接入生产应用的后端开发人员,说明如何选择客户端能力、设置连接生命周期,并处理超时、故障转移和集群重定向。内容以 Redis 7.4.11 为基准;客户端库的配置名称不同,但需要解决的问题相同。
连接管理的目标不是让连接永远不断,而是在故障发生时有界地等待、准确地判断结果,并在能力允许时恢复。权威来源见Redis 客户端处理说明、Sentinel 客户端规范和 Redis Cluster 规范。
1. 确定连接拓扑
客户端必须知道应用连接的是单实例、Sentinel 还是 Cluster。三者对地址发现、读写位置和重连行为的要求不同。
| 拓扑 | 客户端必须具备的能力 | 故障后的关键动作 |
|---|---|---|
| 单实例 | 连接地址、超时、连接池、健康检查 | 有界重连;超过错误预算后快速失败 |
| Sentinel | 使用 Sentinel 地址和服务名发现主节点 | 重新查询主节点,验证角色,不复用旧主地址 |
| Cluster | 维护槽到节点的映射,处理重定向和拓扑刷新 | 跟随 MOVED,按规范处理 ASK,刷新槽映射 |
不要把服务器 IP 写死后用于 Sentinel 服务。Sentinel 规范要求客户端在每次重连时重新解析服务地址,并通过 ROLE 等命令确认目标确实是主节点。Cluster 客户端收到 MOVED 后应向指定节点重发查询;槽迁移期间收到 ASK 时,应按客户端协议完成 ASKING 后再发送原命令。
多 Key 命令还要先检查拓扑约束:
- 单实例和 Sentinel 不限制不同 Key 的槽位置。
- Cluster 中的多 Key 操作必须位于同一哈希槽;相关 Key 使用一致的哈希标签。
- 读副本时要接受复制延迟,写请求必须由客户端或代理路由到可写主节点。
2. 选择客户端能力
选型时先验证故障行为,再比较 API 是否顺手。至少检查以下能力:
- 支持目标部署形态,尤其是 Sentinel 服务发现或 Cluster 槽路由。
- 能分别设置连接、读取和写入超时,而不是只有一个模糊的总超时。
- 连接池具有最大连接数、空闲连接和等待队列上限,能够暴露等待和拒绝指标。
- 重试次数有上限,退避带随机抖动,并能限制一段时间内的总重试预算。
- 能刷新拓扑,不把一次连接失败或
MOVED当作永久地址变化。 - 提供连接数、池等待时间、命令延迟、错误类型和拓扑刷新事件。
- 支持应用使用的 RESP 版本、认证、TLS 和所需命令。
RESP 3 是 Redis 6 引入的协议版本,能返回更丰富的回复类型;RESP 2 仍是许多客户端和部署的默认兼容路径。选择协议前应确认服务端、代理和客户端三者一致,不要仅根据协议版本判断客户端优劣。
客户端选型应通过一次重启、一次 Sentinel 故障转移和一次 Cluster 槽迁移演练验证。只用 PING 成功作为验收条件,会漏掉最重要的拓扑刷新和重连问题。
3. 设置连接生命周期
不同客户端对配置的命名不同,但都应覆盖以下生命周期。
| 配置 | 作用 | 配置过大的风险 | 配置过小的风险 |
|---|---|---|---|
| 连接超时 | 限制建立 TCP 连接的等待时间 | 故障时请求积压 | 网络抖动时误判成功 |
| 读取超时 | 限制等待响应的时间 | 故障恢复变慢,线程或协程被占用 | 高峰期把慢请求当作失败 |
| 连接池上限 | 限制同时持有的连接 | 打满服务端 maxclients 和文件描述符 | 池等待时间成为主要延迟 |
| 最大空闲时间 | 回收长期不用的连接 | 保留过多无用连接 | 频繁建连和 TLS 握手 |
| 重试上限 | 限制单次操作的额外尝试 | 故障被放大,恢复变慢 | 短暂抖动直接暴露给调用方 |
服务端 timeout 只会在空闲达到指定秒数后断开客户端连接,不能代替客户端读取超时。TCP keepalive 用于探测连接是否仍然存在,也不能保证应用已经收到响应。
连接池大小应从应用并发和池等待指标推导,不直接套用“每 100 QPS 一个连接”之类的固定公式。所有客户端的池上限、监控连接和管理连接之和,应低于服务端 maxclients,并为突发流量保留余量:
Σ(应用连接池上限) + 监控/管理连接 + 协议开销 < maxclients × 安全水位还应检查进程和操作系统的文件描述符上限。服务端在无法接受新连接时会返回连接数超限错误并立即关闭连接;此时增加客户端重试只会提高服务端压力。
4. 限制重试和请求放大
重试前要区分“请求肯定没有执行”和“结果未知”。Redis 返回超时只说明客户端没有及时收到响应,不表示服务器没有执行命令。
| 结果 | 是否可直接重试 | 原因与处理 |
|---|---|---|
| 尚未发送请求 | 通常可以 | 按退避策略重连并重新发送 |
| 已发送,响应未知 | 需要业务判断 | INCR、LPUSH、XADD 等命令可能已经成功 |
NOAUTH、WRONGPASS、NOPERM | 不应重试 | 先修复认证或 ACL 配置 |
CROSSSLOT | 不应重试 | 修改 Key 分组或哈希标签 |
MOVED、ASK | 由集群客户端处理 | 更新路由后按协议重发,不手工无限循环 |
READONLY | 重新发现拓扑 | 写请求此前被发到了只读副本 |
LOADING | 有界重试 | 仅在业务允许的恢复窗口内退避等待 |
OOM command not allowed | 默认不重试 | 先确认容量、淘汰策略和业务降级 |
对结果未知且不可重复的写入,应由业务使用幂等键、结果核对或补偿流程处理。客户端无法替应用判断一次 INCR 到底执行了零次、一次还是两次。
重试采用指数退避并加入随机抖动,同时满足三个边界:
- 单次操作的总等待时间不超过业务超时。
- 单位时间内的总重试量不超过故障预算。
- 连续失败达到阈值后打开熔断,让调用方快速失败或走降级路径。
连接池耗尽时,等待本身也是延迟。池等待应有独立上限;不能在应用线程中无限等待一个永远不会恢复的 Redis。
5. 区分常见连接故障
先按错误类型分类,再决定重试、修复配置或切换拓扑。
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| 连接被拒绝 | 服务状态、监听地址、防火墙 | 修复网络或路由,不提高重试次数 |
| 连接建立后很快断开 | maxclients、空闲超时、代理空闲策略 | 调整连接预算和空闲参数 |
| 认证失败 | ACL 用户名、密码注入、默认用户状态 | 轮换错误凭据,确认实际登录身份 |
| 权限不足 | ACL Key 模式和命令规则 | 按最小权限补规则,不直接授予全部命令 |
写请求返回 READONLY | Sentinel/Cluster 拓扑缓存 | 重新发现可写节点并刷新连接 |
| 延迟和池等待同时升高 | 阻塞命令、大 Key、池容量 | 查看慢日志和连接池指标 |
| 集群持续重定向 | 槽映射、网络地址公布、代理配置 | 检查 CLUSTER NODES 和客户端拓扑刷新 |
具体错误见常见错误与连接问题。排障时同时观察应用侧指标和服务端的 INFO clients、CLIENT LIST、SLOWLOG,不要只看进程是否存活。
6. 观察连接状态
应用侧至少记录:
- 连接成功、失败和当前活跃连接数。
- 连接池最大连接数、空闲连接数、等待任务数和等待时间。
- 连接、读取、重试和拓扑刷新各自的耗时与失败次数。
- 按错误类型区分的结果,不把所有失败合并成一个“Redis 异常”。
- 故障转移前后旧连接的释放速度和新拓扑的生效时间。
服务端可从 INFO clients 查看 connected_clients、blocked_clients 和 maxclients。CLIENT LIST 用于查看单个连接的地址、空闲时间和发送请求数;连接很多时应限制输出规模,并避免把包含应用名称或地址的日志长期留存到不受控位置。
连接相关指标的告警不应只针对连接总数。更常见的故障信号是池等待时间上升、重试率上升、拓扑刷新失败,以及连接数达到上限前的持续增长。
7. 验证连接方案
上线前至少完成以下测试:
- 拒绝一个错误地址,确认连接超时受控,调用方没有无上限等待。
- 重启 Redis,确认连接池清理失效连接,新连接能够恢复。
- 执行 Sentinel 故障转移,确认客户端重新发现主节点,不继续向旧地址写入。
- 执行 Cluster 槽迁移,确认
MOVED和ASK被正确处理,应用无持续重定向循环。 - 切换 ACL 凭据,确认旧连接失效后按计划重建,新凭据不写入日志。
- 制造高并发池等待,确认等待超时、降级和告警符合预期。
验收记录应包含业务超时、池上限、重试预算、故障恢复时间,以及每项测试的预期结果。没有经过故障演练的默认配置,只能证明连接成功,不能证明故障可控。
8. 参考资料
- Redis 官方:Client handling
- Redis 官方:Sentinel client specification
- Redis 官方:Redis Cluster specification