jedis 用鏈接池時超時返回值類型錯誤

     這個是今天發現一個bug:在測試redis併發讀寫的時候(jedis做爲客戶端,並使用了鏈接池),老是報 java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.Long 
at redis.clients.jedis.Connection.getIntegerReply(Connection.java:161) 
at redis.clients.jedis.Jedis.del(Jedis.java:108) 
相似的錯誤,就是返回值類型和文檔上的返回值類型不相符,感受很不該該;開始懷疑是jedis實現的一個bug,後來發現一個現象,當拋一個超時異常的時候,後面就連續的出現一個相似上面的錯誤,最後終於發現了問題所在。
原先的代碼是這樣的: 

public long del(String key) {  
        long rt = 0L;  
        Jedis jedis = null;  
        try {  
            jedis = getJedis();  
            rt = jedis.del(key);  
        }   
        finally  
        {  
            releaseJedisInstance(jedis);  
        }  
        return rt;  
    }




這樣寫貌似OK,但實際上有問題,假設jedis在執行這個命令的時候,由於redis超負荷,jedis可能返回超時的異常,這個時候發生了什麼,沒有處理這個異常,直接將這個jedis的連接返回到了鏈接池,這樣有沒有問題呢? 
查看jedis源碼發現他的connection中對網絡輸出流作了一個封裝,其中自建了一個buffer,因此當發生異常的時候,這個buffer裏還殘存着上次沒有發送或者發送不完整的命令,這個時候沒有作處理,直接將該鏈接返回到鏈接池,那麼重用該鏈接執行下次命令的時候,就會將上次沒有發送的命令一塊兒發送過去,因此纔會出現上面的錯誤「返回值類型不對」; 
因此正確的寫法應該是在發送異常的時候,銷燬這個鏈接,不能再重用! 
正確的寫法以下: 
public long del(String key) {  
        long rt = 0L;  
        Jedis jedis = null;  
        try {  
            jedis = getJedis();  
            rt = jedis.del(key);  
                        releaseNormalResource(jedis);  
        } catch (Exception e) {  
            returnBrokenResource(jedis);  
                        throw Exception x;  
        }  
          
        return rt;  
    }




從上面的分析來看,我更認爲是jedis實現的一個bug,當鏈接出現異常的時候,應該對該鏈接的buffer進行清空的,你認爲呢? 


11 樓  bert82503 2015-02-09  
咱們在線上也遇到了這個問題,也使用相同的處理方法,但本質上該問題是沒有解決的。
由於在 ShardedJedisFactory.destroyObject(pooledShardedJedis) 中經過捕獲全部的Exception,同時把異常忽略了,並未warn日誌。經過這種方式雖然屏蔽了該問題,僅僅是忽略了異常,其實並未根本解決。(從下面日誌就能看出來)

「從上面的分析來看,我更認爲是jedis實現的一個bug,當鏈接出現異常的時候,應該對該鏈接的buffer進行清空的,你認爲呢?」  贊同   

Java代碼   收藏代碼
  1. [2015-02-07 09:17:47] WARN  c.f.f.b.s.r.i.CustomShardedJedisFactory -quit jedis connection for server fail: xxx.xxx.xxx.xxx:xxx  
  2.   
  3. java.lang.ClassCastException: java.lang.Long cannot be cast to [B   (強制類型轉換異常)  
  4.     at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:181) ~[jedis-2.6.2.jar:na]  
  5.     at redis.clients.jedis.BinaryJedis.quit(BinaryJedis.java:136) ~[jedis-2.6.2.jar:na]  
  6.     at cn.fraudmetrix.forseti.biz.service.redis.impl.CustomShardedJedisFactory.destroyObject(CustomShardedJedisFactory.java:116) ~[forseti-biz-service-1.0-SNAPSHOT.jar:na]  
  7.     at org.apache.commons.pool2.impl.GenericObjectPool.destroy(GenericObjectPool.java:848) [commons-pool2-2.0.jar:2.0]  
  8.     at org.apache.commons.pool2.impl.GenericObjectPool.invalidateObject(GenericObjectPool.java:626) [commons-pool2-2.0.jar:2.0]  
  9.     at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:83) [jedis-2.6.2.jar:na]  
  10.     at cn.fraudmetrix.forseti.biz.service.redis.impl.CustomShardedJedisPool.returnBrokenResource(CustomShardedJedisPool.java:121) [forseti-biz-service-1.0-SNAPSHOT.jar:na]  
  11.     at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:337) [forseti-biz-service-1.0-SNAPSHOT.jar:na]  
  12.     at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:319) [forseti-biz-service-1.0-SNAPSHOT.jar:na]  
  13.     ...  
  14. [2015-02-07 09:17:47] ERROR c.f.f.b.s.r.i.RedisServiceImpl -'zadd' key fail, key: xxx, score: xxx, member: xxx  
  15.   
  16. [2015-02-07 09:17:47] ERROR c.f.f.b.s.r.i.RedisServiceImpl -java.net.SocketTimeoutException: Read timed out  
  17.   
  18. redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out    (Socket讀取超時異常)  
  19.     at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:201) ~[jedis-2.6.2.jar:na]     ('limit = in.read(buf);' at java.io.InputStream.read(InputStream.java:100) - 這裏出現阻塞致使"Socket讀取超時"!)  
  20.     at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40) ~[jedis-2.6.2.jar:na]  
  21.     at redis.clients.jedis.Protocol.process(Protocol.java:128) ~[jedis-2.6.2.jar:na]  
  22.     at redis.clients.jedis.Protocol.read(Protocol.java:192) ~[jedis-2.6.2.jar:na]  
  23.     at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:282) ~[jedis-2.6.2.jar:na]  
  24.     at redis.clients.jedis.Connection.getIntegerReply(Connection.java:207) ~[jedis-2.6.2.jar:na]  
  25.     at redis.clients.jedis.Jedis.zadd(Jedis.java:1293) ~[jedis-2.6.2.jar:na]  
  26.     at redis.clients.jedis.ShardedJedis.zadd(ShardedJedis.java:364) ~[jedis-2.6.2.jar:na]  
  27.     at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:328) [forseti-biz-service-1.0-SNAPSHOT.jar:na]  
  28.     at cn.fraudmetrix.forseti.biz.service.redis.impl.RedisServiceImpl.zadd(RedisServiceImpl.java:319) [forseti-biz-service-1.0-SNAPSHOT.jar:na]  
  29.     ...  
  30. Caused by: java.net.SocketTimeoutException: Read timed out  
  31.     at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.7.0_51]  
  32.     at java.net.SocketInputStream.read(SocketInputStream.java:152) ~[na:1.7.0_51]  
  33.     at java.net.SocketInputStream.read(SocketInputStream.java:122) ~[na:1.7.0_51]  
  34.     at java.net.SocketInputStream.read(SocketInputStream.java:108) ~[na:1.7.0_51]  
  35.     at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:195) ~[jedis-2.6.2.jar:na]  
  36.     ... 38 common frames omitted  
