JMS 消费者研究

optimizeACK和prefetch配合,将会达成一个高效的消息消费模型:批量获取消息,并“延迟”确认(ACK)。prefetch表达了“批量获取”消息的语义,broker端主动的批量push多条消息给client端,总比client多次发送PULL指令然后broker返回一条消息的方式要优秀很多,它不仅减少了client端在获取消息时阻塞的次数和阻塞的时间,还能够大大的减少网络开支。optimizeACK表达了“延迟确认”的语义(ACK时机),client端在消费消息后暂时不发送ACK,而是把它缓存下来(pendingACK),等到这些消息的条数达到一定阈值时,只需要通过一个ACK指令把它们全部确认;这比对每条消息都逐个确认,在性能上要提高很多。由此可见,prefetch优化了消息传送的性能,optimizeACK优化了消息确认的性能。
当consumer端消息消费的速率很高(相对于producer生产消息),而且消息的数量也很大时(比如消息源源不断的生产),我们使用optimizeACK + prefetch将会极大的提升consumer的性能。不过反过来:

1) 如果consumer端消费速度很慢(对消息的处理是耗时的),过大的prefetchSize,并不能有效的提升性能,反而不利于consumer端的负载均衡(只针对queue);按照良好的设计准则,当consumer消费速度很慢时,我们通常会部署多个consumer客户端,并使用较小的prefetch,同时关闭optimizeACK,可以让消息在多个consumer间“负载均衡”(即均匀的发送给每个consumer);如果较大的prefetchSize,将会导致broker一次性push给消费者大量的消息,但是这些消息需要很久才能ACK(消息积压),而且在消费者故障时,还会导致这些消息的重发。

2) 如果consumer端消费速度很快,但是producer端生成消息的速率较慢,比如生产者10秒钟生成10条消息,但是consumer一秒就能消费完毕,而且我们还部署了多个consumer!!这种场景下,建议开启optimizeACK,但是需要设置较小的prefetchSize;这样可以保证每个consumer都能有”活干”,否则将会出现一个consumer非常忙碌,但是其他consumer几乎收不到消息。

3) 如果消息很重要,特别是不愿意接收到”redelivery”的消息,那么我们需要将optimizeACK=false,prefetchSize=1

ActiveMQ MessageConsumer 消费消息的风格有2种:
同步 和异步 使用consumer.receive()就是同步,使用messageListener就是异步;在同一个consumer中,我们不能同时使用这2种风格,比如在使用listener的情况下,当调用receive()方法将会获得一个异常

  • ActiveMQ ACK_MODE详解

SESSION_TRANSACTED

当session使用事务时,就是使用此模式。在事务开启之后,和session.commit()之前,所有消费的消息,要么全部正常确认,要么全部redelivery(原意为再装船)。


AUTO_ACKNOWLEDGE
自动确认,这就意味着消息的确认时机将有consumer择机确认.”择机确认”似乎充满了不确定性,这也意味着,开发者必须明确知道”择机确认”的具体时机,否则将有可能导致消息的丢失,或者消息的重复接收。
1. 对于consumer而言,optimizeAcknowledge属性只会在AUTO_ACK模式下有效。
2. 其中DUPS_ACKNOWLEGE也是一种潜在的AUTO_ACK,只是确认消息的条数和时间上有所不同。
3. 在“同步”(receive)方法返回message之前,会检测optimizeACK选项是否开启,如果没有开启,此单条消息将立即确认,所以在这种情况下,message返回之后,如果开发者在处理message过程中出现异常,会导致此消息也不会redelivery,即”潜在的消息丢失”;如果开启了optimizeACK,则会在unAck数量达到prefetch * 0.65时确认,当然我们可以指定prefetchSize = 1来实现逐条消息确认。
4. 在”异步”(messageListener)方式中,将会首先调用listener.onMessage(message),此后再ACK,如果onMessage方法异常,将导致client端补充发送一个ACK_TYPE为REDELIVERED_ACK_TYPE确认指令;如果onMessage方法正常,消息将会正常确认(STANDARD_ACK_TYPE)。此外需要注意,消息的重发次数是有限制的,每条消息中都会包含“redeliveryCounter”计数器,用来表示此消息已经被重发的次数,如果重发次数达到阈值(MaximumRedeliveries),将会导致消费者发送一个ACK_TYPE为“POSION_ACK_TYPE”确认指令,这就导致broker端认为此消息无法消费,此消息将会被删除或者迁移到”dead letter queue”通道中。
这里写图片描述

在使用message queue的过程中,总会由于种种原因而导致消息失败。一个经典的场景是一个生成者向queue中发消息,里面包含了一组邮件地址和邮件内容。而消费者从queue中将消息一条条读出来,向指定邮件地址发送邮件。消费者在发送消息的过程中由于种种原因会导致失败,比如网络超时、当前邮件服务器不可用等。这样我们就希望建立一种机制,对于未发送成功的邮件再重新发送,也就是重新处理。重新处理超过一定次数还不成功,就放弃对该消息的处理,记录下来,继续对剩余消息进行处理。

activemq为我们实现了这一功能,叫做redelivery(重新投递)。当消费者在处理消息时有异常发生,会将消息重新放回queue里,进行下一次处理。当超过重试次数时,消息会被放置到一个特殊的queue中,即dead letter queue,简称dlq,用于进行后续分析。

package jms;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQPrefetchPolicy;
import org.apache.activemq.RedeliveryPolicy;

public class JMSConsumer {

    public static void main(String[] args) throws JMSException {
        ActiveMQConnectionFactory connectionFactory;// 连接工厂
        Connection connection = null;// 连接
        ActiveMQPrefetchPolicy p = new ActiveMQPrefetchPolicy();
        p.setQueuePrefetch(1);
        Session session;// 会话 接受或者发送消息的线程
        Destination destination;// 消息的目的地

        MessageConsumer messageConsumer;// 消息的消费者

        // 实例化连接工厂
        connectionFactory = new ActiveMQConnectionFactory("system", "zdc524", "tcp://ip:61616");
        connectionFactory.setPrefetchPolicy(p);

        // 通过连接工厂获取连接


        RedeliveryPolicy  p1 = new RedeliveryPolicy();// 添加重装策略
        p1.setMaximumRedeliveries(3);
        //p1.setRedeliveryDelay(100);
        connectionFactory.setRedeliveryPolicy(p1);
        // 启动连接
        connection = connectionFactory.createConnection();
        connection.start();
        // connection
        // 创建session
        session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        // 创建一个连接HelloWorld的消息队列
        destination = session.createQueue("HelloWorld");

        // 创建消息消费者
        messageConsumer = session.createConsumer(destination);
        //Listener listener = new Listener();
        //messageConsumer.setMessageListener(listener);
         while (true) {
             TextMessage textMessage = (TextMessage) messageConsumer.receive(100000);
             if (textMessage != null) {
                 System.out.println("收到的消息:" + textMessage.getText());
                 session.rollback();
             } else {
                 break;
             }
         }

    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值