Redis客戶端常見異常分析

一.沒法從鏈接池獲取到鏈接

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

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時的效果。git

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)

對於這個問題,須要重點討論的是爲何鏈接池沒有資源了,形成沒有資源的可能的緣由很是多github

  • 1.客戶端:高併發下鏈接池設置太小,出現供不該求,因此會出現上面的錯誤,可是正常狀況下只要比默認的最大鏈接數(8個)多一些便可,由於正常狀況下JedisPool以及Jedis的處理效率足夠高。redis

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

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的理解,順藤摸瓜逐漸找到問題所在。

2、 客戶端讀寫超時

Jedis在調用Redis時,若是出現了讀寫超時後,會出現下面的異常:併發

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

 

形成該異常的緣由也有如下幾種:運維

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

三 客戶端鏈接超時

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

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

 

形成該異常的緣由也有如下幾種:分佈式

  • 鏈接超時設置的太短。
  • Redis發生阻塞,形成tcp-backlog已滿,形成新的鏈接失敗。
  • 客戶端與服務端網絡不正常。

4、客戶端緩衝區異常

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對象同時被多個線程併發操做,可能會出現上述異常。

5、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.

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

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

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

 

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

Jedis調用Redis執行寫操做時,若是Redis的使用內存大於maxmemory的設置,會收到下面的異常,此時應該調整maxmemory並找到形成內存增加的緣由(maxmemory以前章節已經介紹了)

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

8、客戶端鏈接數過大

若是客戶端鏈接數超過了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。

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

附贈GenericObjectPoolConfig的重要屬性

序號 參數名 含義 默認值
1 maxActive 鏈接池中最大鏈接數 8
2 maxIdle 鏈接池中最大空閒的鏈接數 8
3 minIdle 鏈接池中最少空閒的鏈接數 0
4 maxWaitMillis 當鏈接池資源用盡後,調用者的最大等待時間(單位爲毫秒),通常不建議使用默認值 -1:表示永遠不超時,一直等。
5 jmxEnabled 是否開啓jmx監控,若是應用開啓了jmx端口而且jmxEnabled設置爲true,就能夠經過jconsole或者jvisualvm看到關於鏈接池的相關統計,有助於瞭解鏈接池的使用狀況,而且能夠針對其作監控統計 true
6 minEvictableIdleTimeMillis 鏈接的最小空閒時間,達到此值後空閒鏈接將被移除 30分鐘
7 numTestsPerEvictionRun 作空閒鏈接檢測時,每次的採樣數 3
8 testOnBorrow 向鏈接池借用鏈接時是否作鏈接有效性檢測(ping),無效鏈接會被移除,每次借用多執行一次ping命令 false
9 testOnReturn 向鏈接池歸還鏈接時是否作鏈接有效性檢測(ping),無效鏈接會被移除,每次歸還多執行一次ping命令 false
10 testWhileIdle 向鏈接池借用鏈接時是否作鏈接空閒檢測,空閒超時的鏈接會被移除 false
11 timeBetweenEvictionRunsMillis 空閒鏈接的檢測週期(單位爲毫秒) -1:表示不作檢測
12 blockWhenExhausted 當鏈接池用盡後,調用者是否要等待,這個參數是和maxWaitMillis對應的,只有當此參數爲true時,maxWaitMillis纔會生效 true

本文部份內容來自《Redis開發與運維》一書,轉載請聲明。

相關文章
相關標籤/搜索