队列消费者线程不工作问题排查

现象:队列在抛出异常后不再进行消费,但是线程仍然存活

查看线程状态

  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发现pool-1-thread-1这个线程有问题

线程状态为“waiting on condition”:

说明它在等待另一个条件的发生,来把自己唤醒,或者干脆它是调用了 sleep(N)。

此时线程状态大致为以下几种:

java.lang.Thread.State: WAITING (parking):线程挂起,一直等那个条件发生;

java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己。

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

由at org.apache.http.pool.PoolEntryFuture.await可知,是http连接池被拿光了连接数

推测可能是由于http的连接资源没有正确释放导致的

配合业务日志打印的异常“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语法创建的资源抛出异常后,JVM会自动调用close 方法进行资源释放,当没有抛出异常正常退出try-block时候也会调用close方法。

try-catch-resources语法

try-catch-resources语法自jdk1.7新增,在try的()内部创建资源,创建的资源在退出try-block时候会自动调用该资源的close方法

示例:

public class AutoCloseableDemo {
    public static void main(String[] args) {
        try (AutoCloseableApp app = new AutoCloseableApp(); AutoCloseableApp2 app2 = new AutoCloseableApp2()) {
            System.out.println("--执行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--");
        }
    }
}

输出:

--执行main方法--
--close2--
--close1--
--exception--
--finally--
  1. 由带资源的try语句管理的资源必须是实现了AutoCloseable接口的类的对象
  2. 在try代码中声明的资源被隐式声明为fianl
  3. 通过使用分号分隔每个声明可以管理多个资源
  4. 执行顺序: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的源码,也没发现什么特殊之处

还需要继续研究

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,924评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,776评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,954评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,576评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,589评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,105评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,480评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,457评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,998评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,026评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,174评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,812评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,502评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,979评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,101评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,674评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,204评论 2 363

推荐阅读更多精彩内容