最近由於不合理的配置了redis中的最大連接數導致了線上服務間歇性不可用的問題,問題無小事,穩定大於一切。
一、結論
先直接說結論:併發量激增,redis最大連接數過小,導致獲取redis連接超時,超時導致大量請求阻塞,從而導致客戶端因超時主動關閉連接,服務端大量請求阻塞,無法關閉連接,慢慢積累出現close_wait,大量close_wait出現,舊連接不釋放,新連接無法創建,導致沒法對外提供服務。
方案:1)redis連接池要根據具體的業務量進行設置,太大連接數過多浪費資源,過小無法獲取連接,影響業務。
2)不管是請求第三方系統還是緩存,數據庫,大數據系統(Hbase)等中間件,要設置符合預期的timeout和降級操作,否則請求積壓會拖死系統。
3)當服務出現大量close_wait的情況下,大概率都是服務本身的問題,需要排查響應的代碼。
二、現象
2.1 服務模塊圖
2.2 翻車現場重現
1)由於業務擴展,訪問大概增加3倍,因此選擇擴容了一倍機器。
2)機器擴容之後,報警來了,說是redis proxy的連接水位找過了70%,op讓減小連接數,之前最大連接數maxTotal設置的3000,運維讓減小到300,沒多想,修改了,上線,proxy不在報警,感覺很好。
3)有個別機器開始間歇性報警,端口不可用,應該是服務內部出現了問題。
2.3 查看各種監控(藍色問題機器,棕色正常機器)
端口可用性降低:
業務日誌明顯變少:
mem.used 內存使用驟降
thread.num 線程驟增
close_wait 大量出現:
業務日誌:
Caused by: redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
三、原因
結合之前上線的修改以及業務日誌,應該是併發量大的情況下,無法從redis連接池中獲取到連接(maxTotal設置過小),導致報錯。
3.1 爲什麼獲取不到連接會導致服務不可用?
看一下redis的配置
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空閒數 -->
<property name="maxIdle" value="100" />
<!-- 連接池的最大數據庫連接數 -->
<property name="maxTotal" value="300" />
<!-- 最小空連接數 -->
<property name="minIdle" value="100" />
<!-- 最大建立連接等待時間 -->
<property name="maxWaitMillis" value="1000" />
<!-- 連接超時時是否阻塞,false時報異常,true阻塞直到超時, 默認true -->
<property name="blockWhenExhausted" value="true" />
<!-- 返回連接時,檢測連接是否成功 -->
<property name="testOnBorrow" value="true" />
<!--定時對線程池中空閒的鏈接進行validateObject校驗 -->
<property name="testWhileIdle" value="true" />
<!--在進行returnObject對返回的connection進行validateObject校驗 -->
<property name="testOnReturn" value="true" />
</bean>
看到blockWhenExhausted這個參數是true,說明如果獲取連接失敗,那麼會阻塞maxWaitMills那麼長時間再嘗試獲取連接。那麼很清晰了,最大連接數設置的300,但是併發量比它大一到兩倍,自然會有大量的請求阻塞在獲取連接的階段。
3.2 什麼是close_wait?
感覺學的東西都還給書本了。
active close:主動關閉的一方
passive close:被動關閉的一方
3.3 爲什麼會出現大量close_wait?
上游在請求的過程中也是有超時時間的,當上遊主動發起關閉連接的時候,由於請求在服務端非常慢,收到關閉請求之後就會處於close_wait狀態,慢慢的大量的請求積壓使得連接處於close_wait狀態。
3.4 爲什麼大量close_wait會影響服務接收請求?
因爲大量的連接處於待關閉狀態,沒法釋放資源來應對新的連接。或者說由於端口未釋放,導致tcp連接失敗。
四、復現
通過微服務來將場景進行復現,服務A:ip:8300;服務B:ip:8200,A.a訪問B.b,接口b內部sleep 20s,A.a對B.b的訪問有2000ms的超時降級,我們觀察一下從A.a發起向B.b訪問過程中的網絡狀態。
發起訪問:
localhost: wahaha$ netstat -anlt | grep "8200\|8300\|Local"
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp46 0 0 *.8300 *.* LISTEN
tcp46 0 0 *.8200 *.* LISTEN
Proto/ID Flags Local Address Foreign Address (state)
2s以內:
localhost: wahaha$ netstat -anlt | grep "8200\|8300\|Local"
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp4 0 0 192.168.0.107.8200 192.168.0.107.65332 ESTABLISHED
tcp4 0 0 192.168.0.107.65332 192.168.0.107.8200 ESTABLISHED
tcp6 0 0 ::1.8300 ::1.65331 ESTABLISHED
tcp6 0 0 ::1.65331 ::1.8300 ESTABLISHED
tcp46 0 0 *.8300 *.* LISTEN
tcp46 0 0 *.8200 *.* LISTEN
Proto/ID Flags Local Address Foreign Address (state)
2s ~ 20s:
localhost: wahaha$ netstat -anlt | grep "8200\|8300\|Local"
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp4 0 0 192.168.0.107.8200 192.168.0.107.65332 CLOSE_WAIT
tcp4 0 0 192.168.0.107.65332 192.168.0.107.8200 FIN_WAIT_2
tcp46 0 0 *.8300 *.* LISTEN
tcp46 0 0 *.8200 *.* LISTEN
tcp6 0 0 ::1.8300 ::1.65331 TIME_WAIT
Proto/ID Flags Local Address Foreign Address (state)
20s以後:
localhost: wahaha$ netstat -anlt | grep "8200\|8300\|Local"
Proto Recv-Q Send-Q Local Address Foreign Address (state)
tcp46 0 0 *.8300 *.* LISTEN
tcp46 0 0 *.8200 *.* LISTEN
Proto/ID Flags Local Address Foreign Address (state)
可以看到在2s ~ 20s直接出現CLOSE_WAIT是客戶端超時主動關閉導致的。
五、總結
1)合理的設置redis連接池大小,根據業務進行估算。
2)訪問第三方服務和中間件要做降級。
3)大量close_wait出現大概率是被主動關閉了連接,並且有大量超時請求阻塞。
Author:憶之獨秀
Email:[email protected]
註明出處:https://lavorange.blog.csdn.net/article/details/106984845