隊列消費者線程不工作問題排查

現象:隊列在拋出異常后不再進行消費伦泥,但是線程仍然存活

查看線程狀態(tài)

  1. 進入docker
  2. jps -l查看pid
  3. jstack -l pid
    "pool-1-thread-1" #37 prio=5 os_prio=0 cpu=100.93ms elapsed=125.98s tid=0x00007fc530ccd800 nid=0x39 waiting on condition  [0x00007fc4dc9b9000]
   java.lang.Thread.State: WAITING (parking)
    at jdk.internal.misc.Unsafe.park(java.base@11.0.2/Native Method)
    - parking to wait for  <0x0000000095cc6b80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(java.base@11.0.2/LockSupport.java:194)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@11.0.2/AbstractQueuedSynchronizer.java:2081)
    at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:138)
    at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:306)
    at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:64)
    at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:192)
    at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:185)
    at org.apache.http.pool.PoolEntryFuture.get(PoolEntryFuture.java:107)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.leaseConnection(PoolingHttpClientConnectionManager.java:276)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:263)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:190)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
    at com.XXXXXX.taiji.common.api.ExternalAPIClient.lambda$callWithTracer$0(ExternalAPIClient.java:81)
    at com.XXXXXX.taiji.common.api.ExternalAPIClient$$Lambda$374/0x00000001005cd440.run(Unknown Source)
    at com.XXXXXX.taiji.common.tracer.TracerSupport.lambda$new$0(TracerSupport.java:6)
    at com.XXXXXX.taiji.common.tracer.TracerSupport$$Lambda$310/0x000000010045d840.with(Unknown Source)
    at com.XXXXXX.taiji.common.tracer.TracerSupport.lambda$tracer$1(TracerSupport.java:17)
    at com.XXXXXX.taiji.common.tracer.TracerSupport$$Lambda$331/0x00000001004d6440.run(Unknown Source)
    at com.XXXXXX.taiji.common.cat.CatExternalAPITracer.with(CatExternalAPITracer.java:22)
    at com.XXXXXX.taiji.common.cat.CatExternalAPITracer.with(CatExternalAPITracer.java:11)
    at com.XXXXXX.taiji.common.tracer.TracerSupport.lambda$tracer$2(TracerSupport.java:16)
    at com.XXXXXX.taiji.common.tracer.TracerSupport$$Lambda$312/0x000000010045d040.with(Unknown Source)
    at com.XXXXXX.taiji.common.api.ExternalAPIClient.callWithTracer(ExternalAPIClient.java:79)
    at com.XXXXXX.taiji.common.api.ExternalAPIClient.executeWithJSON(ExternalAPIClient.java:72)
    at com.XXXXXX.taiji.qingniao.service.factory.WeiWangSendService.sendMsg(WeiWangSendService.java:76)
    at com.XXXXXX.taiji.qingniao.service.factory.AbstractSendService.sendSMS(AbstractSendService.java:63)
    at com.XXXXXX.taiji.qingniao.service.MessageService.deal(MessageService.java:286)
    at com.XXXXXX.taiji.qingniao.redismq.SendMessageRedisQueue.lambda$start$0(SendMessageRedisQueue.java:74)
    at com.XXXXXX.taiji.qingniao.redismq.SendMessageRedisQueue$$Lambda$315/0x000000010045a440.accept(Unknown Source)
    at com.XXXXXX.taiji.common.redismq.RedisQueueWorker.lambda$kickit$1(RedisQueueWorker.java:85)
    at com.XXXXXX.taiji.common.redismq.RedisQueueWorker$$Lambda$372/0x00000001005ce440.run(Unknown Source)
    at java.util.concurrent.Executors$RunnableAdapter.call(java.base@11.0.2/Executors.java:515)
    at java.util.concurrent.FutureTask.run(java.base@11.0.2/FutureTask.java:264)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@11.0.2/ThreadPoolExecutor.java:1128)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@11.0.2/ThreadPoolExecutor.java:628)
    at java.lang.Thread.run(java.base@11.0.2/Thread.java:834)

