ActiveMQ中Consumer特性详解与优化

本文深入探讨ActiveMQ Consumer的特性,包括消息消费模型、session运作机制、优化性能的方法,如asyncDispatch、alwaysSessionAsync、maxThreadPoolSize等参数的调整。还涉及selector、group、exclusiveConsumer对消息消费的影响,以及Priority、Slow Consumer处理和消息确认策略。通过理解这些特性,可以有效优化ActiveMQ的消费者性能和消息处理效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

    从本文中你可以了解到如下内容:

    1) consumer端消息消费的模型,session的运作机制

    2) 如果提升broker和consumer端消息消费的速率

    3) selector,group,exclusive对消息消费的影响

    4) 如何让Priority更好的运行,提高消息的顺序性

    5) Slow Consumer的产生原因,以及如何调优。

 

 

    Consumer作为ActiveMQ的消费端,开起来简单,不过还有很多隐藏的特性,值得我们去探索和调优。

    如下为典型的Consumer端代码示例

String brokerUrl = "tcp://localhost:61616?"
String queueName = "test-queue";
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrl);
ActiveMQConnection connection = (ActiveMQConnection)factory.createConnection();
final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination queue = session.createQueue(queueName);
MessageConsumer consumer = session.createConsumer(queue);
consumer.setMessageListener(new MessageListener() {
	@Override
	public void onMessage(Message message) {
		try{
			ActiveMQMessage m = (ActiveMQMessage)message;
			System.out.println(m.toString());
		}catch(Exception e){
			//
			e.printStackTrace();
		}
	}
});
connection.start();

//connection.close();

 

    Consumer端涉及到众多的特性,其中包括:消息转发机制,session机制与多线程,消息ACK策略等,我们无法详解所有的原理,如下为Consumer端消息转发机制,仅供参考,[详细请参考]



 

    上图非常复杂,具体的原理,我们将会在下文中逐个解释。大概原理与过程:

 

  • 通过Connection实例创建Session之后,将会把session实例保存在本地的list中,即connection持有session列表,并在底层开启transport,侦听数据。
  • Session创建Consumer之后,将会把Consumer实例添加到本地的list中,以便此后分拣消息,即session持有consumer列表;此外connection中也持有一个consumer集合(Map),其中Key为consumerId,value为session引用。
  • 如果Session支持异步转发(asyncDispatch)或者使用了转发池(dispatchPool),将创建线程池用来转发消息。
  • 当broker端有消息通过transport发送时,connection将会分拣消息,根据消息中指定的consumerId,从本地session列表中获取其对应的session实例;然后将消息交付session负责转发。
  • 当session接受到消息后,会根据消息的consumerId,在本地consumer列表中找到对应的consumer实例;检测session的消息转发方式,如果是同步转发,则直接将消息交给consumer,即调用consumer.dispatch(message),此方法要么调用messageListener.onMessage(),要么将消息添加到本地的unconsumedMessages队列中(唤醒receive);如果是异步转发,则将消息添加到session级别的队列中并由线程池负责转发。
  • 当consumer接受到消息之后,将会调用messageListener.onMessage方法或者从receive方法中返回。
  • 当消息消费成功后,consumer将会根据指定的ACK_MODE负责向broker发送ACK指令,此后消息将会在broker端清除。

    (broker运行期间,当Consumer加入时,broker将会根据Consumer消费的Destination和相关配置信息,创建Queue或者Topic实例(Broker端,如果已经创建,则直接将Conusmer注册在Queue的消费者列表中),每个Destination实例都会引用Store实例,且Queue和Topic本身是一个Task,将会被运行在Broker的线程池中;这意味着Queue和Topic将可以根据Consumers的能力,并发的从Store中获取属于自己的消息列表,并转发给Consumers;消息从Store中Pagin到Queue内部的buffer列表中,并逐个遍历Consumers将消息转发;如果Consumers为异步转发,则添加到Consumer内部的buffer中,并通过Consumer内部的线程从buffer中移除,通过底层Transport发送) 

1. asyncDispatch

    broker端是否允许使用“异步转发”。broker端处理connection、session、consumer的模型和Client端几乎一样;当通道中(Queue/Topic)有消息需要转发给consumer时,将会调用相应的Subscription的dispatch方法(在broker端负责与Consumer通讯的服务,称为Subscription,每个Consumer客户端对应broker端一个Subscription实例);如果asyncDispatch为false,那么将会阻塞转发线程(dispatchThread),直到底层的Transport将消息发送到Consumer客户端(如果底层transport正在发送消息,意味着阻塞);如果为true,则会将消息添加到transport本地的队列中(负责与Consumer客户端通信的每个transport都有一个本地的queue和线程池),并启动线程池负责发送消息,那么转发线程即可立即返回。(每个Consumer一个异步转发线程)

    (参见代码:QueueStoreCursor/TopicStoreCursor --> broker端Queue/Topic(核心类)--->QueuePrefetchSubscription,QueueRegion,AbstractRegion,RoundRobinDispatchPolicy/StrictOrderDispatchPolicy)

    

    由此可见,当前通道中所有的consumer都在brorker端开启了“异步转发”,可以大大提升broker转发消息的性能,此值默认为true。

 

