記一次誤用JedisPool引起的系統(tǒng)假死問題排查

最近一直在搞公司Ai云平臺的服務(wù)網(wǎng)關(guān),項目涉及到了一個Oauth2的認證系統(tǒng)。實現(xiàn)認證系統(tǒng)的時候,用了Spring Security Oauth2框架,然后通過redis來實現(xiàn)用戶的token等信息的存儲。
在進行壓測的時候,發(fā)現(xiàn)系統(tǒng)經(jīng)常會出現(xiàn)假死狀態(tài),出現(xiàn)假死狀態(tài)的時候,所有請求都會被掛起不返回。發(fā)現(xiàn)這種情況時,我起初猜測是死鎖引起的,就用jconsole連到測試服務(wù)器來檢測死鎖,不過并沒有檢測到死鎖。
發(fā)現(xiàn)沒有死鎖,我就登上服務(wù)器,用jstack命令dump下當前線程的堆棧信息。拿到堆棧信息之后,我發(fā)現(xiàn)大量的線程都被阻塞在從JedisPool獲取Jedis資源上,具體堆棧信息貼在下面:


部分堆棧信息

再結(jié)合JedisPool的源碼發(fā)現(xiàn),線程都阻塞在從資源隊列中獲取資源這步。這是什么鬼?怎么會這樣?起初我懷疑是Jedis對象創(chuàng)建失敗了,所以資源隊列中沒有Jedis對象,于是我dump下了堆信息,使用VisualVM分析堆信息,但是我發(fā)現(xiàn),堆中是有Jedis對象的,而且Jedis對象個數(shù)正好和JedisPool設(shè)置的最大對象個數(shù)一致??磥碛植洛e了?。?!難道是使用JedisPool的時候有地方忘記歸還資源了???我檢查了一遍代碼,使用jedispoll的時候清一色的try()語句:

  try (Jedis jedis = jedisPool.getResource()){
  ...
  }

我這就納悶了,這是怎么回事,資源也都釋放了,為什么會這樣?靜下心來再去看線程堆棧信息,我發(fā)現(xiàn)一個問題:


堆信息

這兩個方法,在一個線程調(diào)用鏈中,方法實現(xiàn)如下:

    @Override
    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        String key = ACCESS_KEY + authentication.getOAuth2Request().getClientId();
        try (Jedis jedis = redisSource.getConnect();) {
            String accessToken = jedis.get(key);
            if (accessToken != null) {
                return readAccessToken(accessToken);
            }
        }
        return null;

    }

    @Override
    public OAuth2AccessToken readAccessToken(String tokenValue) {
        String key = TOKEN_PREFIX + tokenValue;
        try (Jedis jedis = redisSource.getConnect();) {
            List<String> result = jedis.hmget(key, "access_token", "access_key_id", "access_key", "refresh_token", "user_id");
            if (result != null && result.size() > 0 && result.get(0) != null) {
                DefaultOAuth2AccessToken auth2AccessToken = new DefaultOAuth2AccessToken(tokenValue);
                auth2AccessToken.setRefreshToken(new DefaultOAuth2RefreshToken(result.get(3)));
                Map<String, Object> map = new HashMap<>();
                map.put("accessKeyId", result.get(1));
                map.put("userId", result.get(4));
                long expire = jedis.ttl(key);
                auth2AccessToken.setExpiration(new Date(System.currentTimeMillis() + expire * 1000));
                auth2AccessToken.setAdditionalInformation(map);
                if (logger.isDebugEnabled()) {
                    logger.debug("讀取到accessToken:" + MoreObjects.toStringHelper(auth2AccessToken).toString());
                }
                return auth2AccessToken;
            }
        }
        return null;
    }

細心的朋友肯定能發(fā)現(xiàn),在獲取Jedis對象的時候有一個問題:重入了!?。]錯,就是重入了。當并發(fā)高的時候,請求一起打過來,多個線程同時執(zhí)行g(shù)etAccessToken(OAuth2Authentication authentication)方法的時候,獲取了Jedis對象,然后進入readAccessToken(String tokenValue)方法,這個時候,Jedis對象都被外部的getAccessToken(OAuth2Authentication authentication)方法持有,所以就被阻塞了,而外部的getAccessToken(OAuth2Authentication authentication)等不到readAccessToken(String tokenValue)執(zhí)行完成,所以永遠都不會釋放自己持有的Jedis對象?。?!而這種情況是檢測不到死鎖的。竟然是因為這個原因?。。∧馨l(fā)現(xiàn)這個錯誤也是運氣好呀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,886評論 11 349
  • 一、多線程 說明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)。 NEW:這種情況指的是,通過 New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,874評論 0 44
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 6,026評論 1 19
  • 欠缺太多,迷茫中努力,越努力越迷茫!思考致勝!在忙不忘學(xué)習(xí),切記!
    榮海田閱讀 214評論 0 0
  • 總算平穩(wěn)度過手術(shù)后的第一周,視力算是穩(wěn)定在1.0了。答應(yīng)小伙伴的飛秒小攻略總算是出了。 1基本概述。 26歲,近視...
    胖胖迪閱讀 642評論 0 0

友情鏈接更多精彩內(nèi)容