waiting on condition

通過jstack -l pid發(fā)現pool-1-thread-1這個線程有問題

線程狀態(tài)為“waiting on condition”:

說明它在等待另一個條件的發(fā)生,來把自己喚醒,或者干脆它是調用了 sleep(N)档礁。

此時線程狀態(tài)大致為以下幾種:

java.lang.Thread.State: WAITING (parking):線程掛起,一直等那個條件發(fā)生脊髓;

java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的恭朗,那個條件不到來,也將定時喚醒自己。

at org.apache.http.pool.PoolEntryFuture.await

由at org.apache.http.pool.PoolEntryFuture.await可知弟翘,是http連接池被拿光了連接數

推測可能是由于http的連接資源沒有正確釋放導致的

配合業(yè)務日志打印的異常“api xxxx respond status code 503”

推測可能是http請求過程中拋出異常導致了沒有正確close

代碼排查

this.tracer().with(traceKey, req, () -> {
    try {
        var res = this.client.execute(req);
        var code = res.getStatusLine().getStatusCode();
        if (code < 200 || code >= 300) {
            throw GlobalErrors.API_STATUS_ERROR.args(path, code);
        }
        var output = new ByteArrayOutputStream();
        var input = res.getEntity().getContent();
        IOUtils.copy(input, output);
        input.close();
        String content = output.toString();
        if (content.isEmpty()) {
            throw GlobalErrors.API_ACCESS_ERROR.args(path, "http body is empty");
        }
        holder.value(content);
    } catch (IOException e) {
        throw GlobalErrors.API_ACCESS_ERROR.args(path, e.getMessage(), e);
    }
});

res、output、input一共三個

OutputStream和InputStream的close方法是一個空方法辟癌,交給jvm的gc來處理黍少,不關閉也沒事

this.client.execute(req)返回的是一個CloseableHttpResponse(接口)厂置,源碼比較復雜,最終找到res的close方法調用了releaseConnection

public void close() throws IOException {
    this.releaseConnection(false);
}

那么應該是Response沒有close導致的

AutoCloseable

java的輸入輸出流传于、各種Connection沼溜,都繼承了AutoCloseable接口

看了下AutoCloseable接口的源碼系草,注釋比較長,從注釋可知它的出現是為了更好的管理資源能耻,準確說是資源的釋放晓猛,當一個資源類實現了該接口close方法戒职,在使用try-catch-resources語法創(chuàng)建的資源拋出異常后,JVM會自動調用close 方法進行資源釋放捧韵,當沒有拋出異常正常退出try-block時候也會調用close方法纫版。

try-catch-resources語法

try-catch-resources語法自jdk1.7新增其弊,在try的()內部創(chuàng)建資源梭伐,創(chuàng)建的資源在退出try-block時候會自動調用該資源的close方法

示例:

public class AutoCloseableDemo {
    public static void main(String[] args) {
        try (AutoCloseableApp app = new AutoCloseableApp(); AutoCloseableApp2 app2 = new AutoCloseableApp2()) {
            System.out.println("--執(zhí)行main方法--");
            throw new RuntimeException("--exception--");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("--finally--");
        }
    }

    public static class AutoCloseableApp implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("--close1--");
        }
    }

    public static class AutoCloseableApp2 implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("--close2--");
        }
    }
}

輸出:

--執(zhí)行main方法--
--close2--
--close1--
--exception--
--finally--
  1. 由帶資源的try語句管理的資源必須是實現了AutoCloseable接口的類的對象
  2. 在try代碼中聲明的資源被隱式聲明為fianl
  3. 通過使用分號分隔每個聲明可以管理多個資源
  4. 執(zhí)行順序:close-catch-finally
  5. 有多個資源時赂苗,關閉順序為資源聲明順序的反序

修改后的代碼

