天天看點

jedisPool使用遇到的bug

這個是今天發現一個bug:在測試redis并發讀寫的時候(jedis作為用戶端,并使用了連接配接池),總是報用完jedis無法傳回jedisPool

Caused by: redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
    at redis.clients.util.Pool.returnResourceObject(Pool.java:69)
    at redis.clients.jedis.JedisPool.returnResource(JedisPool.java:253)
    ... 14 more
Caused by: java.lang.IllegalStateException: Object has already been returned to this pool or is invalid
    at org.apache.commons.pool2.impl.GenericObjectPool.returnObject(GenericObjectPool.java:538)
    at redis.clients.util.Pool.returnResourceObject(Pool.java:67)
    ... 15 more
           

或者

Exception in thread "Thread-71" java.lang.ClassCastException: java.lang.Long cannot be cast to [B
    at redis.clients.jedis.Connection.getBinaryBulkReply(Connection.java:259)
    at redis.clients.jedis.Connection.getBulkReply(Connection.java:248)
    at redis.clients.jedis.Jedis.lpop(Jedis.java:1055)
    at us.codecraft.webmagic.scheduler.RedisScheduler.poll(RedisScheduler.java:77)
    at us.codecraft.webmagic.Spider.run(Spider.java:308)
    at java.lang.Thread.run(Thread.java:748)
           

類似的錯誤,就是傳回值類型和文檔上的傳回值類型不相符,感覺很不應該;開始懷疑是jedis實作的一個bug,後來發現一個現象,當抛一個逾時異常的時候,後面就連續的出現一個類似上面的錯誤,最後終于發現了問題所在。

原先的代碼是這樣的:

public void releaseResource() {
        if (this.jedis != null) {
            jedisPool.returnResource(jedis);
        }
    }
           

發現returnResource被标記為廢棄,檢視jedis源代碼發現了close()方法

public void close() {
        if (this.dataSource != null) {
            if (this.client.isBroken()) {
                this.dataSource.returnBrokenResource(this);
            } else {
                this.dataSource.returnResource(this);
            }
        } else {
            this.client.close();
        }

    }
           

這個問題已經有前輩遇到過了,其解釋:

檢視 Jedis 源碼發現它的Connection中對網絡輸出流做了一個封裝(RedisInputStream),其中自建了一個buffer。當發生異常的時候,這個buffer裡還殘存着上次沒有發送或者發送不完整的指令。這個時候沒有做處理,直接将該連接配接傳回到連接配接池,那麼重用該連接配接執行下次指令的時候,就會将上次沒有發送的指令一起發送過去,是以才會出現上面的錯誤“傳回值類型不對”。

是以,正确的寫法應該是:在發送異常的時候,銷毀這個連接配接,不能再重用!

于是修改代碼為

public void releaseResource() {
        if (this.jedis != null) {
            try {
                jedis.close();
            } catch (Exception e) {
                log.error("釋放jedis資源出錯,将要關閉jedis,異常資訊:" + e.getMessage());
                if (jedis != null) {
                    try {
                        // 2. 用戶端主動關閉連接配接
                        jedis.disconnect();
                    } catch (Exception e1) {
                        log.error("disconnect jedis connection fail: " , e);
                    }finally {
                    }
                }
            }
        }
    }
           

這樣經過測試解決了jedis用完無法傳回jedisPool的問題。但是java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.Long 類型轉換的錯誤依然存在。

于是把多線程的測試環境改為單線程,單個線程調用jedis不再出現問題。但是違背了初衷。把使用jedis的對象加鎖,同時隻有一個對象使用同一個jedis,如果因為

Jedis “Socket讀取逾時”導緻“傳回值類型錯誤”
           

還是可能出現這個問題(不過幾率較小了),調用releaseReource方法銷毀jedis對象,重新從jedisPool獲得一個,不要用之前的jedis對象,問題解決

參考:

http://bert82503.iteye.com/blog/2184225 https://github.com/xetorthio/jedis/issues/186