记一次ActiveMQ正常连接,消费者接收不到消息问题排查
container项目使用的是自动ACK的模式,当MQ线程无异常返回时,即可自动ACK,当消费不用发短信的消息时,即使有异常也会因为try catch而被捕获,所以消息可以正常消费。但发送短信的代码中使用了CompletableFuture,获取结果时会阻塞线程,并且没有设置超时时间,所以内部出现报错时,CompletableFuture阻塞不动,MQ便卡死了。临时办法异步任务加上超时时间,终极办
项目背景:
生产者生产消息到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便卡死了。临时办法异步任务加上超时时间,终极办法解决错误。
参考文献:
更多推荐
所有评论(0)