this.tracer().with(traceKey, req, () -> {
    try (var res = this.client.execute(req)) {
        var code = res.getStatusLine().getStatusCode();
        if (code < 200 || code >= 300) {
            throw GlobalErrors.API_STATUS_ERROR.args(path, code);
        }
        var output = new ByteArrayOutputStream();
        var input = res.getEntity().getContent();
        IOUtils.copy(input, output);
        String content = output.toString();
        if (content.isEmpty()) {
            throw GlobalErrors.API_ACCESS_ERROR.args(path, "http body is empty");
        }
        holder.value(content);
    } catch (IOException e) {
        throw GlobalErrors.API_ACCESS_ERROR.args(path, e.getMessage(), e);
    }
});

問題來了拌滋,為什么不拋出異常的時候不close也沒事赌渣?

this.tracer().with(traceKey, req, () -> {
    try {
        var res = this.client.execute(req);
        var code = res.getStatusLine().getStatusCode();
        var output = new ByteArrayOutputStream();
        var input = res.getEntity().getContent();
        IOUtils.copy(input, output);
        if (code < 1000) {
            throw GlobalErrors.API_STATUS_ERROR.args(path, code);
        }
        String content = output.toString();
        if (content.isEmpty()) {
            throw GlobalErrors.API_ACCESS_ERROR.args(path, "http body is empty");
        }
        holder.value(content);
    } catch (IOException e) {
        throw GlobalErrors.API_ACCESS_ERROR.args(path, e.getMessage(), e);
    }
});

以上這段代碼,一定會拋出異常throw GlobalErrors.API_STATUS_ERROR.args(path, code)鸿竖,res也沒有close,但是并不會出現線程park的情況。

當把IOUtils.copy(input, output)這一行代碼挪到拋出異常下面時桩撮,線程會park

查看了一下IOUtils.copy的源碼,也沒發(fā)現什么特殊之處

還需要繼續(xù)研究

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蚁吝,隨后出現的幾起案子怀伦,更是在濱河造成了極大的恐慌房待,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敏簿,死亡現場離奇詭異,居然都是意外死亡轻猖,警方通過查閱死者的電腦和手機咙边,發(fā)現死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門淑蔚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刹衫,“玉大人音羞,你說我怎么就攤上這事嗅绰【矫妫” “怎么了民镜?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵们童,是天一觀的道長慧库。 經常有香客問我,道長甘磨,這世上最難降的妖魔是什么眯停? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮椎侠,結果婚禮上,老公的妹妹穿的比我還像新娘丐吓。我一直安慰自己,他們只是感情好族操,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枷莉,像睡著了一般冒掌。 火紅的嫁衣襯著肌膚如雪股毫。 梳的紋絲不亂的頭發(fā)上铃诬,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天醇蝴,我揣著相機與錄音哑蔫,去河邊找鬼闸迷。 笑死腥沽,一個胖子當著我的面吹牛今阳,可吹牛的內容都是我干的盾舌。 我是一名探鬼主播妖谴,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洼滚!你這毒婦竟也來了遥巴?” 一聲冷哼從身側響起挪哄,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砂碉,沒想到半個月后增蹭,有當地人在樹林里發(fā)現了一具尸體滋迈,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡帝美,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年庇忌,在試婚紗的時候發(fā)現自己被綠了皆疹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片墙基。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颗祝,靈堂內的尸體忽然破棺而出恼布,到底是詐尸還是另有隱情搁宾,我是刑警寧澤盖腿,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站鸟款,受9級特大地震影響茂卦,放射性物質發(fā)生泄漏。R本人自食惡果不足惜等龙,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一蛛砰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧暴备,春花似錦、人聲如沸浅妆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽康辑。三九已至,卻和暖如春按咒,著一層夾襖步出監(jiān)牢的瞬間智袭,已是汗流浹背瞳步。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工馏臭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锐想,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓洽故,卻偏偏與公主長得像荒适,于是被迫代替她去往敵國和親串结。 傳聞我的和親對象是個殘疾皇子把敞,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容