【redis】常見JedisConnectionException異常分析
最近專案開發中用到了Redis, 選擇了官網推薦的java client Jedis。
Redis常用命令學習:http://redis.io/commandsRedis官方推薦Java客戶端Jedis(包含了所有Redis命令的實現):https://github.com/xetorthio/jedis
Jedis使用過程中最常見異常JedisConnectionException有時確實給我們帶來了很多困惑,這個異常通常出現在兩個使場景。
一、當我們執行如下JedisPool類例項的getResource()時丟擲can't get a resource異常。
異常程式碼如下:
redis.clients.jedis.exceptions.JedisConnectionException
at redis.clients.util.Pool.getResource(Pool.java:22)
分析:
redis.clients.util.Pool.getResource會從JedisPool例項池中返回一個可用的redis連線。分析原始碼可知JedisPool extends redis.clients.util.Pool<Jedis> .而Pool<T>是通過
commons-pool開源工具包中的org.apache.commons.pool.impl.GenericObjectPool
其中三個重要個幾個屬性是:
MaxActive: 可用連線例項的最大數目,為負值時沒有限制。
MaxIdle: 空閒連線例項的最大數目,為負值時沒有限制。Idle的例項在使用前,通常會通過org.apache.commons.pool.BasePoolableObjectFactory<T>的activateObject()方法使其變得可用。
MaxWait
(注:pool.getResource()方法實際呼叫的GenericObjectPool類borrowObject()方法,該方法會根據MaxWait變數值在沒有可用連線(idle/active)時阻塞等待知道超時,具體含義參看api。)
也就是說當連線池中沒有active/idle的連線時,會等待maxWait時間,如果等待超時還沒有可用連線,則丟擲Could not get a resource from the pool異常。所以為避免這樣的錯誤,
我們應該根據程式實際情況合理設定這三個引數的值,同時在我們獲取一個連線的程式方法中也應該合理的處理這個異常,當沒有連線可用時,等待一段時間再獲取也許是個比較好的選擇。
二、當我們獲取連線後對redis進行操作時,丟擲redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out異常。
異常程式碼如下:
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
at redis.clients.jedis.Protocol.process(Protocol.java:79)
at redis.clients.jedis.Protocol.read(Protocol.java:131)
at redis.clients.jedis.Connection.getIntegerReply(Connection.java:188)
at redis.clients.jedis.Jedis.sismember(Jedis.java:1266)
我們還是先分析一下Jedis的原始碼吧,以sadd操作為例:
- public Long sadd(final String key, final String... members) {
- checkIsInMulti();
- client.sadd(key, members);
- return client.getIntegerReply();
- }
public class Client extends BinaryClient implements Commands;
public class BinaryClient extends Connection;
Connection包裝了對Redis server的socket操作,命令寫操作通過socket.getOutputStream()輸出流將命令資訊傳送到redis server,當寫完命令後要通過socket.getInputStream()的到的輸入流將命令執行結果返回,這中間必然會有一個命令執行到結果返回的延時時間,這就是一個Jedis呼叫redis命令操作所用的時間。
需要說明的是,Redis server是單執行緒執行所有連線傳送過來的命令的,也就是說不管併發中有多少個client在傳送命令,redis-server端是單執行緒處理的,並按照預設的FIFO方式處理請求,
這個可在redis.conf配置檔案中配置。關於redis server的詳細執行機制參見:http://redis.io/documentation
所以client.sadd(key, members);呼叫完後只是將命令資訊傳送到了redis server端,具體有沒有執行要看redis server的負載情況。然後,通過client.getIntegerReply();等待(time out)返回結果。Connection初始化socket時有多種選擇,其中設定socket time out 的方法如下:
- public void rollbackTimeout() {
- try {
- socket.setSoTimeout(timeout);
- socket.setKeepAlive(false);
- } catch (SocketException ex) {
- throw new JedisException(ex);
- }
- }
ava.net.SocketTimeoutException: Read timed out異常呢?redis操作記憶體雖然平均毫秒級的,但當資料量很大時未必都如此快速。在我的開發過程中就遇到過一個集合到了
千萬級資料量,一次操作超時時間在秒級是很正常的,而且機器效能很好的情況下已經如此,更何況我們本機開發的機器相對於生產伺服器來說速度會更慢了。所以在初始化JedisPool時應該根據實際
情況通過redis.clients.jedis.JedisPoolConfig合理設定連線池引數,通過edisPool構造方法,合理設定socket讀取輸入InputStream的超時時間。
- pool = new JedisPool(config, host, port, 100000);
注意第四個引數time out,設定成我們能容忍的超時時間,單位是毫秒。但不知道為什麼既然單位是毫秒,為什麼引數型別是int而不是long。
設定第四個引數後,我在四千萬資料量集合上操作最多一次大概超時5秒,問題基本解決。