I have a Spring integration app, attached to an AMQP broker.
I want to receive messages from an amqp-queue, and update db records.
In order to improve performance, I have a pool of workers allowing multiple updates to occur concurrently.
I have the following configuration:
<int-amqp:inbound-channel-adapter queue-names="pricehub.fixtures.priceUpdates.queue"
channel="pricehub.fixtures.priceUpdates.channel"
message-converter="jsonMessageConverter"/>
<int:channel id="pricehub.fixtures.priceUpdates.channel">
<int:queue />
</int:channel>
<int:service-activator ref="updatePriceAction"
method="updatePrices"
input-channel="pricehub.instruments.priceUpdates.channel">
<int:poller fixed-delay="50" time-unit="MILLISECONDS" task-executor="taskExecutor" />
</int:service-activator>
<task:executor id="taskExecutor" pool-size="5-50" keep-alive="120" queue-capacity="500"/>
If I start this running, with no inbound messages to process on the AMQP channel, I quickly see the thredpool get exhausted, and start rejecting.
Here's the logs:
[Thu Apr 2013 23:41:51.153] DEBUG [] (org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:185) - Retrieving delivery for Consumer: tag=[amq.ctag-w4qPp60jVEQOIEovR4cERv], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,1), acknowledgeMode=AUTO local queue size=0
[Thu Apr 2013 23:41:51.160] DEBUG [] (org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:185) - Retrieving delivery for Consumer: tag=[amq.ctag-Q3Lq4R9g9E8WBNVLYzaFmq], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,2), acknowledgeMode=AUTO local queue size=0
[Thu Apr 2013 23:41:51.166] DEBUG [] (org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:185) - Retrieving delivery for Consumer: tag=[amq.ctag-w8bg7ltEV2mot8QXDPCmfK], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,3), acknowledgeMode=AUTO local queue size=0
[Thu Apr 2013 23:41:51.170] DEBUG [] (org.springframework.amqp.rabbit.listener.BlockingQueueConsumer:185) - Retrieving delivery for Consumer: tag=[amq.ctag-A-0KdqhFjpc-Hvjmv7aZAc], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,4), acknowledgeMode=AUTO local queue size=0
[Thu Apr 2013 23:41:51.180] DEBUG [] (org.springframework.integration.endpoint.PollingConsumer:71) - Received no Message during the poll, returning 'false'
[Thu Apr 2013 23:41:51.180] DEBUG [] (org.springframework.integration.endpoint.PollingConsumer:71) - Received no Message during the poll, returning 'false'
[Thu Apr 2013 23:41:51.199] DEBUG [] (org.springframework.integration.endpoint.PollingConsumer:71) - Received no Message during the poll, returning 'false'
[Thu Apr 2013 23:41:51.200] DEBUG [] (org.springframework.integration.endpoint.PollingConsumer:71) - Received no Message during the poll, returning 'false'
[Thu Apr 2013 23:41:51.220] DEBUG [] (org.springframework.integration.endpoint.PollingConsumer:71) - Received no Message during the poll, returning 'false'
Pretty quickly, the thread pool starts rejecting the executions:
[Thu Apr 2013 23:47:15.363] ERROR [] (org.springframework.integration.handler.LoggingHandler:126) - org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@6ff3cb0e] did not accept task: org.springframework.integration.util.ErrorHandlingTaskExecutor$1@78615c8b
at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:244)
at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:49)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.run(AbstractPollingEndpoint.java:231)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:98)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:206)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:680)
Caused by: java.util.concurrent.RejectedExecutionException
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1768)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:767)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:658)
at org.springframework.sched
uling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:241) ... 12 more
I suspect that the culprit lies here: BlockingQueueConsumer
- indicating that each poll for a message blocks the thread until a message arrives ... leading to the threadpool being exhausted quickly.
What's the correct way to configure this?
Rather than using a QueueChannel
and poller, why not simply increase the concurrent-consumers
attribute on the inbound adapter?
<xsd:attribute name="concurrent-consumers" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify the number of concurrent consumers to create. Default is 1.
Raising the number of concurrent consumers is recommended in order to scale the consumption of messages coming in
from a queue. However, note that any ordering guarantees are lost once multiple consumers are registered. In
general, stick with 1 consumer for low-volume queues.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
And, remove the <queue/>
and <poller/>
.
Also, I always recommend including the thread name in the log (%t
for log4J); it makes it easier to debug threading issues.
EDIT:
With the poller, the reason you are running out of threads is the poller has a default receive-timeout
of 1 second. You are scheduling a thread every 50ms, but each then waits in the QueueChannel
for 1 second. Eventually your task queue fills up.
To avoid this, simply set the receive-timeout
to 0
on the <poller/>
if you wish to continue with this technique - but using higher concurrency in the adapter is more efficient because there's no polling or handover to another thread.
It appears I needed a bridge to map between the amqp-inbound queue (which is a pub/sub style queue), and a queue-channel.
<int-amqp:inbound-channel-adapter queue-names="pricehub.fixtures.priceUpdates.queue"
channel="pricehub.fixtures.priceUpdates.subpub"
message-converter="jsonMessageConverter"/>
<int:publish-subscribe-channel id="pricehub.fixtures.priceUpdates.subpub" />
<int:bridge input-channel="pricehub.fixtures.priceUpdates.subpub"
output-channel="pricehub.fixtures.priceUpdates.channel" />
<int:channel id="pricehub.fixtures.priceUpdates.channel">
<int:queue />
</int:channel>
<int:service-activator ref="updatePriceAction"
method="updatePrices"
input-channel="pricehub.instruments.priceUpdates.channel">
<int:poller fixed-delay="50" time-unit="MILLISECONDS" task-executor="taskExecutor" />
</int:service-activator>
<task:executor id="taskExecutor" pool-size="5-50" keep-alive="120" queue-capacity="500"/>
This seems like a LOT of code to achieve a fairly trivial task - so if anyone has better solutions, or suggestions for improvements, I'd love to see them.
来源:https://stackoverflow.com/questions/15811831/spring-integration-queue-poller-seems-to-exhaust-threadpool-without-any-action