Redis客戶端常見異常分析

一.無法從連接池獲取到連接 

JedisPool中的Jedis對象個數是有限的,默認是8個。這裏假設使用的默認配置,如果有8個Jedis對象被佔用,並且沒有歸還,如果調用者還要從JedisPool中借用Jedis,就需要進行等待(例如設置了maxWaitMillis>0),如果在maxWaitMillis時間內仍然無法獲取到Jedis對象就會拋出如下異常。



1
2
3
4
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)

還有一種情況,就是設置了blockWhenExhausted=false,那麼調用者發現池子中沒有資源時,會立即拋出異常不進行等待,下面的異常就是blockWhenExhausted=false時的效果。



1
2
3
4
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

Caused by: java.util.NoSuchElementException: Pool exhausted
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)

對於這個問題,需要重點討論的是爲什麼連接池沒有資源了,造成沒有資源的可能的原因非常多

1.客戶端:高併發下連接池設置過小,出現供不應求,所以會出現上面的錯誤,但是正常情況下只要比默認的最大連接數(8個)多一些即可,因爲正常情況下JedisPool以及Jedis的處理效率足夠高。


2.客戶端:沒有正確使用連接池,比如沒有進行釋放,例如下面代碼所示: 定義JedisPool,使用默認的連接池配置。


1
2
3
4
5
6
7
8
9
10
11
12
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
//向JedisPool借用8次連接,但是沒有執行歸還操作。
for (int i = 0; i < 8; i++) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.ping();
} catch (Exception e) {
e.printStackTrace();
}
}

當調用者再向連接池借用Jedis時(如下操作),就會拋出異常:



1
jedisPool.getResource().ping();

3.客戶端:存在慢查詢操作,這些慢查詢持有的Jedis對象歸還速度會比較慢,造成池子滿了。 
4.服務端:客戶端是正常的,但是Redis服務端由於一些原因造成了客戶端命令執行過程的阻塞,也會使得客戶端拋出這種異常。 可以看到造成這個異常的原因是多個方面的,不要被異常的表象所迷惑,而且並不存在萬能鑰匙能解決所有問題,開發和運維只能不斷加強對於Redis的理解,順藤摸瓜逐漸找到問題所在。 二、 客戶端讀寫超時 

Jedis在調用Redis時,如果出現了讀寫超時後,會出現下面的異常:



1
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out


造成該異常的原因也有以下幾種:

讀寫超時設置的過短。 
命令本身就比較慢。 
客戶端與服務端網絡不正常。 
Redis自身發生阻塞。 三 客戶端連接超時 

Jedis在調用Redis時,如果出現了讀寫超時後,會出現下面的異常:



1
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out


造成該異常的原因也有以下幾種:

連接超時設置的過短。 
Redis發生阻塞,造成tcp-backlog已滿,造成新的連接失敗。 
客戶端與服務端網絡不正常。 四、客戶端緩衝區異常 

Jedis在調用Redis時,如果出現客戶端數據流異常,會出現下面的異常。



1
redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.


造成這個異常原因可能有如下幾種:

1.輸出緩衝區滿。例如將普通客戶端的輸出緩衝區設置爲1M 1M 60: 
1
config set client-output-buffer-limit "normal 1048576 1048576 60 slave 268435456 67108864 60 pubsub 33554432 8388608 60"

如果使用get命令獲取一個bigkey(例如3M),就會出現這個異常。

2.長時間閒置連接被服務端主動斷開,可以查詢timeout配置的設置以及自身連接池配置是否需要做空閒檢測。 
3.不正常併發讀寫:Jedis對象同時被多個線程併發操作,可能會出現上述異常。 五、Lua腳本正在執行 

如果Redis當前正在執行Lua腳本,並且超過了lua-time-limit,此時Jedis調用Redis時,會收到下面的異常。對於如何處理這類問題(Lua lua-time-limit配置之前章節已經介紹了)



1
redis.clients.jedis.exceptions.JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

六、Redis正在加載持久化文件 

Jedis調用Redis時,如果Redis正在加載持久化文件,那麼會收到下面的異常。



1
redis.clients.jedis.exceptions.JedisDataException: LOADING Redis is loading the dataset in memory


七、Redis使用的內存超過maxmemory配置 

Jedis調用Redis執行寫操作時,如果Redis的使用內存大於maxmemory的設置,會收到下面的異常,此時應該調整maxmemory並找到造成內存增長的原因(maxmemory之前章節已經介紹了)



1
redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.

八、客戶端連接數過大 

如果客戶端連接數超過了maxclients,新申請的連接就會出現如下異常:



1
redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached


此時新的客戶端連接執行任何命令,返回結果都是如下:



1
2
127.0.0.1:6379> get hello
(error) ERR max number of clients reached

這個問題可能會比較棘手,因爲此時無法執行Redis命令,一般來說可以從兩個方面進行着手。

1.客戶端:如果maxclients參數不是很小的話,應用方的客戶端連接數基本不會超過maxclients,通常來看是由於應用方對於Redis客戶端使用不當造成的。此時如果應用方是分佈式結構的話,可以通過下線部分應用節點(例如佔用連接較多的節點),使得Redis的連接數先降下來。從而讓絕大部分節點可以正常運行,此時在再通過查找程序bug或者調整maxclients進行問題的修復。


2.服務端:如果此時客戶端無法處理,而當前Redis爲高可用模式(例如Redis Sentinel和Redis Cluster),可以考慮將當前Redis做故障轉移。

此問題不存在確定的解決方式,但是無論從哪個方面進行處理,故障的快速恢復極爲重要,當然更爲重要的是找到問題的所在,否則一段時間後客戶端連接數依然會超過maxclients。


九、JedisCluster異常將在集羣章節介紹。 

附贈GenericObjectPoolConfig的重要屬性



序號 
參數名 
含義 
默認值 

maxActive 
連接池中最大連接數 




maxIdle 
連接池中最大空閒的連接數 




minIdle 
連接池中最少空閒的連接數 




maxWaitMillis 
當連接池資源用盡後,調用者的最大等待時間(單位爲毫秒),一般不建議使用默認值 
-1:表示永遠不超時,一直等。 



jmxEnabled 
是否開啓jmx監控,如果應用開啓了jmx端口並且jmxEnabled設置爲true,就可以通過jconsole或者jvisualvm看到關於連接池的相關統計,有助於瞭解連接池的使用情況,並且可以針對其做監控統計 
true 



minEvictableIdleTimeMillis 
連接的最小空閒時間,達到此值後空閒連接將被移除 
30分鐘 



numTestsPerEvictionRun 
做空閒連接檢測時,每次的採樣數 




testOnBorrow 
向連接池借用連接時是否做連接有效性檢測(ping),無效連接會被移除,每次借用多執行一次ping命令 
false 



testOnReturn 
向連接池歸還連接時是否做連接有效性檢測(ping),無效連接會被移除,每次歸還多執行一次ping命令 
false 


10 
testWhileIdle 
向連接池借用連接時是否做連接空閒檢測,空閒超時的連接會被移除 
false 


11 
timeBetweenEvictionRunsMillis 
空閒連接的檢測週期(單位爲毫秒) 
-1:表示不做檢測 


12 
blockWhenExhausted 
當連接池用盡後,調用者是否要等待,這個參數是和maxWaitMillis對應的,只有當此參數爲true時,maxWaitMillis纔會生效 
true 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章