ActiveMQ高级特性总结_17

一、高级特性之异步投递

ctiveMQ官网对异步投递的描述:http://activemq.apache.org/async-sends

ActiveMQ支持同步投递和异步投递两种模式将消息发送到broker,模式的选择对发送的延时有巨大的影响。生产者能达到的产出率主要受发送延时的影响,使用异步发送可以显著提高发送的性能。

ActiveMQ默认采用异步发送的模式,除非明确指定使用同步发送方式,或者在未使用事务的前提下发送持久化消息,这两种情况都是同步发送的。

如果没有使用事务且发送的是持久化的消息,每一次发送都是同步发送且会阻塞生产者,直到broker返回一个确认信息,表示信息已经被安全的持久化到硬盘了。确认机制提供了消息安全的保障,但同时会阻塞客户端带来很大延时。

许多高性能的应用,允许在失败的情况下有少量数据丢失。如果可以接受这一点,建议使用异步发送来挺高生产率,即使发送的是持久化的消息。

异步发送

       可以最大化producer生产者的发送效率,通常在发送消息量比较密集的情况下使用异步发送,可以很大程度提升生产者的性能,不过也带来了额外的问题,这需要消耗较多的Client端内存,同时会导致broker性能消耗增加,这种方式也不能有效的保证消息发送成功,在useAsyncSend=true的情况下,客户端需要容忍消息丢失的可能。

如果使用同步方式,假设此时有一个慢消费者,那么就会导致生产者阻塞,这不是我们希望看到的,所以,如果有慢消费者时,要采用异步发送。

三种类开启异步投递的方法:

        1.ACTIVEMQ_URL后面跟上参数jms.useAsyncSend=true。

//方式一
    public static final String ACTIVEMQ_URL = "tcp://10.5.96.48:61616?jms.useAsyncSend=true";
    public static final String QUEUE_NAME = "AsyncSend";

        2.使用connectionFactory的set方法,((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);

public static void main(String[]args) throws JMSException {
        //1.创建连接工厂
        //异步投递方式二
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        activeMQConnectionFactory.setAlwaysSyncSend(true);

        3.使用connection的set方法,((ActiveMQConnection)connection).setUseAsyncSend(true);

       //异步投递方式三
        ActiveMQConnection connection = (ActiveMQConnection) activeMQConnectionFactory.createConnection();
        connection.setUseAsyncSend(true);

二、高级特性之异步投递如何确认发送成功

异步发送丢失场景:生产者设置了useAsyncSend=true,使用producer.send(message);持续发送消息。由于消息不阻塞,生产者会认为所有send的消息都被成功发送到MQ了。如果此时MQ突然宕机,生产者端内存中尚未发送至MQ的消息就会丢失。

所以,正确的异步发送方法是需要接收回调函数的。

同步和异步的区别就在这里,同步发送send不阻塞就一定发送成功,异步发送需要接收回调函数并由客户端再判断一次是否发送成功。
 

package com.wsy.activemq;
 
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQMessageProducer;
import org.apache.activemq.AsyncCallback;
 
import javax.jms.*;
import java.util.UUID;
 
public class JmsProducer_Async {
    public static final String ACTIVEMQ_URL = "tcp://10.5.96.48:61616";
    public static final String QUEUE_NAME = "queue_asyncSend";
 
    public static void main(String[] args) throws JMSException {
        // 创建连接工厂,按照给定的url地址采用默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 设置消息发送模式是AsyncSend模式
        activeMQConnectionFactory.setUseAsyncSend(true);
        // 通过连接工厂,获取Connection并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        // 创建Session
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 创建目的地(目的地有两个子接口,分别是Queue和Topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        // 创建消息生产者,生产的消息放到queue中
        ActiveMQMessageProducer activeMQMessageProducer = (ActiveMQMessageProducer) session.createProducer(queue);
        for (int i = 0; i < 3; i++) {
            TextMessage textMessage = session.createTextMessage("message-" + i);
            textMessage.setJMSMessageID(UUID.randomUUID().toString());
            String messageID = textMessage.getJMSMessageID();
            // 设置回调函数,通过回调函数判断是否发送成功,成功走onSuccess()方法,失败走onException()方法
            activeMQMessageProducer.send(textMessage, new AsyncCallback() {
                @Override
                public void onSuccess() {
                    System.out.println(messageID + " success!");
                }
 
                @Override
                public void onException(JMSException e) {
                    System.out.println(messageID + " fail!");
                }
            });
        }
        // 按照资源打开的相反顺序关闭资源
        activeMQMessageProducer.close();
        session.close();
        connection.close();
    }
}

三、高级特性之延迟投递和定时投递

ActiveMQ官网对延迟投递和定时投递的描述:http://activemq.apache.org/delay-and-schedule-message-delivery.html

broker的schedulerSupport有4个属性。

Property nametypedescription
AMQ_SCHEDULED_DELAYlong延迟投递时间
AMQ_SCHEDULED_PERIODlong重复投递时间间隔
AMQ_SCHEDULED_REPEATint重复投递次数
AMQ_SCHEDULED_CRONStringcron表达式

延迟投递或定时投递默认是不开启的,所以需要在activemq.xml配置文件的broker结点加上schedulerSupport="true"属性来开启。修改完配置文件后,记得重启ActiveMQ。

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">

修改Producer的代码 ,延时3秒,间隔4秒发一次,发5次。

public class JmsProduce {

    public static final String ACTIVEMQ_URL = "tcp://10.5.96.48:61616";
    public static final String QUEUE_NAME = "delayAndSchedule";

    public static void main(String[]args) throws JMSException {
        //1.创建连接工厂
        //异步投递方式二
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得连接connection并启动访问
        //异步投递方式三
        ActiveMQConnection connection = (ActiveMQConnection) activeMQConnectionFactory.createConnection();
        connection.start();
        //3.创建会话 session
        //两个桉树 ,第一个叫事务  第二个叫签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 .创建目的地(具体是队列还是主题topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5. 创建消息的生产者
        ActiveMQMessageProducer activeMQMessageProducer = (ActiveMQMessageProducer) session.createProducer(queue);
        //消息的持久化设置(现在是非持久)
        activeMQMessageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
        //6.通过使用messageProducer 生产3条消息发送到队列里面
        for (int i = 0; i <3 ; i++) {
            // 7.创建消息  
            TextMessage message = session.createTextMessage("Msg---" + i);
           //延时3秒,间隔4秒,投递5次
            message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,3000);
            message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,4000);
            message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,5);
            //8.消息生产者发送给mq
            activeMQMessageProducer.send(message);
        }
        //9.关闭资源 顺着开启,倒着关闭
        activeMQMessageProducer.close();
        session.close();
        connection.close();

        System.out.println("**** 消息发布到MQ完毕 ****");
    }

}

