問題背景
某在線教育平臺在一個工作日上午 10:00(業(yè)務(wù)高峰時段)收到大量線上報警:
用戶端:頁面加載失敗、提交作業(yè)超時
服務(wù)端:Java 應(yīng)用頻繁報出redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
Redis 監(jiān)控:connected_clients達(dá)到上限maxclients=10000,rejected_connections開始出現(xiàn)
更嚴(yán)重的是,Redis 連接數(shù)打滿后,連鎖導(dǎo)致依賴 Redis 的認(rèn)證服務(wù)、會話服務(wù)、緩存服務(wù)全部不可用,流量進(jìn)一步轉(zhuǎn)移到剩余的正常服務(wù),引發(fā)了小范圍的業(yè)務(wù)雪崩。
一、Redis 連接機(jī)制快速理解
1.1 Redis 如何處理連接
Redis 是單線程事件循環(huán)模型(6.0 之后網(wǎng)絡(luò) IO 可多線程,但核心處理仍是單線程)。每個客戶端連接占用一個文件描述符(fd),Redis 通過epoll或kqueue處理事件。
關(guān)鍵配置項:
maxclients 10000 # 最大連接數(shù)(默認(rèn) 10000,Redis 2.4+) timeout 0 # 連接空閑超時(0 表示永不超時) tcp-keepalive 300 # TCP keepalive 間隔
1.2 連接數(shù)打滿的影響
當(dāng)連接數(shù)達(dá)到maxclients后,Redis 不再接受新連接,并直接在日志中輸出:
# Redis 9001 refused connection (maxclients) -ERR max number of clients reached
此時所有依賴 Redis 的服務(wù)都會受到影響:
新請求無法獲取 Redis 連接 → 業(yè)務(wù)線程阻塞
阻塞線程不斷重試 → 線程池打滿 → Web 容器無可用線程
上游服務(wù)的健康檢查失敗 → 從注冊中心摘除節(jié)點
流量轉(zhuǎn)移到剩余節(jié)點 → 剩余節(jié)點的 Redis 連接也暴增 → 級聯(lián)故障
二、排查過程
2.1 直接檢查 Redis 連接狀況
# 登錄 Redis 查看連接數(shù) $ 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,連接數(shù)已打滿。
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 統(tǒng)計連接數(shù):
# 統(tǒng)計各客戶端 IP 的連接數(shù)
$ 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,}"
發(fā)現(xiàn)大量連接的idle值在 600~3600 秒之間,說明這些連接長時間空閑但沒有被回收。timeout配置為 0(永不超時)。
注:Redis 7.0+ 引入CLIENT NO-TOUCH和CLIENT NO-EVICT指令,但timeout仍是控制僵尸連接的主要參數(shù)。
2.4 確認(rèn)系統(tǒng)級限制
Redis 的maxclients受系統(tǒng)文件描述符限制和內(nèi)核參數(shù)限制:
# Redis 進(jìn)程的 fd 限制 $ cat /proc/$(pidof redis-server)/limits | grep"Max open files" Max open files 10024 10024 files # 系統(tǒng)級 fd 限制 $ cat /proc/sys/fs/file-max 100000 # 當(dāng)前 fd 使用量 $ cat /proc/sys/fs/file-nr 30000 0 100000
Redis 進(jìn)程的Max open files為 10024,與 maxclients 10000 非常接近(Redis 本身還需要占用少量 fd 用于監(jiān)聽端口、AOF 寫入等)。
2.5 應(yīng)用層排查
檢查應(yīng)用側(cè)的 Redis 連接池配置:
3000 個應(yīng)用實例(部署了 15 個節(jié)點 × 每個節(jié)點 maxTotal=200) × 連接復(fù)用不足 → 實際連接數(shù)遠(yuǎn)超預(yù)期。
三、根因分析
經(jīng)過以上排查,確定本次事故由三個因素疊加導(dǎo)致:
3.1 直接原因:應(yīng)用發(fā)布后連接數(shù)暴增
當(dāng)天凌晨發(fā)布新版本后,應(yīng)用啟動時連接池的minIdle=10導(dǎo)致每個節(jié)點預(yù)先建立 10 條連接。發(fā)布方式是滾動更新,舊節(jié)點和新節(jié)點短暫共存,連接數(shù)翻倍。部分舊節(jié)點上的連接在優(yōu)雅關(guān)閉時未正確釋放,變成僵尸連接。
3.2 放大因素:timeout = 0
Redis 配置timeout=0(永不主動斷開空閑連接)。大量僵尸連接(idle > 600s)未被回收,持續(xù)占用連接槽位。等到上午業(yè)務(wù)高峰到來時,新連接請求直接打到上限。
3.3 雪崩鏈路
Redis 連接滿(maxclients=10000) → 新請求無法獲取連接 → JedisConnectionException → 應(yīng)用層無熔斷降級 → 線程被重試阻塞 → Web 容器線程池打滿 → 健康檢查接口超時 → 注冊中心摘除節(jié)點 → 流量轉(zhuǎn)移到剩余節(jié)點 → 剩余節(jié)點 Redis 連接數(shù)飆升 → 也打滿 → 服務(wù)全面不可用
四、快速止血方案
4.1 方案 A:臨時增大 maxclients(推薦首選)
# 臨時修改(重啟后失效) $ redis-cli CONFIG SET maxclients 20000
注意:增大 maxclients 的同時必須增大系統(tǒng)的 fd 限制:
# 臨時修改 Redis 進(jìn)程的 fd 限制 $ prlimit --pid $(pidof redis-server) --nofile=30000 # 或修改系統(tǒng)級限制 $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 自動清理僵尸連接
# 設(shè)置空閑超時 60 秒(立即生效) $ redis-cli CONFIG SET timeout 60
60 秒后,所有空閑超過 60 秒的連接會被 Redis 自動關(guān)閉。這會觸發(fā)以下效果:
# 觀察連接數(shù)變化 $ watch -n 5'redis-cli INFO clients | grep connected_clients'
timeout值應(yīng)根據(jù)業(yè)務(wù)心跳間隔設(shè)置。一般建議:
Web 應(yīng)用 + 連接池:timeout=60~300
長連接訂閱/推送:timeout=600~3600
僅做緩存:timeout=60
4.3 方案 C:批量清理異常連接
如果上述方案來不及等待,直接按條件 kill 連接:
# 按類型 kill(kill 所有普通客戶端連接,保留主從復(fù)制連接) $ redis-cli CLIENT KILL TYPE normal # 按 IP 段 kill(如果某臺應(yīng)用服務(wù)器連接異常) $ redis-cli CLIENT KILL addr 10.0.1.12:0 # 跳過 skipme(是否 kill 當(dāng)前連接,默認(rèn) yes) $ redis-cli CLIENT KILL addr 10.0.1.12:0 skipme no
4.4 方案 D:重啟應(yīng)用(讓連接池重建)
作為兜底方案,重啟應(yīng)用讓連接池重新初始化:
# 重啟應(yīng)用服務(wù)(確保是滾動重啟) $ systemctl restart app-service
重啟后連接數(shù)會在短時間內(nèi)回歸正常水平(minIdle 重新建立)。
五、長期治理方案
5.1 連接池最佳實踐
生產(chǎn)環(huán)境合理的連接池配置:
配置原則:
maxTotal 不要過大:單個應(yīng)用節(jié)點對單個 Redis 實例的 maxTotal 建議 20~100,視并發(fā)度而定。連接數(shù)不是越多越好,Redis 處理 10000 個空閑連接和 500 個活躍連接的開銷完全不同。
設(shè)置 maxWaitMillis:避免線程無限等待連接,建議 1000~3000ms。
開啟 testWhileIdle:定時檢測空閑連接是否可用,避免連接被 Redis 側(cè)關(guān)閉后應(yīng)用仍在使用。
代碼中正確歸還連接:使用 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(); // 歸還到連接池而非真正關(guān)閉
}
}
5.2 使用連接代理收斂架構(gòu)
如果有大量客戶端直連 Redis,考慮引入連接代理層:
[應(yīng)用實例] x 50個 → [Twemproxy / Predixy] → [Redis 主從]
優(yōu)勢:
連接數(shù)從 M × N 降為 M + N(M 是應(yīng)用節(jié)點,N 是 Redis 節(jié)點)
代理層可以緩沖突發(fā)連接請求
支持讀寫分離和故障轉(zhuǎn)移
缺點:
增加一層網(wǎng)絡(luò)跳轉(zhuǎn),延遲增加 0.1~0.5ms
代理本身可能成為瓶頸
5.3 連接限額和防火墻防護(hù)
# 限制單臺應(yīng)用服務(wù)器的 Redis 連接數(shù)(iptables) $ iptables -A INPUT -p tcp --dport 6379 -m connlimit --connlimit-above 50 -j REJECT
5.4 熔斷降級和重連退避
應(yīng)用側(cè)必須實現(xiàn)熔斷機(jī)制:
// 使用 Resilience4j CircuitBreaker
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50% 失敗率觸發(fā)熔斷
.waitDurationInOpenState(Duration.ofSeconds(30)) // 熔斷后等 30 秒
.slidingWindowSize(10)
.build();
// 或簡單的退避策略
intbaseDelay =100; // 基礎(chǔ)等待 100ms
for(inti =0; i < maxRetries; i++) {
? ??try?{
? ? ? ??return?jedisPool.getResource();
? ? }?catch?(Exception e) {
? ? ? ? Thread.sleep(baseDelay * (long)Math.pow(2, i)); ?// 指數(shù)退避
? ? }
}
5.5 監(jiān)控和告警
必須監(jiān)控的 Redis 連接指標(biāo):
# Prometheus redis_exporter 已暴露的關(guān)鍵指標(biāo) redis_connected_clients redis_config_maxclients redis_rejected_connections_total
告警閾值建議:
| 指標(biāo) | 告警閾值 | 嚴(yán)重級別 |
|---|---|---|
| connected_clients/maxclients | > 80% | Warning |
| connected_clients/maxclients | > 90% | Critical |
| rejected_connections > 0 | 立即 | P0 Emergency |
六、生產(chǎn)環(huán)境注意事項
CONFIG SET maxclients需要聯(lián)動調(diào)整多個參數(shù):
缺了任何一個,maxclients設(shè)置不生效。
Redis 配置maxclients
系統(tǒng)的ulimit -n
內(nèi)核的fs.file-max
/etc/security/limits.conf
不要在高峰期修改 timeout:如果timeout從 0 改為一個很小的值(如 30),會瞬間斷開大量連接,導(dǎo)致客戶端連接池出現(xiàn)批量重建連接的場景,可能觸發(fā) CPU 暴漲和網(wǎng)絡(luò)抖動。
CLIENT KILL 需要注意 skipme:如果當(dāng)前連接也在 kill 范圍內(nèi),會導(dǎo)致當(dāng)前命令執(zhí)行中斷。默認(rèn) skipme=yes 不 kill 當(dāng)前連接。
云 Redis 服務(wù)的 maxclients 限制:各云廠商 Redis 實例的maxclients可能與實例規(guī)格綁定(如 2GB 實例 = maxclients 10000),不能無限上調(diào)。提工單前先確認(rèn)產(chǎn)品文檔。
連接池泄漏排查:如果頻繁出現(xiàn)連接打滿但CLIENT LIST看不出來異常,檢查應(yīng)用代碼中是否將 Jedis 實例作為成員變量而非局部變量使用,或者異常分支未執(zhí)行 close。
七、總結(jié)
Redis 連接數(shù)打滿導(dǎo)致業(yè)務(wù)雪崩的排查和治理可以分為三層:
第一層:快速止血
CONFIG SET maxclients 20000 # 增大上限 CONFIG SET timeout 60 # 清理僵尸連接 CLIENT KILL TYPE normal # 批量殺異常連接
第二層:排查根因
檢查連接分布 → 分析空閑時間 → 核對連接池配置 → 確認(rèn)系統(tǒng)限制
第三層:架構(gòu)治理
連接池規(guī)范化 → 熔斷降級 → 代理收斂 → 監(jiān)控告警 → 應(yīng)急預(yù)案
本次故障的核心教訓(xùn)是:
timeout=0在生產(chǎn)環(huán)境是高風(fēng)險配置,必須設(shè)置合理的空閑超時
連接池的 minIdle/maxTotal 要根據(jù)實際并發(fā)度計算,而非隨意配置
服務(wù)必須有熔斷降級機(jī)制,否則單一組件故障會級聯(lián)擴(kuò)散
發(fā)布過程中要監(jiān)控連接數(shù)變化,發(fā)現(xiàn)異常立即回滾或調(diào)整
-
網(wǎng)絡(luò)
+關(guān)注
關(guān)注
14文章
8341瀏覽量
95622 -
線程
+關(guān)注
關(guān)注
0文章
511瀏覽量
20878 -
Redis
+關(guān)注
關(guān)注
0文章
395瀏覽量
12262
原文標(biāo)題:一次 Redis 連接數(shù)打滿導(dǎo)致業(yè)務(wù)雪崩的排查記錄
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
2011全球移動連接數(shù)將增至56億
ESP32-C3超過了最大站點連接數(shù)如何解決?
請問ESP32藍(lán)牙連接數(shù)如何設(shè)置?
企業(yè)打開Redis的正確方式,來自阿里云云數(shù)據(jù)庫團(tuán)隊的解讀
請問如何讓一個數(shù)從零開始遞增每遞增一次間隔1s記錄一次記錄完成再進(jìn)行遞增依次循環(huán)
防火墻的并發(fā)連接數(shù)
[Ganglia監(jiān)控擴(kuò)展]監(jiān)控nginx的連接數(shù)
一次Redis連接數(shù)打滿導(dǎo)致業(yè)務(wù)雪崩的排查記錄
評論