spring-jms/DefaultMessageListenerContainer配置

一个DefaultMessageListenerContainer可以开启多个 (concurrent)AsyncMessageListenerInvoker并发 收消息

两种模式:

  • 模式一:递增监听线程并调度,监听线程轮询监听消息
  • 模式二:动态调度监听线程(递增/递减),有限轮询+重新触发调度

1. 模式一

递增监听线程并调度,监听线程轮询监听消息。这种模式初始化为concurrent个AsyncMessageListenerInvoker线程然后调度执行,并在接受到消息后适度新增监听线程,直到达到最大值maxConcurrentConsumers。这种模式在高峰期过后线程数量不会主动下降,会一直保持高峰值运行。

配置

  • maxMessagesPerTask < 0
  • concurrency=2-10
  • receiveTimeout=1000*60 (consumer.receive(timeout), 默认值是1秒,如果设置不超时设置为负数即可)

2. 模式二

动态调度监听线程(递增/递减),有限轮询+重新触发调度。这种模式初始化为concurrent个AsyncMessageListenerInvoker线程然后调度执行,并在接受到消息后适度新增监听线程,直到达到最大值maxConcurrentConsumers。这种模式在高峰期过后会主动下降到concurrent数量个线程。

配置

  • maxMessagesPerTask > 0
  • concurrency=2-10
  • receiveTimeout=1000*60 (consumer.receive(timeout), 默认值是1秒,如果设置不超时设置为负数即可)

每个AsyncMessageListenerInvoker在执行了maxMessagesPerTask轮(不区分有没有收到消息)后就会结束本线程,然后交给container确定是否继续调度本线程。

如果使用xml(jms:listener-container)配置, prefetch就是maxMessagesPerTask。如果某个参数没起作用可以看看spring-jmx.jar (org.springframework.jms.config.AbstractListenerContainerParser)源码。

3. 缓存相关配置 

缓存(cacheLevel)级别:

  • NONE=0
  • CONNECTION=1
  • SESSION=2
  • CONSUMER=3
  • AUTO=4

3.1 NONE 不启用缓存

通过分析AsyncMessageListenerInvoker#run源码发现这种模式每次轮询收消息会从连接池获取连接并创建session和consumer, 即1个连接–1个session–1个consumer。每个container对应[0, concurrent]个connnection/session/consumer,每次轮询都会创建/连接池获取和关闭/回收。

3.2 CONNECTION 缓存连接

一个container永久(可以这么说)持有一个connection, 该container下的所有AsyncMessageListenerInvoker共享这同一个连接。每次轮询都会动态创建和关闭session/consumer。

连接异常处理:

假设在轮询的过程中connection中断了,也就是不能正常使用了,怎么办?如果缓存级别是NONE,连接异常了只会影响本次轮询,下次轮询会重新冲连接池获取,连接池来处理异常的连接。看看AsyncMessageListenerInvoker#run的catch部分,有个recoverAfterListenerSetupFailure()#refreshConnectionUntilSuccessful(), 即如果中途异常了会进入恢复模式,只要container不关闭就会一直重试连接服务器,直到连接成功。

3.3 SESSION 缓存session

一个container永久(可以这么说)持有一个connection, 该container下的所有AsyncMessageListenerInvoker共享这同一个连接。每个AsyncMessageListenerInvoker执行期内各自保持一个session,创建后不关闭。
每次轮询都会动态创建和关闭consumer。

3.4 CONSUMER 缓存消费者

一个container永久(可以这么说)持有一个connection, 该container下的所有AsyncMessageListenerInvoker共享这同一个连接。每个AsyncMessageListenerInvoker执行期内各自保持一个session和一个consumer,创建后不关闭。

3.5 AUTO 自动选择

进过查看initialize()代码,如果有事务管理器,则cacheLevel=NONE, 否则cacheLevel=CACHE_CONSUMER

上面的分析相关代码可以看看这里

private void initResourcesIfNecessary() throws JMSException {
    if (getCacheLevel() <= CACHE_CONNECTION) {
        updateRecoveryMarker();
    }
    else {
        if (this.session == null && getCacheLevel() >= CACHE_SESSION) {
            updateRecoveryMarker();
            this.session = createSession(getSharedConnection());
        }
        if (this.consumer == null && getCacheLevel() >= CACHE_CONSUMER) {
            this.consumer = createListenerConsumer(this.session);
            synchronized (lifecycleMonitor) {
                registeredWithDestination++;
            }
        }
    }
}

综上如果有事务管理 + 连接池建议设置为NONE, 否则适当设置缓存级别。