10 樓  bert82503 2015-01-09  
HelloJimmy 寫道

關注


謝謝 
9 樓  HelloJimmy 2015-01-08  
bert82503 寫道
HelloJimmy 寫道
bert82503 寫道
javaeyes 寫道
這個太悲劇了,每一個請求都要try catch finally


Java代碼   收藏代碼
  1. poolConfig.setTestOnBorrow(true);  
  2. poolConfig.setTestOnReturn(true);  


這樣就搞定問題了~



這樣是OK,可是性能降低的厲害,每一個驗證就是一個網絡操做(ping/pong),效率低,並且對redis服務仍是一個負擔,十分不建議!


你說得對,咱們在實際生產環境中使用時也是關閉了這兩個屬性,在外層統一包裝一層 RedisService。
咱們基於 Jedis 定製實現的"Redis服務器異常(宕機)時自動摘除,恢復正常時自動添加"的功能。
https://github.com/EdwardLee03/jedis-x


關注
8 樓  bert82503 2015-01-06  
HelloJimmy 寫道
bert82503 寫道
javaeyes 寫道
這個太悲劇了,每一個請求都要try catch finally


Java代碼   收藏代碼
  1. poolConfig.setTestOnBorrow(true);  
  2. poolConfig.setTestOnReturn(true);  


這樣就搞定問題了~



這樣是OK,可是性能降低的厲害,每一個驗證就是一個網絡操做(ping/pong),效率低,並且對redis服務仍是一個負擔,十分不建議!


你說得對,咱們在實際生產環境中使用時也是關閉了這兩個屬性,在外層統一包裝一層 RedisService。
咱們基於 Jedis 定製實現的"Redis服務器異常(宕機)時自動摘除,恢復正常時自動添加"的功能。
https://github.com/EdwardLee03/jedis-x
7 樓  HelloJimmy 2014-12-05  
bert82503 寫道
javaeyes 寫道
這個太悲劇了,每一個請求都要try catch finally


Java代碼   收藏代碼
  1. poolConfig.setTestOnBorrow(true);  
  2. poolConfig.setTestOnReturn(true);  


這樣就搞定問題了~



這樣是OK,可是性能降低的厲害,每一個驗證就是一個網絡操做(ping/pong),效率低,並且對redis服務仍是一個負擔,十分不建議!
6 樓  bert82503 2014-11-25  
javaeyes 寫道
這個太悲劇了,每一個請求都要try catch finally


Java代碼   收藏代碼
  1. poolConfig.setTestOnBorrow(true);  
  2. poolConfig.setTestOnReturn(true);  


這樣就搞定問題了~
5 樓  zhulh0927 2013-10-09  
這實際上是jedis不支持多線程的緣由,在你調用del等方法時,在調用方法申明前加上關鍵字 synchronized就能夠解決問題。好比delkey調用del方法,在delkey方法前加synchronized就能夠了
4 樓  restart1107 2012-11-19  
你這個是什麼版本的?2 .1.0沒有你裏面的代碼了
3 樓  javaeyes 2012-09-10  
這個太悲劇了,每一個請求都要try catch finally
2 樓  HelloJimmy 2012-07-13  
Sweblish 寫道
     
redis太傻了,這方面!


no,no,no.這跟redis沒有關係,是jedis的問題。redis很棒的!
1 樓  Sweblish 2012-06-30  
      redis太傻了,這方面!
相關文章
相關標籤/搜索