//在brokerUrl中设置
tcp://localhost:61616?jms.dispatchAsync=true
//在destinationUri中为当前通道设定
orderQueue?consumer.dispatchAsync=true

 

     broker“异步转发”采用的是线程池模式,线程池的缺点即使当任务单元运行时间足够短时,线程切换的成本将变得很重要,这也意味着,如果通道中(Queue/Topic)中的consumer客户端接收和消费消息的速度很快,我们可以关闭异步转发来避免线程切换。如果consumer通常为慢速(Slower Consumer),且关闭了异步转发,那就意味着每次主线程转发消息时,都需要阻塞较长的时间,这极大的影响了整体的通讯效率。无论如何,我们都没有理由关闭异步转发,异步转发应该是最佳选择。

 

    备注:broker端Queue/Topic(例如:org.apache.activemq.broker.region.Queue)实例,负责从底层store中获取消息,并使用线程池模式转发消息,dispatchPolicy负责将消息转发给相应的subscription,当Consumer客户端创建时,会向Broker发送一个指令,这个指令中包括consumerId(参看ConsumerInfo),那么此后Region(在逻辑上相当于客户端的Session,但每个通道只有一个实例)将会为每个ConsumerId创建相应Subscription实例(持有底层Transport,buffer队列、异步发送的线程),那么此后Consumer与Broker的数据交互,就有Subscription负责。每个Region都维护了一个Subscription列表,用于dispatchPolicy转发消息。这个设计模型,和Consumer客户端 + Session非常相似。

 

2. alwaysSessionAsync

    客户端session是否使用异步转发,它和(1)中的参数没有任何关系,当底层Transport接收到Broker发送的消息后,会交付给session,那么session是否采用异步的方式将消息传递给Consumer!此值默认为true。

    如果为false,表示使用同步。当consumer使用receive()获取消息时,那么session将会把消息添加到consumer的本地queue,然后唤醒receive等待;当consumer使用messageListener异步侦听消息时,将会调用其onMessage()方法直到方法执行完毕,然后返回。

    如果为true,表示使用异步。session接受的消息,将会首先放入session buffer中(队列),那么此后的线程池将会负责移除buffer中的消息,并转发给相应的consumer。

 

    当session中有多个consumer时,或者Transport中消息量比较密集,异步方式是最佳的。如果session中只有一个consumer,或者transport中消息量很少,使用异步并不能明显的提升性能。

 

//在brokerUrl中设置
tcp://localhost:61616?jms.alwaysSessionAsync=true

 

3. maxThreadPoolSize

    在alwaysSessionAsync为true的情况下,会引入一个线程池,那么这个线程池中线程的个数,默认为Integer.MAX_VALUE,不过我们首先需要清楚,这个线程池是Connection内所有的session共享的。你可以指定合适的值来决定线程池的大小。

 

//在brokerUrl中设置
tcp://localhost:61616?jms.maxThreadPoolSize=64

 

4. useDedicatedTaskRunner

    当前connetion下的每个session是否使用单独的线程。在上述介绍中,我知道了异步转发是使用了线程池的,而且这个线程池是当前Connection下所有的session共享,尽管这不会带来任何问题,可是有时候我们可能期望在threadLocal或者基于当前线程绑定一些特殊的数据,那么我们就可以使用useDedicatedTaskRunner=true;这就意味着,每个session都将创建一个单独的线程,整个生命周期中都使用它来转发消息。

 

    当每个session只有一个Consumer,或者有多个consumer但它们的处理业务类似(比如selector具有一定逻辑关系),或者这些consumer消费速度都很快,或者我们期望在Consumer中使用类似于Thread.sleep(long)这样的方式来阻塞session转发消息时,这个参数可以帮助我们。

 

//brokerUrl中,默认为false
tcp://localhost:61616?jms.useDedicatedTaskRunner=true

  

5. redeliveryPolicy

    consumer使用的重发策略,当消息在client端处理失败(比如onMessage方法抛出异常,事务回滚等),将会触发消息重发。对于Brok

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值