彻底关闭Spring集成(使用RabbitMQ)

时间:2017-08-23 16:29:01

标签: spring-integration spring-rabbitmq

我正在使用:Spring Boot 1.4.7,Spring Integration 4.3.10,RabbitMQ 3.6.5

我有一个Spring Boot应用程序,它有几个Spring Integration流程,可以向rabbitMQ代理发送和接收消息。

我遇到的问题是,当调用“关闭”执行器时,应用程序并不总是干净地关闭。

执行线程转储后,我可以看到单个“SimpleMessageListenerContainer”线程在对代理的“发送”操作中被阻止:

"org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer#0-1" #81 prio=5 os_prio=0 tid=0x00007fe49bcac800 nid=0x4fc5 waiting on condition [0x00007fe489efe000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000006c259a6f8> (a java.util.concurrent.SynchronousQueue$TransferStack)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:458)
    at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362)
    at java.util.concurrent.SynchronousQueue.put(SynchronousQueue.java:877)
    at org.springframework.integration.channel.QueueChannel.doSend(QueueChannel.java:93)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:105)
    at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutput(AbstractMessageProducingHandler.java:358)
    at org.springframework.integration.handler.AbstractMessageProducingHandler.produceOutput(AbstractMessageProducingHandler.java:269)
    at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutputs(AbstractMessageProducingHandler.java:186)
    at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:115)
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:127)
    at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:148)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:121)
    at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:89)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:423)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:373)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:105)
    at org.springframework.integration.endpoint.MessageProducerSupport.sendMessage(MessageProducerSupport.java:188)
    at org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter.access$1100(AmqpInboundChannelAdapter.java:56)
    at org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter$Listener.processMessage(AmqpInboundChannelAdapter.java:246)
    at org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter$Listener.onMessage(AmqpInboundChannelAdapter.java:203)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:823)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:746)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:99)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:191)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1238)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:727)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1192)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:1176)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1100(SimpleMessageListenerContainer.java:99)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1370)
    at java.lang.Thread.run(Thread.java:745)

根据Gary的反馈更新了信息:

我回顾了所有的频道定义(我们使用Spring集成DSL),其中大部分定义如下:

public MessageChannel channelIdMailOut() {
    return MessageChannels.direct().get();
}

然而,我确实发现了几个异常值:

@Bean(name=CHANNEL_NAME_ID_MAIL_IN)
public MessageChannel channelIdMailIn() {
    //Using a rendezvous channel on inbound because we use a rest endpoint to pull messages rather than using a push model
    return MessageChannels.rendezvous().get();
}
@Bean(name=CHANNEL_NAME_CATEGORY_REFRESH_PRODUCTION_OUT)
public MessageChannel channelCategoryRefreshProductionOut() {
    return MessageChannels.publishSubscribe().get();
}

我很欣赏快速反馈,我将进一步探索这条途径。

2 个答案:

答案 0 :(得分:2)

  

at org.springframework.integration.channel.QueueChannel.doSend(QueueChannel.java:93)

看起来您正在使用入站通道适配器下游的有界QueueChannel ...

adapter->DirectChannel->someEndpoint->QueueChannel<-somePoller

......并且队列已满。因为你正在停止上下文;从队列中读取的轮询线程永远不会释放空间。

您没有显示您的配置,但您可以在发送到sendTimeout的端点上设置QueueChannel,并且发送将超时。

但是,在这种情况下使用QueueChannel通常不是一个好主意,除非您不介意消息丢失。关闭时队列中的消息将丢失。

修改

在下面回答你的评论。

有几种选择......

  1. 向发送到RendezvousChannel的端点添加发送超时;它应该小于入站适配器的侦听器容器上的shutDownTimeout
  2. 在amqp入站适配器中使用外部taskExecutor并使用executor.shutDownNow()(或setWaitForTasksToCompleteOnShutdown(false)作为Spring执行程序),这将中断尝试发布RC的线程 - 这可能会导致日志中存在一些噪音,因为容器会尝试重新启动使用者线程。
  3. 在关闭执行程序之前手动stop()入站通道适配器(应该避免日志噪声)。
  4. 添加IntegrationMBeanExporter并在超时时调用stopActiveComponents(),以便让事情停顿。
  5. 对于3和4,如果你可以将spring-rabbit版本提升到1.7.3,你可以使用ApplicationListemer<AsyncConsumerStoppedEvent>来获得容器线程终止的通知。

答案 1 :(得分:0)

只是跟进对我有用的解决方案。选项#2就像一个魅力。我创建了一个新的任务执行器,并将waitForTasksToCompleteOnShutdown设置为“false”。加里,非常感谢你的帮助!

@Bean
public TaskExecutor taskExecutorIdMailIn(
        @Value("${taskExecutor.idMailIn.corePoolSize:4}") int corePoolSize,
        @Value("${taskExecutor.idMailIn.maxPoolSize:4}") int maxPoolSize, 
        @Value("${taskExecutor.idMailIn.queueCapacity:0}") int queueCapacity) {

    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setThreadNamePrefix("taskExecutorIdMailIn-");
    taskExecutor.setCorePoolSize(corePoolSize);
    taskExecutor.setMaxPoolSize(maxPoolSize);
    if (queueCapacity > 0) {
        taskExecutor.setQueueCapacity(queueCapacity);
    }
    taskExecutor.setRejectedExecutionHandler(new CallerRunsPolicy());
    taskExecutor.setWaitForTasksToCompleteOnShutdown(false);
    return taskExecutor;
}   

@Bean
public IntegrationFlow flowRabbitToIdMailIn(ConnectionFactory factory, @Qualifier("taskExecutorIdMailIn") TaskExecutor taskExecutor) {
    return IntegrationFlows
            .from(Amqp.inboundAdapter(factory, queueNameIdMail)
                    .taskExecutor(taskExecutor)
                    .errorHandler(errorHandler)
            )
            .transform(Transformers.fromJson())
            .channel(CHANNEL_NAME_ID_MAIL_IN)
            .get();
}