四、高级特性之消费重试机制

具体哪些情况会引起消息重发?

  1. Client用了事务且在session中调用了rollback()
  2. Client用了事务且在调用commit()前关闭或者没有commit()
  3. Client在CLIENT_ACKNOWLEDGE的传递模式下,在session中调用了recover()

消息重发的间隔和重发次数是多少?

消息重发的间隔是每秒钟重发6次。

对有毒消息Poison ACK的理解。

一个消息被redelivedred超过默认的最大重发次数(默认6次)时,消费端会给MQ发送一个poison ack表示这条消息有毒,告诉broker不要再发了。这时候broker会把这条消息放到死信队列中。

ActiveMQ官网对重试机制的解释:http://activemq.apache.org/redelivery-policy

initialRedeliveryDelay和maximumRedeliveries是最常用的两个参数。

PropertyDefault ValueDescription
backOffMultiplier5重连时间间隔递增倍数,只有大于1和启用useExponentialBackOff参数时才生效。
collisionAvoidanceFactor0.15设置防止冲突范围的正负百分比,只有启用useCollisionAvoidance参数时,才生效,在延迟时间上再加一个时间的波动范围。
initialRedeliveryDelay1000L初始重发延迟时间。
maximumRedeliveries6最大重试次数,达到最大重连次数后抛出异常,-1表示不限制次数,0表示不进行重传。
maximumRedeliveryDelay-1最大传送延迟,只在useExponentialBackOff为true时有效。假设重连间隔10ms,倍数为2,第二次重连间隔为20ms,第三次重连间隔是40ms,当重连最大时间间隔达到直达重连时间间隔时,以后每次重连时间间隔都为最大重连时间间隔。
redeliveryDelay1000L重发延迟时间,当initialRedeliveryDelay=0时生效。
useCollisionAvoidancefalse启用防止冲突功能。
useExponentialBackOfffalse启用指数倍数递增的方式增加延迟时间。

