Redis连接失败如何排查?监听地址、认证与防火墙配置详解

应用一上线就开始刷 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.1systemctl status redis;ss -lntp | grep 6379启动进程;bind 指向内网地址或 0.0.0.0 后重启
Connection timed outiptables/ufw DROP;云安全组未放行nc -vz IP 6379;firewall-cmd --list-all放行应用服务器网段,同步检查安全组
DENIED ... protected modeprotected-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 reachedconnected_clients 达到 maxclientsredis-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 datasetRDB/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),节点间通信依赖它,这一点在防火墙规划时经常被漏掉。

八、标准排查清单

  1. 读懂错误信息:refused 指向进程/端口,timed out 指向防火墙,NOAUTH/WRONGPASS 指向认证,max number of clients 指向容量。
  2. 在服务端用 ss -lntp | grep 6379 确认进程确实在听目标地址和端口。
  3. 本机 redis-cli -h 127.0.0.1 PING 应返回 PONG,否则是服务本身没起来,看 journalctl 与 logfile。
  4. 远端 nc -vz ip 6379 不通,逐段排查 firewalld / iptables / ufw 与云厂商安全组。
  5. 检查 bind 与 protected-mode 的组合是否自相矛盾,特别是 bind 0.0.0.0 且没密码的情况。
  6. 核对客户端密码、ACL 用户名,注意特殊字符的 URL 编码和框架配置项改名。
  7. 检查 maxclients 与 connected_clients、rejected_connections,判断是否打满。
  8. 看 SLOWLOG 是否有一条阻塞命令打挂了整条命令队列。
  9. 用 CLIENT LIST 按 IP 统计连接来源,找到连接数异常的服务。
  10. 检查内核 somaxconn 与 Redis 的 tcp-backlog 是否匹配。
  11. 修复后用 CONFIG REWRITE 落盘,并在恢复文档中记录变更。

小结

Redis 连接失败的排查顺序建议固定为:先确认端口是否监听(服务层),再确认网络是否可达(防火墙与安全组),然后确认认证(requirepass / ACL),最后确认容量(maxclients、慢查询、backlog)。绝大多数线上问题集中在 bind 与 protected-mode 的组合没理顺、以及云安全组被忽略这两点上。避坑清单:不要使用 bind 0.0.0.0 且无密码的组合;不要把 Redis 端口暴露在公网;不要在生产使用 KEYS 命令或用 HGETALL 拉大集合;不要给 Redis 设置与 MySQL 相同的账号密码;修改 redis.conf 后一定要同时确认运行时配置已经生效。