问题背景
某在线教育平台在一个工作日上午 10:00(业务高峰时段)收到大量线上报警:
用户端:页面加载失败、提交作业超时
服务端:Java 应用频繁报出redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
Redis 监控:connected_clients达到上限maxclients=10000,rejected_connections开始出现
更严重的是,Redis 连接数打满后,连锁导致依赖 Redis 的认证服务、会话服务、缓存服务全部不可用,流量进一步转移到剩余的正常服务,引发了小范围的业务雪崩。
一、Redis 连接机制快速理解
1.1 Redis 如何处理连接
Redis 是单线程事件循环模型(6.0 之后网络 IO 可多线程,但核心处理仍是单线程)。每个客户端连接占用一个文件描述符(fd),Redis 通过epoll或kqueue处理事件。
关键配置项:
maxclients 10000 # 最大连接数(默认 10000,Redis 2.4+) timeout 0 # 连接空闲超时(0 表示永不超时) tcp-keepalive 300 # TCP keepalive 间隔
1.2 连接数打满的影响
当连接数达到maxclients后,Redis 不再接受新连接,并直接在日志中输出:
# Redis 9001 refused connection (maxclients) -ERR max number of clients reached
此时所有依赖 Redis 的服务都会受到影响:
新请求无法获取 Redis 连接 → 业务线程阻塞
阻塞线程不断重试 → 线程池打满 → Web 容器无可用线程
上游服务的健康检查失败 → 从注册中心摘除节点
流量转移到剩余节点 → 剩余节点的 Redis 连接也暴增 → 级联故障
二、排查过程
2.1 直接检查 Redis 连接状况
# 登录 Redis 查看连接数 $ redis-cli -h-p 6379 -a INFO clients # Clients connected_clients:10000 client_longest_output_list:0 client_biggest_input_buf:0 blocked_clients:0 cluster_connections:0 maxclients:10000
connected_clients=10000且maxclients=10000,连接数已打满。
2.2 查看连接分布
# 列出所有客户端连接 $ redis-cli CLIENT LIST id=12345 addr=10.0.1.12:54321 fd=23 name= age=12345 idle=456 flags=N ... id=12346 addr=10.0.1.13:54322 fd=24 name= age=12300 idle=12 flags=N ... ...
输出列含义:
addr:客户端 IP 和端口
age:连接存在时间(秒)
idle:连接空闲时间(秒)
flags:N 表示普通客户端,M 表示主从,S 表示从库
按 IP 统计连接数:
# 统计各客户端 IP 的连接数
$ redis-cli CLIENT LIST | awk'{print $2}'| cut -d= -f2 | cut -d: -f1 | sort | uniq -c | sort -rn | head -10
3000 10.0.1.12
2800 10.0.1.14
1500 10.0.1.13
1200 10.0.1.15
...
2.3 查找僵尸连接
# 查找空闲时间大于 300 秒的连接
$ redis-cli CLIENT LIST | awk -F'[ =]''{for(i=1;i<=NF;i++) if($i=="idle") print $(i+1), $0}' | awk '$1 > 300'| head -20
# 更简洁的方式
$ redis-cli CLIENT LIST | grep -E"idle=[0-9]{4,}"
发现大量连接的idle值在 600~3600 秒之间,说明这些连接长时间空闲但没有被回收。timeout配置为 0(永不超时)。
注:Redis 7.0+ 引入CLIENT NO-TOUCH和CLIENT NO-EVICT指令,但timeout仍是控制僵尸连接的主要参数。
2.4 确认系统级限制
Redis 的maxclients受系统文件描述符限制和内核参数限制:
# Redis 进程的 fd 限制 $ cat /proc/$(pidof redis-server)/limits | grep"Max open files" Max open files 10024 10024 files # 系统级 fd 限制 $ cat /proc/sys/fs/file-max 100000 # 当前 fd 使用量 $ cat /proc/sys/fs/file-nr 30000 0 100000
Redis 进程的Max open files为 10024,与 maxclients 10000 非常接近(Redis 本身还需要占用少量 fd 用于监听端口、AOF 写入等)。
2.5 应用层排查
检查应用侧的 Redis 连接池配置:
3000 个应用实例(部署了 15 个节点 × 每个节点 maxTotal=200) × 连接复用不足 → 实际连接数远超预期。
三、根因分析
经过以上排查,确定本次事故由三个因素叠加导致:
3.1 直接原因:应用发布后连接数暴增
当天凌晨发布新版本后,应用启动时连接池的minIdle=10导致每个节点预先建立 10 条连接。发布方式是滚动更新,旧节点和新节点短暂共存,连接数翻倍。部分旧节点上的连接在优雅关闭时未正确释放,变成僵尸连接。
3.2 放大因素:timeout = 0
Redis 配置timeout=0(永不主动断开空闲连接)。大量僵尸连接(idle > 600s)未被回收,持续占用连接槽位。等到上午业务高峰到来时,新连接请求直接打到上限。
3.3 雪崩链路
Redis 连接满(maxclients=10000) → 新请求无法获取连接 → JedisConnectionException → 应用层无熔断降级 → 线程被重试阻塞 → Web 容器线程池打满 → 健康检查接口超时 → 注册中心摘除节点 → 流量转移到剩余节点 → 剩余节点 Redis 连接数飙升 → 也打满 → 服务全面不可用
四、快速止血方案
4.1 方案 A:临时增大 maxclients(推荐首选)
# 临时修改(重启后失效) $ redis-cli CONFIG SET maxclients 20000
注意:增大 maxclients 的同时必须增大系统的 fd 限制:
# 临时修改 Redis 进程的 fd 限制 $ prlimit --pid $(pidof redis-server) --nofile=30000 # 或修改系统级限制 $ulimit-n 65535
永久修改/etc/security/limits.conf:
# 添加以下行 root soft nofile 65535 root hard nofile 65535 redis soft nofile 65535 redis hard nofile 65535
Redis 配置文件中的maxclients也要同步修改:
# /etc/redis/redis.conf maxclients 20000
4.2 方案 B:启用 timeout 自动清理僵尸连接
# 设置空闲超时 60 秒(立即生效) $ redis-cli CONFIG SET timeout 60
60 秒后,所有空闲超过 60 秒的连接会被 Redis 自动关闭。这会触发以下效果:
# 观察连接数变化 $ watch -n 5'redis-cli INFO clients | grep connected_clients'
timeout值应根据业务心跳间隔设置。一般建议:
Web 应用 + 连接池:timeout=60~300
长连接订阅/推送:timeout=600~3600
仅做缓存:timeout=60
4.3 方案 C:批量清理异常连接
如果上述方案来不及等待,直接按条件 kill 连接:
# 按类型 kill(kill 所有普通客户端连接,保留主从复制连接) $ redis-cli CLIENT KILL TYPE normal # 按 IP 段 kill(如果某台应用服务器连接异常) $ redis-cli CLIENT KILL addr 10.0.1.12:0 # 跳过 skipme(是否 kill 当前连接,默认 yes) $ redis-cli CLIENT KILL addr 10.0.1.12:0 skipme no
4.4 方案 D:重启应用(让连接池重建)
作为兜底方案,重启应用让连接池重新初始化:
# 重启应用服务(确保是滚动重启) $ systemctl restart app-service
重启后连接数会在短时间内回归正常水平(minIdle 重新建立)。
五、长期治理方案
5.1 连接池最佳实践
生产环境合理的连接池配置:
配置原则:
maxTotal 不要过大:单个应用节点对单个 Redis 实例的 maxTotal 建议 20~100,视并发度而定。连接数不是越多越好,Redis 处理 10000 个空闲连接和 500 个活跃连接的开销完全不同。
设置 maxWaitMillis:避免线程无限等待连接,建议 1000~3000ms。
开启 testWhileIdle:定时检测空闲连接是否可用,避免连接被 Redis 侧关闭后应用仍在使用。
代码中正确归还连接:使用 try-with-resources(Jedis 3.x+ 支持)或 finally 块确保 close。
// Jedis 3.x 推荐用法
try(Jedis jedis = jedisPool.getResource()) {
jedis.set("key","value");
}// 自动归还连接,无需显式 close
// Jedis 2.x 必须显式 close
Jedis jedis =null;
try{
jedis = jedisPool.getResource();
jedis.set("key","value");
}finally{
if(jedis !=null) {
jedis.close(); // 归还到连接池而非真正关闭
}
}
5.2 使用连接代理收敛架构
如果有大量客户端直连 Redis,考虑引入连接代理层:
[应用实例] x 50个 → [Twemproxy / Predixy] → [Redis 主从]
优势:
连接数从 M × N 降为 M + N(M 是应用节点,N 是 Redis 节点)
代理层可以缓冲突发连接请求
支持读写分离和故障转移
缺点:
增加一层网络跳转,延迟增加 0.1~0.5ms
代理本身可能成为瓶颈
5.3 连接限额和防火墙防护
# 限制单台应用服务器的 Redis 连接数(iptables) $ iptables -A INPUT -p tcp --dport 6379 -m connlimit --connlimit-above 50 -j REJECT
5.4 熔断降级和重连退避
应用侧必须实现熔断机制:
// 使用 Resilience4j CircuitBreaker
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50% 失败率触发熔断
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔断后等 30 秒
.slidingWindowSize(10)
.build();
// 或简单的退避策略
intbaseDelay =100; // 基础等待 100ms
for(inti =0; i < maxRetries; i++) {
try {
return jedisPool.getResource();
} catch (Exception e) {
Thread.sleep(baseDelay * (long)Math.pow(2, i)); // 指数退避
}
}
5.5 监控和告警
必须监控的 Redis 连接指标:
# Prometheus redis_exporter 已暴露的关键指标 redis_connected_clients redis_config_maxclients redis_rejected_connections_total
告警阈值建议:
| 指标 | 告警阈值 | 严重级别 |
|---|---|---|
| connected_clients/maxclients | > 80% | Warning |
| connected_clients/maxclients | > 90% | Critical |
| rejected_connections > 0 | 立即 | P0 Emergency |
六、生产环境注意事项
CONFIG SET maxclients需要联动调整多个参数:
缺了任何一个,maxclients设置不生效。
Redis 配置maxclients
系统的ulimit -n
内核的fs.file-max
/etc/security/limits.conf
不要在高峰期修改 timeout:如果timeout从 0 改为一个很小的值(如 30),会瞬间断开大量连接,导致客户端连接池出现批量重建连接的场景,可能触发 CPU 暴涨和网络抖动。
CLIENT KILL 需要注意 skipme:如果当前连接也在 kill 范围内,会导致当前命令执行中断。默认 skipme=yes 不 kill 当前连接。
云 Redis 服务的 maxclients 限制:各云厂商 Redis 实例的maxclients可能与实例规格绑定(如 2GB 实例 = maxclients 10000),不能无限上调。提工单前先确认产品文档。
连接池泄漏排查:如果频繁出现连接打满但CLIENT LIST看不出来异常,检查应用代码中是否将 Jedis 实例作为成员变量而非局部变量使用,或者异常分支未执行 close。
七、总结
Redis 连接数打满导致业务雪崩的排查和治理可以分为三层:
第一层:快速止血
CONFIG SET maxclients 20000 # 增大上限 CONFIG SET timeout 60 # 清理僵尸连接 CLIENT KILL TYPE normal # 批量杀异常连接
第二层:排查根因
检查连接分布 → 分析空闲时间 → 核对连接池配置 → 确认系统限制
第三层:架构治理
连接池规范化 → 熔断降级 → 代理收敛 → 监控告警 → 应急预案
本次故障的核心教训是:
timeout=0在生产环境是高风险配置,必须设置合理的空闲超时
连接池的 minIdle/maxTotal 要根据实际并发度计算,而非随意配置
服务必须有熔断降级机制,否则单一组件故障会级联扩散
发布过程中要监控连接数变化,发现异常立即回滚或调整
-
网络
+关注
关注
14文章
8341浏览量
95619 -
线程
+关注
关注
0文章
511浏览量
20878 -
Redis
+关注
关注
0文章
395浏览量
12262
原文标题:一次 Redis 连接数打满导致业务雪崩的排查记录
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
一次Redis连接数打满导致业务雪崩的排查记录
评论