创建JmsProducer_Redelivery.java和JmsConsumer_Redelivery.java。对于consumer,开启事务,并注释掉commit(),故意不执行commit()。

package com.wsy.activemq;
 
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ScheduledMessage;
 
import javax.jms.*;
 
public class JmsProducer_Redelivery {
    public static final String ACTIVEMQ_URL = "tcp://10.5.96.48:61616";
    public static final String QUEUE_NAME = "Redelivery";
 
    public static void main(String[] args) throws JMSException {
        // 创建连接工厂,按照给定的url地址采用默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 通过连接工厂,获取Connection并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        // 创建Session
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        // 创建目的地(目的地有两个子接口,分别是Queue和Topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        // 创建消息生产者,生产的消息放到queue中
        MessageProducer messageProducer = session.createProducer(queue);
        // 使用messageProducer生产消息发送到队列中
        for (int i = 0; i < 3; i++) {
            // 创建一条消息,可以理解成字符串
            TextMessage textMessage = session.createTextMessage("message-" + i);
            messageProducer.send(textMessage);
        }
        // 按照资源打开的相反顺序关闭资源
        messageProducer.close();
        session.close();
        connection.close();
    }
}
package com.wsy.activemq;
 
import org.apache.activemq.ActiveMQConnectionFactory;
 
import javax.jms.*;
 
public class JmsConsumer_Redelivery {
    public static final String ACTIVEMQ_URL = "tcp://192.168.0.123:61616";
    public static final String QUEUE_NAME = "Redelivery";
 
    public static void main(String[] args) throws JMSException {
        // 创建连接工厂,按照给定的url地址采用默认的用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        // 通过连接工厂,获取Connection并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        // 创建Session
        Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        // 创建目的地(目的地有两个子接口,分别是Queue和Topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        // 创建消费者,指明从queue取消息
        MessageConsumer messageConsumer = session.createConsumer(queue);
        while (true) {
            // 因为向队列中存放的是TextMessage的实例,所以取出来的时候,也要用TextMessage的实例来接收
            // 这里的receive()方法表示一直等待,如果给它传一个long类型的毫秒数,表示consumer等待超时时间
            TextMessage textMessage = (TextMessage) messageConsumer.receive(1000L);
            if (textMessage != null) {
                System.out.println("消费者消费:"+textMessage.getText());
            } else {
                break;
            }
        }
        // 上面开启了事务,正常情况下,必须执行commit();这里模拟出问题的情况,注释掉commit();
        // session.commit();
        // 按照资源打开的相反顺序关闭资源
        messageConsumer.close();
        session.close();
        connection.close();
    }
}

 运行Producer,运行Consumer。如果程序正确的情况下,这一次消息的发送和消费就完成了。因为consumer里没有执行commit,对于broker来说,它没有收到consumer的反馈,它认为consumer并没有消费掉消息。

此时,再启动consumer,会触发重发机制,consumer依旧会消费到消息,但是不告诉broker。再启动consumer6次,发现第6次收不到消息了,意味着broker已经把消息放到死信队列里了。也就是在第7次重试的时候,收不到消息了。

此时,打开管理页面的queue标签,会发现多了一个DLQ(死信队列)。

如果希望修改重发策略,也就是用自定义值覆盖默认值,使用RedeliveryPolicy这个类的实例,修改实例的属性值,最后再将实例设置到ActiveMQConnectionFactory实例的RedeliveryPolicy属性上即可,这里以修改重试次数为例,其他属性的修改方法类似,具体看代码。在消费者中添加

// 手动修改RedeliveryPolicy(重发策略)
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);// 修改重发次数为3次
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);// 将重发策略设置到ConnectionFactory中

如果项目是和Spring整合的,可以将这些属性值设置到Spring的配置文件中。

<!--定义RedeliveryPolicy(重发机制)-->
<bean id="activeMQRedeliveryPolicy" class="org.apache.activemq.RedeliveryPolicy">
    <property name="useCollisionAvoidance" value="false"/>
    <property name="useExponentialBackOff" value="true"/>
    <property name="maximumRedeliveries" value="3"/>
    <property name="initialRedeliveryDelay" value="1000"/>
    <property name="backOffMultiplier" value="2"/>
    <property name="maximumRedeliveryDelay" value="1000"/>
