我想用ActiveMQ代理和非常基本的Stomp客户端实现一个设置,它一次只能处理一条消息(请求 - 回复)。
我在Spring(5.0.0.RELEASE)Boot(1.5.7.RELEASE)+ Apache Camel(2.20.0)JMS应用程序中使用嵌入式ActiveMQ(5.15.1)代理,以及用于Stomp客户端Spring Boot使用Apache Camel的Stomp实现。 “main”应用程序接收HTTP POST,确定相应的负责队列,并使用replyTo
连接器通过JMS(Apache Camel ActiveMQComponent)将POST主体作为InOut(vm://localhost
选项)消息发送到嵌入式代理。 Stomp客户端通过TCP连接到代理,从队列读取请求,并将(相关)响应发送到replyTo
响应队列。最终在关联之后,响应将在HTTP响应中返回给调用者。
为了测试整个设置,我使用SoapUI作为测试驱动程序并在Stomp客户端中设置“假”请求持续时间(以模拟长请求处理时间)。
基本上我想实现以下目标:
我第一次尝试获得所需的行为是在Stomp客户端订阅时更改了prefetchSize
和dispatchAsync
的ActiveMQ使用者的默认行为,如here所述。但是,默认的自动确认似乎在这里出现。由于消息以正确的方式被确认(不确定是什么时候),我几乎从未看到驻留在代理内部的消息(使用Java VisualVM进行检查);这些消息似乎在接收时就被承认了。这基本上可以反对 prefetchSize
。
那么我想使用客户端ACK,实际上是STOMP spec中描述的单个ACK。显然Camel Stomp组件不支持(至少我找不到)。所以我扩展了Apache Camel Stomp组件,以便在交换处理之后发送客户端ACK 。为此我实施并注册了一个定制的org.apache.camel.component.stomp.StompEndpoint
。 ACK似乎工作正常,我也得到了预期的行为,即,一次只有Stomp客户端中只有一个消息,并且因为ACK仅在处理后发送(并且发送的响应),任何并行消息都驻留在代理中,直到(个人)确认。
但不幸的是,现在,当我以高负载测试这个场景时,我在org.apache.activemq.broker.region.PrefetchSubscription
中遇到了死锁:
Found one Java-level deadlock:
=============================
"ActiveMQ Transport: tcp:///127.0.0.1:64896@25201":
waiting to lock monitor 0x0000000026153878 (object 0x00000006c4dc0d90, a java.lang.Object),
which is held by "ActiveMQ BrokerService[localhost] Task-2"
"ActiveMQ BrokerService[localhost] Task-2":
waiting for ownable synchronizer 0x00000006c4db37f8, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "ActiveMQ Transport: tcp:///127.0.0.1:64896@25201"
Java stack information for the threads listed above:
===================================================
"ActiveMQ Transport: tcp:///127.0.0.1:64896@25201":
at org.apache.activemq.broker.region.PrefetchSubscription.dispatchPending(PrefetchSubscription.java:632)
- waiting to lock <0x00000006c4dc0d90> (a java.lang.Object)
at org.apache.activemq.broker.region.PrefetchSubscription.acknowledge(PrefetchSubscription.java:394)
at org.apache.activemq.broker.region.AbstractRegion.acknowledge(AbstractRegion.java:528)
at org.apache.activemq.broker.region.RegionBroker.acknowledge(RegionBroker.java:475)
at org.apache.activemq.broker.BrokerFilter.acknowledge(BrokerFilter.java:89)
at org.apache.activemq.broker.BrokerFilter.acknowledge(BrokerFilter.java:89)
at org.apache.activemq.broker.TransactionBroker.acknowledge(TransactionBroker.java:276)
at org.apache.activemq.broker.BrokerFilter.acknowledge(BrokerFilter.java:89)
at org.apache.activemq.broker.TransportConnection.processMessageAck(TransportConnection.java:581)
at org.apache.activemq.command.MessageAck.visit(MessageAck.java:245)
at org.apache.activemq.broker.TransportConnection.service(TransportConnection.java:330)
at org.apache.activemq.broker.TransportConnection$1.onCommand(TransportConnection.java:194)
at org.apache.activemq.transport.MutexTransport.onCommand(MutexTransport.java:45)
at org.apache.activemq.transport.AbstractInactivityMonitor.onCommand(AbstractInactivityMonitor.java:301)
at org.apache.activemq.transport.stomp.StompTransportFilter.sendToActiveMQ(StompTransportFilter.java:97)
at org.apache.activemq.transport.stomp.ProtocolConverter.sendToActiveMQ(ProtocolConverter.java:202)
at org.apache.activemq.transport.stomp.ProtocolConverter.onStompAck(ProtocolConverter.java:456)
at org.apache.activemq.transport.stomp.ProtocolConverter.onStompCommand(ProtocolConverter.java:250)
at org.apache.activemq.transport.stomp.StompTransportFilter.onCommand(StompTransportFilter.java:85)
at org.apache.activemq.transport.TransportSupport.doConsume(TransportSupport.java:83)
at org.apache.activemq.transport.tcp.TcpTransport.doRun(TcpTransport.java:233)
at org.apache.activemq.transport.tcp.TcpTransport.run(TcpTransport.java:215)
at java.lang.Thread.run(Thread.java:748)
"ActiveMQ BrokerService[localhost] Task-2":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c4db37f8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at org.apache.activemq.transport.MutexTransport.oneway(MutexTransport.java:66)
at org.apache.activemq.broker.TransportConnection.dispatch(TransportConnection.java:1486)
at org.apache.activemq.broker.TransportConnection.processDispatch(TransportConnection.java:971)
at org.apache.activemq.broker.TransportConnection.dispatchSync(TransportConnection.java:927)
at org.apache.activemq.broker.region.PrefetchSubscription.dispatch(PrefetchSubscription.java:742)
at org.apache.activemq.broker.region.PrefetchSubscription.dispatchPending(PrefetchSubscription.java:664)
- locked <0x00000006c4dc0da0> (a java.lang.Object)
- locked <0x00000006c4dc0d90> (a java.lang.Object)
at org.apache.activemq.broker.region.PrefetchSubscription.add(PrefetchSubscription.java:159)
at org.apache.activemq.broker.region.Queue.doActualDispatch(Queue.java:2121)
at org.apache.activemq.broker.region.Queue.doDispatch(Queue.java:2069)
at org.apache.activemq.broker.region.Queue.pageInMessages(Queue.java:2210)
at org.apache.activemq.broker.region.Queue.iterate(Queue.java:1644)
- locked <0x00000006c4dc21c0> (a java.lang.Object)
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)
Found 1 deadlock.
有趣的是,如果我不在“main”应用程序中使用连接池,那么就不会出现此死锁,即与代理的vm://localhost
连接。但是这会为发送给代理的每条消息创建一个新的生产者,并且速度极慢。使用带有池连接的org.apache.activemq.ActiveMQConnectionFactory
工厂会大大加快速度,但会导致死锁。死锁也出现在上面提到的第一个负载测试中,其中没有并行请求。
(嵌入式)代理设置:
@Bean
public BrokerService embeddedBroker() throws Exception {
final BrokerService embeddedBroker = new BrokerService();
embeddedBroker.addConnector("stomp://0.0.0.0:" + this.configuration.getBrokerStompPort());
return embeddedBroker;
}
@Bean(initMethod = "start", destroyMethod = "stop")
public PooledConnectionFactory activeMQPooledConnectionFactory() {
final ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
"vm://localhost?create=false");
final PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(connectionFactory);
return pooledConnectionFactory;
}
@Bean
@DependsOn("embeddedBroker")
public ActiveMQComponent embeddedBrokerConnection() {
final JmsConfiguration jmsConfiguration = new JmsConfiguration(this.activeMQPooledConnectionFactory());
final ActiveMQComponent brokerConnection = new ActiveMQComponent();
brokerConnection.setConfiguration(jmsConfiguration);
this.camelContext.addComponent("activemq", brokerConnection);
return brokerConnection;
}
现在的具体问题是:
vm
连接有问题,因为只有连接池出现死锁?replyTo
队列上的响应引起的(我没有在这里粘贴自定义,“问题”已经太长了)?