序:处理规则概述
在ActiveMQ单个服务节点的优化中,除了对ActiveMQ单个服务节点的网络IO模型进行优化外,生产者发送消息的策略和消费者处理消息的策略也关乎整个消息队列系统是否能够高效工作。请看下图所示的消息生产者和消息消费者的简要工作原理图:
Producer既是消息生产者,作为一个发送消息的客户端它既可以使用同步消息发送模式,也可以使用异步的消息发送模式。另外,消息生产者在ActiveMQ服务节点产生消息堆积的情况下,也不能一味的追求发送效率。还好,这种情况下消息生产者端有完整的保证机制——Slow Producer。另外,JMS提供事务功能,所以生产者是否开启事务发送消息,将会影响消息发送性能;
在整个消息处理规则中,ActiveMQ服务节点最极端的情况就是产生消息堆积(消息的消费速度持续低于消息生成速度,就会出现消息堆积)。那么ActiveMQ服务节点处理网络IO模型的优化外,最大的优化点就是:如何应对消息堆积。基本原则是:NON_PERSISTENT Message在内存堆积后,转储到Temp Store区域(当然也可以设置为不转储);PERSISTENT Meaage无论怎样都会先使用持久化方案存储到永久存储区域,再视情况决定是否进行发送;在这些区域也产生堆积后,通知消息生产者使用Slow Producer机制;
ActiveMQ服务节点在成功完成PERSISTENT Meaage的持久存储操作后,就会向消息生产者发送一个确认信息,表示该条消息已处理完成。如果ActiveMQ服务节点接受的是NON_PERSISTENT Message,那么生产者默认情况下不会等待服务节点的回执。我们后续会介绍PERSISTENT Meaage的发送也可以设置为不等待回执,这样可以显著提高生产端的消息发送效率;
ActiveMQ服务节点会以一种设置好的策略将消息发送给消费者,这个策略的原则是ActiveMQ服务节点主动推送消息给某一个消费者(不是消费者主动拉取),这样的方式很好便于ActiveMQ服务节点占据整个策略的领导地位。在这个策略中,最重要的属性是prefetchSize:单次获得的消息数量。除此以外,消费者端也是可以调整IO网络模型;
消费者在获得到消息后,就可以根据这条消息进行业务处理了。最后,消费者需要按照处理结果向ActiveMQ服务节点告知这条(或这些)消息是否处理成功——ACK应答。ActiveMQ中有多种ACK应答方式,它们对性能的影响也不相同。通过这些描述可以看出,消费者的处理性能更能直接影响整个ActiveMQ系统的性能。因为消费者不仅要接受消息、处理消息还要返回消息应答。所以如果您的业务有一定的复杂性,造成每一条消息的处理都会消耗相当的处理时间,那么最直接的性能改善方式就是采用多个消费者节点。
生产者策略
1、生产者策略:Send
消息生产者发送的消息主要分为两种类型:发送PERSISTENT Meaage和发送NON_PERSISTENT Message。
1.1、发送NON_PERSISTENT Message
默认情况下,ActiveMQ服务端认为生产者端发送的是PERSISTENT Message。所以如果要发送NON_PERSISTENT Message,那么生产者端就要明确指定。以下语句明确指定消息发送者将要发送NON_PERSISTENT Message:
......
// 设置发送者发送的是NON_PERSISTENT Message
MessageProducer sender = session.createProducer(sendQueue);
sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
......
发送NON_PERSISTENT Message时,消息发送方默认使用异步方式:即是说消息发送后发送方不会等待NON_PERSISTENT Message在服务端的任何回执。那么问题来了:如果这时服务端已经出现了消息堆积,并且堆积程度已经达到“无法再接收新消息”的极限情况了,那么消息发送方如何知晓并采取相应的策略呢?
实际上所谓的异步发送也并非绝对的异步,消息发送者会在发送一定大小的消息后等待服务端进行回执(这个配置只是针对使用异步方式进行发送消息的情况):
......
// 以下语句设置消息发送者在累计发送102400byte大小的消息后(可能是一条消息也可能是多条消息)
// 等待服务端进行回执,以便确定之前发送的消息是否被正确处理
// 确定服务器端是否产生了过量的消息堆积,需要减慢消息生产端的生产速度
connectionFactory.setProducerWindowSize(102400);
......
如果您使用的是异步发送方式,那么必须通过这个以上代码指明回执点。例如发送NON_PERSISTENT Message时,这时消息发送者默认使用异步方式。那么如果您想在发送NON_PERSISTENT Message时,每次都等待消息回执,又该如何设置呢?您可以使用connectionFactory提供的“alwaysSyncSend”设置来指定每次都等待服务端的回执:
......
// 设置成:无论怎样每次都等待服务器端的回执
// 但关键是您确定自己的业务需求真的需要这样使用吗?
connectionFactory.setAlwaysSyncSend(true);
......
1.2、发送PERSISTENT Message
如果您不特意指定消息的发送类型,那么消息生产者默认发送PERSISTENT Meaage。这样的消息发送到ActiveMQ服务端后将被进行持久化存储(持久化存储方案将在后文进行详细介绍),并且消息发送者默认等待ActiveMQ服务端对这条消息处理情况的回执。
以上这个过程非常耗时,ActiveMQ服务端不但要接受消息,在内存中完成存储,并且按照ActiveMQ服务端设置的持久化存储方案对消息进行存储(主要的处理时间耗费在这里)。为了提高ActiveMQ在接受PERSISTENT Meaage时的性能,ActiveMQ允许开发人员遵从JMS API中的设置方式,为消息发送端在发送PERSISTENT Meaage时提供异步方式:
......
// 使用异步传输
// 上文已经说过,如果发送的是NON_PERSISTENT Message
// 那么默认就是异步方式
connectionFactory.setUseAsyncSend(true);
......
一旦您进行了这样的设置,就需要设置回执窗口:
......
// 同样设置消息发送者在累计发送102400byte大小的消息后
// 等待服务端进行回执,以便确定之前发送的消息是否被正确处理
// 确定服务器端是否产生了过量的消息堆积,需要减慢消息生产端的生产速度
connectionFactory.setProducerWindowSize(102400);
......
2、生产者策略:事务
JMS规范中支持带事务的消息,也就是说您可以启动一个事务(并由消息发送者的连接会话设置一个事务号Transaction ID),然后在事务中发送多条消息。这个事务提交前这些消息都不会进入队列(无论是Queue还是Topic)。
不进入队列,并不代表JMS不会在事务提交前将消息发送给ActiveMQ服务端。 实际上这些消息都会发送给服务端,服务端发现这是一条带有Transaction ID的消息,就会将先把这条消息放置在“transaction store”区域中(并且带有redo日志,这样保证在收到rollback指令后能进行取消操作),等待这个Transaction ID被rollback或者commit。
一旦这个Transaction ID被commit,ActiveMQ才会依据自身设置的PERSISTENT Meaage处理规则或者NON_PERSISTENT Meaage处理规则,将Transaction ID对应的message进行入队操作(无论是Queue还是Topic)。以下代码示例了如何在生产者端使用事务发送消息:
......
//进行连接
connection = connectionFactory.createQueueConnection();
connection.start();
//建立会话(设置一个带有事务特性的会话)
session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//建立queue(当然如果有了就不会重复建立)
Queue sendQueue = session.createQueue("/test");
//建立消息发送者对象
MessageProducer sender = session.createProducer(sendQueue);
//发送(JMS是支持事务的)
for(int index = 0 ; index < 10 ; index++) {
TextMessage outMessage = session.createTextMessage();
outMessage.setText("这是发送的消息内容-------------------" + index);
// 无论是NON_PERSISTENT message还是PERSISTENT message
// 都要在commit后才能真正的入队
if(index % 2 == 0) {
sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
} else {
sender.setDeliveryMode(DeliveryMode.PERSISTENT);
}
// 没有commit的消息,也是要先发送给服务端的
sender.send(outMessage);
}
session.commit();
......
以上代码中,在“connection.createSession”这个方法中一共有两个参数(这句代码在上文中已经出现过多次)。第一个布尔型参数很好理解,就是标示这个连接会话是否启动事务;第二个整型参数标示了消息消费者的“应答模型”,我们会在下文中进行详细介绍。
3、生产者策略:ProducerFlowControl
生产流控制,是ActiveMQ消息生产者端最为重要的性能策略,它主要设定了在ActiveMQ服务节点在产生消息堆积,并超过限制大小的情况下,如何进行消息生产者端的限流。
具体来说,如果以上情况在ActiveMQ出现,那么当生产者端再次接受ActiveMQ的消息回执时,ActiveMQ就会让消息生产者进入等待状态或者在发送者端直接抛出JMSException。当然您也可以配置ActiveMQ不进行ProducerFlowControl,如果您对自己ActiveMQ服务端的底层性能和消费者端的性能足够自信的话。
在ActiveMQ的主配置文件activemq.xml中,关于ProducerFlowControl策略的控制标签是“destinationPolicy”和它的子标签。请看如下配置示例:
......
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry topic=">" producerFlowControl="false"/>
</policyEntries>
</policyMap>
</destinationPolicy>
......
以上示例配合所有的Topic模式的队列不进行producerFlowControl策略控制。当然还可以为队列配置启用producerFlowControl策略:
......
<policyEntry queue=">" producerFlowControl="true" memoryLimit="200mb">
</policyEntry>
......
以上配置项表示为ActiveMQ中的所有Queue模式的队列启用producerFlowControl策略,并且限制每个Queue信息的最大内存存储限制(memoryLimit)为200MB;这里指的最多使用200MB的内存区域,而不是说Queue中消息的总大小为200MB。例如,在ActiveMQ 5.X+ 版本中NON_PERSISTENT Message会被转出到 temp store区域,所以有可能您观察到的现象是,无论怎样堆积NON_PERSISTENT Message消息,Queue的使用内存始终无法达到200MB。
......
<policyEntry queue=">" producerFlowControl="true" memoryLimit="200mb">
<pendingQueuePolicy>
<vmQueueCursor/>
</pendingQueuePolicy>
</policyEntry>
......
以上配置表示只使用内存存储Queue中的所有消息,特别是NON_PERSISTENT Message只存储在内存中,不使用temp store区域进行转储。在官方文档中,有关于policyEntry标签的所有配置选项都有完整说明:http://activemq.apache.org/per-destination-policies.html。