4. 连接数相关分析、计算连接池最大值

1个container, Invoker数量是concurrent个

缓存级别 connection session consumer
NONE [0,concurrent]个,每次获取和回收 [0,concurrent]个,每次创建和关闭 [0,concurrent]个,每次创建和关闭
CONNECTION 共享1个,不回收 [0,concurrent]个,每次创建和关闭 [0,concurrent]个,每次创建和关闭
SESSION 共享1个,不回收 缓存concurrent个 [0,concurrent]个,每次创建和关闭
CONSUMER 共享1个,不回收 缓存concurrent个 缓存concurrent个

假设本实例要消费 m 个队列,即开启了 m 个DefaultMessageListenerContainer,每个container开启的invoker数量是[concurrent,maxConcurrent], 则最多需要连接数量是: m * maxConcurrent。另外可能还需要其他的连接,所及建议设置的比这个值要大一些。

如:

    "queue" container-type="default"
                            connection-factory="jmsFactory" transaction-manager="jmsTransactionManager"
                            receive-timeout="60000" cache="none" acknowledge="transacted"
                            concurrency="${mq.concurrency}" prefetch="20">
        "${app.name}-SEND_SMS" id="MSTContainer_SEND_SMS"  ref="activeMQ"  />
        "${app.name}-LOGIN"               ref="activeMQ" />
        "${app.name}-REGISTER"            ref="activeMQ" />
        "${app.name}-SEND_VOICE_VERIFY_CODE" id="MSTContainer_SEND_VOICE_VERIFY_CODE" ref="activeMQ" />
        "${app.name}-WEICHAT_TEMPLATE_MESSAGE" id="MSTContainer_WEICHAT_TEMPLATE_MESSAGE" ref="activeMQ" />
    </jms:listener-container>

5. 其他配置

  • ActiveMQConnectionFactory.maxThreadPoolSize 这个值会设置到connnection中,即connnection中sessionTaskRunner线程池的最大值,如果是在cacheLeve>=CONNECTION, 该值设置为与maxConcurrent相同即可,否则设置为1。
  • ActiveMQConnectionFactory.prefetchPolicy 这个是设置connnection一次行最多取多少个消息到本地,如果开启了事务建议1或者比较小的值,如果没有开启事务建议设置的大点,提高性能。
  • DefaultMessageListnerContainer.taskExecutor 默认使用的是SimpleAsyncTaskExecutor,这个执行器默认是New Thread, 如果maxConcurrent总量比较大,那在高峰阶段估计会因为创建线程而卡死,应该使用线程池,如org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor。
  • 使用 Advisory Message 监听连接的创建和销毁,队列的生产者和消费者连接和断开等,综合分析消息队列和消费者/订阅者健康程度。
  • Broker如果使用数据库,最好缓存连接池连接,并将开启nio

如果使用tcp连接要慎重设置 ActiveMQConnectionFactory.maxThreadPoolSize(connection的session线程池最大值) 和 PooledConnectionFactory.maxConnections, 假设

  • maxThreadPoolSize=10, 即:每个connection最多同时运行10个session
  • maxConnections=100, 即:最多100个connection(一个connection对应一个TcpTransport,每个TcpTransport是一个线程)

那么在满载的情况下线程峰值至少是 10 * 100 = 1000 , 另外 maxThreadPoolSize 最好设置的与 DefaultMessageListenerContainer.maxConcurrency 相同

TransportThreadSupport.doStart() 会开启线程

TcpTransport extends TransportThreadSupport

protected void doStart() throws Exception {
    connect();
    stoppedLatch.set(new CountDownLatch(1));
    super.doStart();
}

TransportThreadSupport

protected void doStart() throws Exception {
    runner = new Thread(null, this, "ActiveMQ Transport: " + toString(), stackSize);
    runner.setDaemon(daemon);
    runner.start();
}

NIOTransport extends TcpTransport

protected void doStart() throws Exception {
    connect();
    selection.setInterestOps(SelectionKey.OP_READ);
    selection.enable();
}

建议连接池设置:
* 生产者(TCP): 消费队列数量 * 2
* 消费者(NIO): (消费队列数量 + 订阅主题数量) * maxConcurrency

最后调到下面的参数已经满足日常的高峰处理了:

NIO
消费3个队列,每个队列5000消息量
mq.concurrency = 2-10
mq.maxThreadPoolSize=5
mq.maxConnections=60

REF

  • http://blog.csdn.net/kimmking/article/details/8443679
  • http://blog.csdn.net/kimmking/article/details/9773085

你可能感兴趣的:(spring,spring-jms)