</bean>
<!--创建连接工厂-->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
    <property name="brokerURL" value="tcp://localhost:61616"/>
    <!--引用自定义重发机制-->
    <property name="redeliveryPolicy" ref="activeMQRedeliveryPolicy"/>
</bean>

五、高级特性之死信队列 

ActiveMQ对死信队列的介绍:http://activemq.apache.org/message-redelivery-and-dlq-handling

ActiveMQ中引入了死信队列的概念,一条消息被重复发了多次(默认重发6次redeliveryCounter=6),这条消息将会被ActiveMQ加入死信队列。开发人员可以在死信队列中查看出错的消息,进行人工干预。

通常生产环境中,使用MQ会设计两个队列,一个是业务队列,一个是死信队列。

假如第三方物流系统故障,仓储系统每次消费一条订单消息,尝试通知发货和配送,都会遇到对方接口报错,此时仓储系统就可以把这条消息标记为处理失败。一旦标记为处理失败后,MQ会把这条消息转入提前设置好的死信队列。在物流系统故障期间,所有订单全部处理失败,全部会转入死信队列。仓储系统有一个后台线程,监控第三方系统是否正常,当后台线程发现物流系统恢复正常后,后台线程就从死信队列里拿出失败的订单继续处理,重新执行发货和配送的逻辑。

SharedDealLetterStrategy:共享死信队列策略

所有的DeadLetter保存在一个共享的队列中,这是ActiveMQ broker端的默认策略。默认为ActiveMQ.DLQ,可以通过deadLetterQueue属性来设置。

<dealLetterStrategy>
    <sharedDeadLetterStrategy deadLetterQueue="MyDeadLetterQueue">
</dealLetterStrategy>

IndividualDeadLetterStrategy:独特死信队列策略

对于Queue而言,死信通道前缀默认为“ActiveMQ.DLQ.Queue”。

对于Topic而言,死信通道的前缀默认为“ActiveMQ.DLQ.Topic”。

我们使用默认前缀+队列或者主题的名称来指定死信队列。比如:ActiveMQ.DLQ.Queue.MyQueue和ActiveMQ.DLQ.Topic.MyTopic。

默认情况下,无论是Topic还是Queue,broker默认都会使用Queue来保存DeadLetter,即死信队列通常为Queue,开发者也可以指定为Topic。

<policyEntry queue="order">
    <deadLetterStrategy>
        <individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true"/>
    </deadLetterStrategy>
</policyEntry>

 如果需要直接删除过期的消息,而不需要发送到死信队列,那么设置processExpired参数为false,这个参数的默认值是true的。

<deadLetterStrategy>
    <sharedDeadLetterStrategy processExpired="false"/>
</deadLetterStrategy>

默认情况下,ActiveMQ不会把非持久的死信存放到死信队列,如果希望存放非持久的死信,那么需要修改processNonPersistent参数为true,这个参数默认值是fasle。

<deadLetterStrategy>
    <sharedDeadLetterStrategy processNonPersistent="true"/>
</deadLetterStrategy>

六、高级特性之防止重复调用 

网络延迟传输中,会进行MQ的重试,可能会出现重复消费。

如果一条消息是做数据库的插入操作,那么给这个消息做一个唯一主键,如果出现了重复消息,从数据库主键唯一性层面,导致主键冲突,可以避免重复插入数据。

可以使用第三方服务来做消费记录,以redis为例,给消息分配一个全局id,只要消息消费过,将<id,message>写入redis,当消费者开始消费之前,先去redis里查看有没有消费记录。

如果有消费记录,那么这条消息已经被处理过,否则,执行消费逻辑,并将<id,message>存到redis里。
 

参考

https://blog.csdn.net/qq_36059561/article/details/104264405

https://blog.csdn.net/qq_36059561/article/details/104264991

https://blog.csdn.net/qq_36059561/article/details/104266874

https://blog.csdn.net/qq_36059561/article/details/104270551

https://blog.csdn.net/qq_36059561/article/details/104271640

https://blog.csdn.net/qq_36059561/article/details/104281021

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值