這個是今天發現一個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