Spring JMS侦听器 - 容器并发属性不起作用

时间:2015-10-26 05:50:44

标签: java spring concurrency activemq spring-jms

您好我正在使用ActiveMQ学习Spring JMS。 在我的示例场景中,Producer应用程序在队列中发送大约50条消息,当我启动Consumer应用程序时,它开始使用这些消息。

现在我希望多个消费者线程使用来自队列的消息。 我正在使用 JMS侦听器容器。当我用Google搜索时,我发现有一个并发属性。

根据Spring JMS doc并发属性指定

  

为每个侦听器启动的并发会话/使用者数。可以是表示最大数字的简单数字(例如“5”)或表示较低数字和上限的范围(例如“3-5”)。请注意,指定的最小值只是一个提示,可能在运行时被忽略。默认值为1;在主题监听器或队列排序很重要的情况下,将并发性限制为1;考虑将其提升为一般队列。

但在我的配置中,我将此属性设置为5,但似乎无法启动5个并发侦听器。

侦听器的配置:

消费者的applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jms="http://www.springframework.org/schema/jms"

    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/jms
    http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory" p:brokerURL="tcp://localhost:61616" />

    <bean id="listener" class="com.jms.example.MyMessageListener"></bean>

    <jms:listener-container container-type="default" concurrency="5"
        connection-factory="connectionFactory">
        <jms:listener destination="MyQueue" ref="listener"
            method="onMessage"></jms:listener>
    </jms:listener-container>

</beans>

如果我使用bean DefaultMessageListenerContainer 而不是 jms:listener-container 并使用属性:

<bean id="msgListenerContainer" 
        class="org.springframework.jms.listener.DefaultMessageListenerContainer"
        p:connectionFactory-ref="connectionFactory"
        p:destination-ref="destination"
        p:messageListener-ref="listener"
        p:concurrentConsumers="10"
        p:maxConcurrentConsumers="50" />

然后在ActiveMQ控制台中我可以看到10个消费者,但实际上它同时启动了3个消费者,有时甚至是6个消费者或者只有1个消费者。

修改

消费者代码:

public class MyMessageListener implements MessageListener{


    public void onMessage(Message m) {
        TextMessage message=(TextMessage)m;
        try{
            System.out.println("Start = " + message.getText());
            Thread.sleep(5000);
            System.out.println("End = " + message.getText());
        }catch (Exception e) {e.printStackTrace();  }
    }
}

我在控制台上打印消费消息,其输出在下面的场景中解释:

观察:

我发现了一些奇怪的行为。 我的制作人和消费者是两个独立的应用。

情景 - 1:

  1. 我启动制作人并发送消息(同时消费者没有运行)
  2. 然后我开始消费者消费消息。
  3. 这里的问题是它不会加载所有10个消费者。有时加载3或1。

    Start = hello jms 1 // consumer 1 started 
    Start = hello jms 2 // consumer 2 started 
    Start = hello jms 3 // consumer 3 started 
    End = hello jms 1  //  consumer 1 ended
    Start = hello jms 4 // consumer 4 started and hence always 3 consumers and not 10
    End = hello jms 2
    Start = hello jms 5
    End = hello jms 3
    Start = hello jms 6
    

    情景 - 2:

    1. 我启动制作人并发送消息(同时消费者正在运行)
    2. 由于消费者处于运行状态,因此开始消费它们。
    3. 所以它确实按预期正确加载了所有5个消费者。所以输出是:

      Start = hello jms 1 // consumer 1 started 
      Start = hello jms 2 // consumer 2 started 
      Start = hello jms 3 // consumer 3 started 
      Start = hello jms 4 // consumer 4 started 
      Start = hello jms 5 // consumer 5 started 
      Start = hello jms 6 // consumer 6 started 
      Start = hello jms 7 // consumer 7 started 
      Start = hello jms 8 // consumer 8 started 
      Start = hello jms 9 // consumer 9 started 
      Start = hello jms 10 // consumer 10 started. Hence all them started at same time as expected.
      End = hello jms 1
      Start = hello jms 11
      End = hello jms 2
      Start = hello jms 12
      End = hello jms 3
      Start = hello jms 13
      

      为什么会这样。它真的在吃我的大脑。 我不想让消费者永远奔跑。我想保持两个分离。

      请帮忙。

3 个答案:

答案 0 :(得分:0)

Strelok向我指出了预取消息的方法。创建prefetchPolicy bean,queuePrefetch属性设置为1。 在connectionFactory中设置了哪个引用。

我在配置上做了一些更改,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jms="http://www.springframework.org/schema/jms"

    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/jms
    http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory" p:brokerURL="tcp://localhost:61616"
        p:prefetchPolicy-ref="prefetchPolicy" />

    <bean id="prefetchPolicy" class="org.apache.activemq.ActiveMQPrefetchPolicy"
        p:queuePrefetch="1" />

    <bean id="listener" class="com.javatpoint.MyMessageListener"></bean>

    <jms:listener-container concurrency="10-15" connection-factory="connectionFactory">
        <jms:listener destination="javatpointQueue" ref="listener"
            method="onMessage"></jms:listener>
    </jms:listener-container>

    <!-- The JMS destination -->

      <bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="javatpointQueue" />
      </bean>
</beans>

答案 1 :(得分:0)

JMS可以在并发模式下工作。下面我分享了示例代码段 concurrentConsumers = 100值

Spring JMS Documentation

<bean id="listenerContainer"        class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="concurrentConsumers">
            <value>100</value>
        </property>
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="destination" ref="queue" />
        <property name="messageListener" ref="messageListener" />
        <property name="sessionTransacted" value="false" />
        <property name="sessionAcknowledgeMode" value="1" />
    </bean>

答案 2 :(得分:0)

刚刚在spring-boot 1.5.9应用程序中遇到了这个问题。

正如@Strelok和@mahendra kawde所指出的,问题是由prefetchPolicy参数引起的。默认值为1000。

  

对于具有高消息量的高性能,建议使用较大的预取值。但是,对于较低的消息量,每个消息需要很长时间才能处理,预取应设置为1.这可确保消费者一次只处理一条消息。但是,将预取限制指定为零将导致使用者一次一个地轮询消息,而不是将消息推送到消费者。

查看http://activemq.apache.org/what-is-the-prefetch-limit-for.html

可以更改prefetchPolicy参数,如下所示:

  1. application.properties档案(working example

    spring.activemq.broker-url=tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=1
    
  2. 在DefaultMessageListenerContainer中修改destinationName参数(working example

    <bean id="cons-even" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
      <property name="destinationName" value="queue-name?consumer.prefetchSize=1"/>
      ...
    </bean>
    
  3. 在ConnectionFactory bean(working example)中:

    @Bean
    public ConnectionFactory jmsConnectionFactory() {
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
        ActiveMQPrefetchPolicy policy = new ActiveMQPrefetchPolicy();
        policy.setQueuePrefetch(1);
        factory.setPrefetchPolicy(policy);
        return factory;
    }
    
  4. 相关主题:

    1. How do I make Spring JMSListener burst to max concurrent threads?
    2. Dynamic scaling of JMS consumer in spring boot