(接上文《架构设计:系统间通信(22)——提高ActiveMQ工作性能(上)》)
6、ActiveMQ处理规则和优化
在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系统的性能。因为消费者不仅要接受消息、处理消息还要返回消息应答。所以如果您的业务有一定的复杂性,造成每一条消息的处理都会消耗相当的处理时间,那么最直接的性能改善方式就是采用多个消费者节点。
6-1、生产者策略:Send
消息生产者发送的消息主要分为两种类型:发送PERSISTENT Meaage和发送NON_PERSISTENT Message。
6-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);
......
6-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);
......
6-2、生产者策略:事务
JMS规范中支持带事务的消息,也就是说您可以启动一个事务(并由消息发送者的连接会话设置一个事务号Transaction ID)&#