应用一上线就开始刷 Connection refused;配好了 requirepass 之后变成 NOAUTH Authentication required;好不容易连上了高峰期又报 ERR max number of clients reached。Redis 的连接问题之所以频繁,是因为它的默认配置是"面向本机安全"设计的:bind 只绑回环地址、protected-mode 默认开启、无密码。任何远程接入都要同时动三个地方。本文按"网络层—服务层—认证层—容量层"四层给出一套完整的排查方法论和可复制命令。
一、先读懂错误信息:它已经告诉你大概在哪一层
| 错误信息 | 出错层级 | 通常原因 |
|---|---|---|
| Connection refused | 网络 / 进程 | 服务没启动、端口不对、只监听了 127.0.0.1 |
| Connection timed out | 网络 | 防火墙 DROP、云安全组未放行、跨机房 ACL |
| DENIED Redis is running in protected mode | 服务配置 | protected-mode 开启且既没 bind 也没设密码 |
| NOAUTH Authentication required | 认证 | 服务端设了密码而客户端没带,或带了但顺序不对 |
| WRONGPASS invalid username-password pair | 认证 | 密码错误;或 Redis 6 的 ACL 用户名不对 |
| ERR max number of clients reached | 容量 | 连接数达到 maxclients 上限(默认 10000) |
| Connection reset by peer | 容量 / 内核 | 被服务端主动断开:超时、输出缓冲区超限、tcp-backlog 溢出 |
| LOADING Redis is loading the dataset in memory | 服务状态 | RDB 正在载入,数据量大时会持续数十秒 |
区分 refused 与 timed out 很关键:refused 意味着 TCP 握手收到了 RST,说明对端可达但端口没人听;timed out 意味着包发出去没人回,通常是中间被防火墙静默丢包。
二、网络层排查:三步确认端口通不通
2.1 先在服务端确认监听
# Redis 是否存活 systemctl status redis ps aux | grep redis-server | grep -v grep # 端口与监听地址(重点看第一列) ss -lntp | grep 6379 netstat -lntp | grep 6379 # 期望:LISTEN 0 511 0.0.0.0:6379 或 10.0.0.5:6379 # 如果只有 127.0.0.1:6379,就是 bind 配置问题,见第三节
2.2 再从客户端确认连通性
# 本机自测(必过) redis-cli -h 127.0.0.1 -p 6379 PING # 远端自测 redis-cli -h 10.0.0.5 -p 6379 PING # telnet/nc 只做 TCP 层验证,用于区分是网络问题还是 Redis 层问题 nc -vz 10.0.0.5 6379 timeout 3 bash -c 'cat < /dev/null > /dev/tcp/10.0.0.5/6379' && echo "端口可达" # 手工打招呼(Redis 协议很简单):-ERR unknown command 也说明端口通 printf 'PING\r\n' | nc 10.0.0.5 6379
2.3 检查防火墙与安全组
# firewalld sudo firewall-cmd --state sudo firewall-cmd --list-all sudo firewall-cmd --list-ports sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" port protocol="tcp" port="6379" accept' sudo firewall-cmd --reload # iptables(老系统) sudo iptables -L INPUT -n --line-numbers | grep 6379 # ufw(Debian 系) sudo ufw allow proto tcp from 10.0.0.0/24 to any port 6379
云主机的场景里,安全组是第二道闸门:即使操作系统里 firewalld 全部放开,云厂商安全组没放行,外部一样连不上。这一条在阿里云 / 腾讯云 / AWS 上都适用,务必核对。
三、服务层:bind、protected-mode 与 port
Redis 配置文件的监听相关三项:
# redis.conf(常见路径:/etc/redis/redis.conf 或 /etc/redis.conf) bind 0.0.0.0 # 生产建议:bind 127.0.0.1 10.0.0.5,只暴露内网网卡;Redis 6/7 默认为 127.0.0.1 -::1 protected-mode no # 默认 yes;配了密码且 bind 到具体地址后可设为 no port 6379 tcp-backlog 511 # 默认 511,高并发短连接场景可调大 timeout 0 # 0 表示永不主动断开空闲连接 tcp-keepalive 300 # 默认 300 秒,用于清理僵死连接
关于 protected-mode 的逻辑要记清楚:当 protected-mode 为 yes 且(没有 bind 指定地址)且(没有设置 requirepass)时,Redis 只允许来自回环地址的连接,并对其他来源返回 DENIED Redis is running in protected mode。所以正确的组合是二选一:
- 保守组合:
bind 10.0.0.5+requirepass xxx+protected-mode yes(三者都有,最安全)。 - 简化组合:
bind 0.0.0.0+requirepass xxx+protected-mode no(仅在内网可信环境下使用)。
绝对不要采用 bind 0.0.0.0 + 无密码 + protected-mode no,这是 Redis 未授权访问漏洞的经典成因,可被直接写入 crontab 或 SSH 公钥。
修改后记得让配置生效:
# 查看运行中的参数(不用重启就能确认) redis-cli CONFIG GET bind redis-cli CONFIG GET protected-mode redis-cli CONFIG GET requirepass redis-cli CONFIG GET maxclients # 部分参数支持热修改;bind 与 port 不支持热修改,需重启 redis-cli CONFIG SET protected-mode no redis-cli CONFIG REWRITE # 把运行时的改动写回 redis.conf sudo systemctl restart redis
3.1 protected-mode 与 bind 的联动逻辑
这两个参数经常被单独理解,其实它们是一套组合条件。protected-mode(默认 yes)的判定规则是:当它为 yes、并且既没有配置 bind 指定地址、也没有设置 requirepass 时,Redis 只接受来自 IPv4/IPv6 回环地址的连接,对其他来源一律返回 DENIED Redis is running in protected mode。换句话说,只要满足下面任意一个条件,外部连接就有机会通过:
- 用
bind显式指定了具体监听地址(如内网网卡地址),说明管理员"有意开放"; - 设置了
requirepass(或配置了 ACL 用户),说明接入需要口令。
因此推荐的两种自洽组合是:bind 10.0.0.5 + requirepass + protected-mode yes(最稳妥),或者 bind 0.0.0.0 + requirepass + protected-mode no(仅限可信内网)。而 bind 0.0.0.0 + 无密码 + protected-mode no 就是典型的 Redis 未授权访问漏洞成因:攻击者可以直接写入 crontab、SSH authorized_keys 或落马 Redis 模块。
另外要注意 bind 与 port 属于必须重启才生效的参数,CONFIG SET 会返回 OK 但并不切换监听;而 protected-mode、requirepass、maxclients 支持热修改。
四、按错误类型分诊:成因、验证命令与处置
把错误文案和对应的验证动作做成对照表,排障时按顺序执行即可。
| 错误信息 | 首要成因 | 验证命令 | 处置 |
|---|---|---|---|
| Connection refused | 进程未运行;或只监听 127.0.0.1 | systemctl status redis;ss -lntp | grep 6379 | 启动进程;bind 指向内网地址或 0.0.0.0 后重启 |
| Connection timed out | iptables/ufw DROP;云安全组未放行 | nc -vz IP 6379;firewall-cmd --list-all | 放行应用服务器网段,同步检查安全组 |
| DENIED ... protected mode | protected-mode=yes 且无 bind 无密码 | redis-cli CONFIG GET protected-mode;CONFIG GET bind | 按第 3.1 节组合修正,不要简单关 protected-mode |
| NOAUTH Authentication required | 服务端有 requirepass,客户端没带 | redis-cli CONFIG GET requirepass | 客户端补 -a / --pass;注意 @ # 需 URL 编码 |
| WRONGPASS invalid username-password pair | 密码错;或 Redis 6 ACL 用户名错 | redis-cli ACL LIST;ACL WHOAMI | 核对 ACL 用户与 >口令;必要时 ACL SETUSER |
| ERR max number of clients reached | connected_clients 达到 maxclients | redis-cli INFO clients | 修连接池泄漏;调大 maxclients;必要时重启并清理 |
| Connection reset by peer | 输出缓冲区超限;tcp-backlog/somaxconn 溢出 | redis-cli INFO clients;cat /proc/sys/net/core/somaxconn | 限制大结果集;调大 somaxconn 与 tcp-backlog |
| LOADING Redis is loading the dataset | RDB/AOF 正在载入 | redis-cli INFO persistence | 等待载入完成;大实例考虑减小数据集或提高淘汰 |
| MISCONF ... RDB snapshots failed | 持久化失败时拒绝写入(stop-writes-on-bgsave-error yes) | redis-cli CONFIG GET dir;tail /var/log/redis/*.log | 修磁盘权限与容量;确认后再恢复写入 |
最后一条值得单独说明:stop-writes-on-bgsave-error 默认是 yes,当 BGSAVE 因为磁盘满或权限错误失败时,Redis 会拒绝所有写命令并返回 MISCONF。这不是"连接失败",但业务侧表现同样是写入全线报错,很容易被误判为网络问题。检查 dir 目录的权限与剩余空间即可定位。
五、认证层:requirepass 与 Redis 6 的 ACL
Redis 6.0 之前只有单一密码:requirepass。6.0 之后引入了 ACL,可以同时存在多个用户并细粒度控制命令和 key。
# redis.conf:传统方式(对所有连接生效) requirepass Str0ng_P@ssw0rd # Redis 6+ 推荐:ACL 方式 user default off # 禁用默认用户的无限制访问,可选 user appuser on >App_P@ssw0rd ~cache:* +get +set +del +expire user readonly on >Rd_P@ssw0rd ~* -@write +@read
客户端连接时的对应写法:
# 传统密码(--no-auth-warning 可以去掉命令行里明文密码的告警) redis-cli -h 10.0.0.5 -p 6379 -a 'Str0ng_P@ssw0rd' --no-auth-warning PING # ACL 方式 redis-cli -h 10.0.0.5 -p 6379 --user appuser --pass 'App_P@ssw0rd' PING # 连接串格式 redis-cli -u redis://appuser:App_P@ssw0rd@10.0.0.5:6379/0 PING # 运行时管理 ACL redis-cli ACL LIST redis-cli ACL WHOAMI redis-cli ACL GETUSER appuser redis-cli ACL SETUSER cacheuser on >pwd ~cache:* +@all
应用层客户端(Jedis / Lettuce / go-redis / phpredis)也要注意:Spring Boot 的配置项在 2.x 之后从 spring.redis.password 变成了 spring.data.redis.password,版本升级后忘了改会静默连不上。另外密码中包含 @、#、$ 这类特殊字符时,写进连接串必须做 URL 编码,否则会被解析成 host 的一部分。
六、容量层:maxclients 与客户端管理
当报错是 ERR max number of clients reached 时,说明连接打满了。maxclients 默认值为 10000(32 位系统上受可用地址空间限制会更小)。
redis-cli INFO clients
# connected_clients 已建立连接数
# blocked_clients 被 BLPOP/BRPOP 等阻塞的连接数
# maxclients 上限
# total_connections_received 历史累计连接数
# rejected_connections 被拒绝过的连接次数(大于 0 说明曾经打满)
redis-cli CLIENT LIST
redis-cli CLIENT LIST | awk '{print $2}' | sort | uniq -c | sort -nr | head # 按来源 IP 统计
# 清理空闲超过 3600 秒的连接
redis-cli CLIENT KILL TYPE normal
redis-cli CLIENT KILL ADDR 10.0.0.8:52344
# 给客户端打名字,便于识别
redis-cli CLIENT SETNAME order-service-1
如果 connected_clients 居高不下但业务 QPS 很低,通常是客户端没正确使用连接池。要注意:Lettuce 默认共享单个连接做多路复用,而 Jedis 默认不是线程安全的、必须配连接池;混用会造出大量伪连接。此外 BLPOP 类阻塞命令会长期占用连接,务必设置合理的 block 超时。
还有一类隐蔽原因:慢查询阻塞。Redis 是单线程处理命令的,一条执行 500 毫秒的命令会让后面排队的连接全部超时。用慢日志确认:
redis-cli CONFIG GET slowlog-log-slower-than # 默认 10000 微秒(10 毫秒) redis-cli CONFIG SET slowlog-log-slower-than 5000 redis-cli SLOWLOG GET 10 redis-cli SLOWLOG LEN redis-cli SLOWLOG RESET # 典型的慢命令,生产应禁用或用 SCAN 替代 KEYS user:* # O(N) 全量遍历 SMEMBERS huge_set # 大集合全量返回 HGETALL huge_hash # 大哈希全量返回 LRANGE big_list 0 -1 # 大列表全量返回
另外还有一个操作系统层面的坑:高并发短连接时 tcp-backlog 或内核的 somaxconn 太小会导致握手被丢弃,表现为偶发 Connection reset by peer:
cat /proc/sys/net/core/somaxconn sudo sysctl -w net.core.somaxconn=65535 echo 'net.core.somaxconn=65535' | sudo tee -a /etc/sysctl.conf
七、客户端侧:连接池与超时配置
服务端没问题但连接依旧不稳定,多半是客户端配置不当。几个高频坑:
- 没有用连接池:每次请求新建一条 TCP 连接,既慢又会在流量高峰把 maxclients 撑满。Jedis 必须用 JedisPool,Lettuce 默认是多线程共享连接的,不需要也不应用错了。
- 没设合理的超时:
connectTimeout建议 1-3 秒(内网 1 秒足够),soTimeout/ 读超时按业务 P99 设定,一般 200 毫秒到 2 秒。完全不设超时的话,一次网络抖动就会把应用线程池全部挂在 waiting 状态,形成雪崩。 - 连接泄漏:拿到连接后异常分支没有归还,几小时内就会耗尽池上限。务必在 finally 或 try-with-resources 中释放,并开启连接池的泄漏检测(如 Druid 的
removeAbandoned、HikariCP 的leakDetectionThreshold)。 - 连接回了错误的数据库:Redis 支持 16 个逻辑库(
databases 16,默认下标 0),客户端如果配了不同的 db 号,会"连上了却读不到数据",常被误判为连接故障。
# 客户端连接参数核对清单 redis-cli CONFIG GET databases redis-cli -n 1 PING # 指定 db 1 测试 redis-cli CONFIG GET timeout # 服务端空闲断开时间,0 表示不断开 redis-cli INFO stats | grep -E 'total_connections_received|rejected_connections|sync_full|sync_partial_ok'
Sentinel 与 Cluster 场景下还有额外的连接前提:Sentinel 客户端要连的是 Sentinel 的地址列表而不是具体的 Redis 实例;Cluster 客户端必须能连通集群中所有节点的端口,如果只开放了其中一个,会在 MOVED 重定向之后报Connection refused。集群总线端口为节点端口加 10000(如 6379 对应 16379),节点间通信依赖它,这一点在防火墙规划时经常被漏掉。
八、标准排查清单
- 读懂错误信息:refused 指向进程/端口,timed out 指向防火墙,NOAUTH/WRONGPASS 指向认证,max number of clients 指向容量。
- 在服务端用
ss -lntp | grep 6379确认进程确实在听目标地址和端口。 - 本机
redis-cli -h 127.0.0.1 PING应返回 PONG,否则是服务本身没起来,看 journalctl 与 logfile。 - 远端
nc -vz ip 6379不通,逐段排查 firewalld / iptables / ufw 与云厂商安全组。 - 检查 bind 与 protected-mode 的组合是否自相矛盾,特别是 bind 0.0.0.0 且没密码的情况。
- 核对客户端密码、ACL 用户名,注意特殊字符的 URL 编码和框架配置项改名。
- 检查 maxclients 与 connected_clients、rejected_connections,判断是否打满。
- 看 SLOWLOG 是否有一条阻塞命令打挂了整条命令队列。
- 用 CLIENT LIST 按 IP 统计连接来源,找到连接数异常的服务。
- 检查内核 somaxconn 与 Redis 的 tcp-backlog 是否匹配。
- 修复后用
CONFIG REWRITE落盘,并在恢复文档中记录变更。
小结
Redis 连接失败的排查顺序建议固定为:先确认端口是否监听(服务层),再确认网络是否可达(防火墙与安全组),然后确认认证(requirepass / ACL),最后确认容量(maxclients、慢查询、backlog)。绝大多数线上问题集中在 bind 与 protected-mode 的组合没理顺、以及云安全组被忽略这两点上。避坑清单:不要使用 bind 0.0.0.0 且无密码的组合;不要把 Redis 端口暴露在公网;不要在生产使用 KEYS 命令或用 HGETALL 拉大集合;不要给 Redis 设置与 MySQL 相同的账号密码;修改 redis.conf 后一定要同时确认运行时配置已经生效。
企业QQ咨询




