项目背景:

生产者生产消息到MQ,项目作为消费者接收消息,项目处理消息并通过短信/微信公众号/APP推送给用户。用户反馈长时间未收到消息推送。

排查问题:

登录MQ控制台发现,消息大量积压,刷新几次后发现已发送消息列里没有数量增长,说明消费者没有成功消费一条消息

步骤1:WARN [ActiveMQ Session Task-1] (AbstractMessageListenerContainer.java:461) - Rejecting received message because of the listener container having been stopped in the meantime: ActiveMQTextMessage ....

查看运行日志发现该错误,起初以为是项目连接MQ正常,但容器关闭的问题,经过排查并不是,大概是项目关闭时,容器先关闭连接后关闭的情况。

步骤2:内存问题

通过MQ控制台的Send  To功能发送测试消息时,测试环境能够正常接收到消息,但正式环境未能接收到消息,所以估计是两个环境内存方面的问题。

通过  top  -c 发现有一个java进程占用了近20%的内存,另外还有几个java进程占用内存也很大,再通过 free -h 发现空闲内存只有不到1G了。但是这个服务器只运行了一个Java项目,其它的Java项目从何而来是个问题了。

使用jps -l 发现多个假死的Java进程,索性全部kill掉,再重启项目。

再看MQ,发现消息在逐步被消息了,貌似问题得以解决。但第二天早上来看,发现MQ又积压了。只能把前天到当前的日志全部下载下拉慢慢排查。

步骤3:仔细查看日志,再排查MQ线程

查看日志发现,项目运行初期,确实有收到MQ的消息。

但从日志底部往上看,并没有MQ的线程处理的日志,便查找最后一次接收MQ消息在什么地方,

果不其然发现报错:

Exception in thread "httpclient-dispatch-1" java.lang.NoSuchMethodError: javax.net.ssl.SSLParameters.setApplicationProtocols([Ljava/lang/String;)V
	at org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy.applyParameters(DefaultClientTlsStrategy.java:108)
	at org.apache.hc.client5.http.ssl.AbstractClientTlsStrategy.lambda$upgrade$0(AbstractClientTlsStrategy.java:138)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.initialize(SSLIOSession.java:297)
	at org.apache.hc.core5.reactor.ssl.SSLIOSession.beginHandshake(SSLIOSession.java:272)
	at org.apache.hc.core5.reactor.InternalDataChannel.startTls(InternalDataChannel.java:259)
	at org.apache.hc.client5.http.impl.nio.DefaultManagedAsyncClientConnection.startTls(DefaultManagedAsyncClientConnection.java:158)
	at org.apache.hc.client5.http.ssl.AbstractClientTlsStrategy.upgrade(AbstractClientTlsStrategy.java:111)
	at org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy.upgrade(DefaultClientTlsStrategy.java:48)
	at org.apache.hc.client5.http.impl.nio.DefaultAsyncClientConnectionOperator$1.completed(DefaultAsyncClientConnectionOperator.java:116)
	at org.apache.hc.client5.http.impl.nio.DefaultAsyncClientConnectionOperator$1.completed(DefaultAsyncClientConnectionOperator.java:107)
	at org.apache.hc.core5.concurrent.BasicFuture.completed(BasicFuture.java:148)
	at org.apache.hc.core5.concurrent.ComplexFuture.completed(ComplexFuture.java:72)
	at org.apache.hc.client5.http.impl.nio.MultihomeIOSessionRequester$2$1.completed(MultihomeIOSessionRequester.java:153)
	at org.apache.hc.client5.http.impl.nio.MultihomeIOSessionRequester$2$1.completed(MultihomeIOSessionRequester.java:145)
	at org.apache.hc.core5.concurrent.BasicFuture.completed(BasicFuture.java:148)
	at org.apache.hc.core5.reactor.IOSessionRequest.completed(IOSessionRequest.java:73)
	at org.apache.hc.core5.reactor.InternalConnectChannel.onIOEvent(InternalConnectChannel.java:78)
	at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51)
	at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:176)
	at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:125)
	at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:92)
	at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
	at java.lang.Thread.run(Thread.java:748)

再查看MQ线程的情况。

jstack <pid> | grep -A 30 "ActiveMQ Session Task"

发现两个线程都是在等待的状态,那么就发现问题了。刚好报错的线程就是阻塞的线程。

"ActiveMQ Session Task-913" #9367 prio=7 os_prio=0 tid=0x00007f80dc002800 nid=0x1e00 waiting on condition [0x00007f7f9ff51000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000ae5a63a8> (a java.util.concurrent.CompletableFuture$Signaller)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1693)
        at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3323)
        at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1729)
        at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
        at com.zingrow.web.InformationDistribute.component.SMSSendMessage.sendMessage(SMSSendMessage.java:69)
        at com.zingrow.web.InformationDistribute.component.SMSSendMessage.sendMessage(SMSSendMessage.java:38)
        at com.zingrow.web.jms.Receiver.onMessage(Receiver.java:79)
        at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:744)
        at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:682)
        at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:649)
        at org.springframework.jms.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:620)
        at org.springframework.jms.listener.SimpleMessageListenerContainer.processMessage(SimpleMessageListenerContainer.java:330)
        at org.springframework.jms.listener.SimpleMessageListenerContainer$2.onMessage(SimpleMessageListenerContainer.java:306)
        at org.apache.activemq.ActiveMQMessageConsumer.dispatch(ActiveMQMessageConsumer.java:1401)
        - locked <0x0000000082256230> (a java.lang.Object)
        at org.apache.activemq.ActiveMQSessionExecutor.dispatch(ActiveMQSessionExecutor.java:131)
        at org.apache.activemq.ActiveMQSessionExecutor.iterate(ActiveMQSessionExecutor.java:202)
        at org.apache.activemq.thread.PooledTaskRunner.runTask(PooledTaskRunner.java:133)
        at org.apache.activemq.thread.PooledTaskRunner$1.run(PooledTaskRunner.java:48)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

阻塞的代码。

AI解答:

1. 线程状态解读ActiveMQ Session Task-679

状态:WAITING (parking)

原因:在 SMSSendMessage.sendMessage() 里调用了CompletableFuture.get()
这个方法是阻塞等待异步任务完成的,如果内部没有及时 complete(),线程就会一直卡在这里。

由于 onMessage() 是 JMS 消费的核心回调,只要这个线程没返回,消息就不会 ACK,消费者线程就被占用。

SimpleMessageListenerContainer 默认是同步单线程消费(一个 Session 一个线程),所以这个阻塞会导致该 Session 后续消息无法被处理。

2.问题本质

JMS AUTO_ACKNOWLEDGE 模式 + 消费者同步阻塞 = 消息处理线程被卡死,连接虽然“正常”,但监听逻辑已经卡住。

阻塞发生在你的业务代码里(SMSSendMessage 调用 CompletableFuture.get() 等待外部资源)。

那么应该是报错导致线程阻塞,解决那个错误就好了。

总结:

项目使用的是自动ACK的模式,当MQ线程无异常返回时,即可自动ACK,当消费不用发短信的消息时,即使有异常也会因为try catch而被捕获,所以消息可以正常消费。但发送短信的代码中使用了CompletableFuture,获取结果时会阻塞线程,并且没有设置超时时间,所以内部出现报错时,CompletableFuture阻塞不动,MQ便卡死了。临时办法异步任务加上超时时间,终极办法解决错误。

参考文献:

阿里云发送短信报错Exception in thread “httpclient-dispatch-1“ java.lang.NoSuchMethodError: javax.net.ssl.SSLP_exception in thread "hawtdispatch-default-1-CSDN博客

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