JedisPool無法獲得資源問題


線上碰到一個問題:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

at redis.clients.util.Pool.getResource(Pool.java:22)

線上會相隔不定時的天數后出現一次JedisPool種getresouce拿不到resource的情況。中間陸陸續續上過很多次線,然后廢了很大勁努力排除掉了業務可能和多次上線的代碼問題。業務數據量即便是在測試環境種建造了更多,也不會導致那種情況的出現。而業務代碼測試環境和線上相同,后來在測試環境壓測的壓力和線上差不多的情況下,也不會重現這個問題。
后來就有一種束手無策的感覺了,最后只能推論是當時應用集群到Redis集群的網絡出了問題了,但是由於種種原因一直沒有在集群間添加網絡狀態的監控,也就只能是猜測了,但是又沒辦法重現。后來偷偷在線上的一台服務器上面添加了ping的監控,很簡單:ping -i 1 192.168.134.155 > pinglog_{`date +%Y-%m-%d`}.log &,該命令的效果比較簡單,就是每隔1sping一次目標服務器,然后打印到按天分開的日志里面。然而這種事情不再出現我們酒沒辦法驗證推論,領導又催的非常緊,沒辦法還是需要驗證出來啊。
開始的時候,根據代碼來找原因,代碼里面從jedispool種獲得jedis資源實例的代碼是使用了java7里面的try-with-resouce的寫法,也就是用完之后,於是就懷疑是不是這種寫法,在try塊里面有了其他異常會導致resouce無法正常關閉,導致某個Jedis實例用完后沒有還給JedisPool,導致資源不足?

 

public class JedisTest {
    private static final JedisPool jedisPool;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(20);
        config.setMaxTotal(40);
        config.setMinIdle(10);

        jedisPool = new JedisPool(config, "127.0.0.1", 8279, 1000);
    }

    public static void main(String[] args) {
        try(Jedis jedis = jedisPool.getResource()){
            throw new Exception("~");
        }catch (Exception e){
            //do nothing
        }
    }
}

后來其實在JedisPool里面的斷點很容易就可以看到java7 並沒有錯誤,多心了。

於是,那還是回歸主題,其實只要認真分析,不會又那么困難的問題出現:
其實getresouce報錯有兩種可能:
1、本身有錯誤---排除,首先如果這個方法有錯誤,那么之前應該會一直出現,或者其他人也早該把開源包的錯誤爆出,排除這種可能;
2、就是在規定時間內沒取到資源。
剛才我們看maxtotal里面定義了池子最大就40個,如果真的40個都在用,並且在超時的100ms內沒人return resouce,那報錯也正常。
也就是說,我們出現了40個全部被用到,並且在超時的100ms內沒有任何資源還給JedisPool。
后來恰好,在打印的jstack的信息種發現了大量的time_waiting狀態的線程在等待從Jedispool.getResouce().
那么什么情況下會導致這個情況出現?
假設現在並發來了41個請求,然后其中40個正常的進行,但是第41沒拿到資源,於是等待規定的超時時間,但是這會從應用到Redis集群間網絡出現抖動,暫時不通,會導致40個請求種的里面的jedis的get或者set操作變慢甚至超時。
我們設想一種情況:從jedisPool里面拿資源的超時時間是100ms,程序里面進行get或者set資源的是200ms超時,那么就有可能出現這種情況。
事實證明我們的配置確實是jedis里面去get或者set一個key的時候,超時時間是200ms,那也就是說,如果網絡發生了抖動,那就會在並發的情況下迅速耗光資源池,然后超時后報錯才還回去,但是那個時間早就發生了getResouce的錯誤。
Bingo,其實很簡單的原因,那就是沒有正確的理解兩個超時時間之間的關系。
我們可以簡單測試一下:

public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(20);
        for(int i = 0;i < 20 ;i ++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    try(Jedis jedis = jedisPool.getResource()) {
                        Thread.sleep(200L);
                    } catch (InterruptedException e) {
                        System.out.println(e);
                    }
                }
            });
        }
    }

由於本地環境問題,只是示例代碼,就不執行了。其實很容易就還原了問題出來。

后面只要調小jedis的get和set方法的超時時間,同時也盡量小的使用getresource的超時時間(這里為什么不加大,因為在高並發的情況下會迅速耗光線程數量,jstack里面甚至出現了500個線程有450個是time_waiting的狀態,這可不是我們想要的結果)。

然后在服務器之間添加監控和警報,及時報警進行網絡的修復。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM