RabbitMQ高级特性:消息确认、削峰限流、TTL、私信队列、延迟队列、防止重复消费和消息积压...

在这里插入图片描述

一、消息的可靠投递性

在这里插入图片描述

1、【生产者端】Spring消息可靠性投递配置

Confirm测试

测试确认机制一定要开启publisher-confirms="true"和publisher-returns=“true”,默认是关闭的,即默认不会回调 。
1、配置
rabbitmq.properties配置文件

rabbitmq.host=192.168.131.171
rabbitmq.port=5672
rabbitmq.username=jihu
rabbitmq.password=jihu
rabbitmq.virtual-host=/jihu

spring-rabbitmq-producer.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- ${}获取配置 -->
    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
                               publisher-returns="true"/>
    <!-- 加上这个配置之后,如果交换机不存在,可以直接通过代码中创建交换机 -->
    <!--定义管理交换机、队列-->
    <!-- <rabbit:admin connection-factory="connectionFactory"/> -->

	<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!--  ================== 消息可靠性投递  ======================== -->

    <!--消息可靠性投递(生产端)-->
    <rabbit:queue id="test_queue_confirm" name="test_queue_confirm"/>

    <rabbit:direct-exchange name="test_exchange_confirm">
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>
    
    <!--  ================== 消息可靠性投递  ======================== -->
</beans>    

2、测试代码

	@Test
    public void testConfirm() throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        //定义回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *
             * @param correlationData 相关配置信息
             * @param ack   exchange交换机 是否成功收到了消息。true 成功,false代表失败
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法被执行了....");

                //ack 为  true表示 消息已经到达交换机
                if (ack) {
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }
            }
        });

        rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm ...");

        // 不要让程序结束,否则可能无法来得及执行回调
        countDownLatch.await();
    }

在这里插入图片描述
此时,我们关闭“<rabbit:admin connection-factory=“connectionFactory”/>”这个配置,关闭之后无法主动的通过代码创建不存在的交换机。我们以此来演示发送失败:

	@Test
    public void testConfirmSendFailed() throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        //定义回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *
             * @param correlationData 相关配置信息
             * @param ack   exchange交换机 是否成功收到了消息。true 成功,false代表失败
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法被执行了....");

                //ack 为  true表示 消息已经到达交换机
                if (ack) {
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }
            }
        });
		// 设置一个不存在的交换机的名字
        rabbitTemplate.convertAndSend("test_exchange_confirmxxxxxxx", "confirm", "message confirm ...");

        // 不要让程序结束,否则可能无法来得及执行回调
        countDownLatch.await();
    }

在这里插入图片描述
可以看到,失败的回调被执行了,并且返回了错误信息。

Return测试
注意,生产者要想获取到发送队列失败的消息,必须开启:

rabbitTemplate.setMandatory(true);

开启之后才会将发送到队列失败的消息退还给生产者,否则即使发送失败也不会退还!

测试代码

@Test
    public void testReturn() throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        // 设置交换机处理失败消息的模式.设置为true的时候,会将将发送到队列的失败消息返回给生产者。默认为false,不返回
        rabbitTemplate.setMandatory(true);

        // 定义回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             *
             * @param message 消息对象
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange  交换机
             * @param routingKey  路由key
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("return 执行了...");

                System.out.println("message:"+message);
                System.out.println("replyCode:"+replyCode);
                System.out.println("replyText:"+replyText);
                System.out.println("exchange:"+exchange);
                System.out.println("routingKey:"+routingKey);

                //处理
            }
        });


        // 交换机名称正确,设置一个错误的路由key来模拟消息发送到队列失败的场景
        rabbitTemplate.convertAndSend("test_exchange_confirm", "confirmXXX", "message confirm ...");

        // 不要让程序结束,否则可能无法来得及执行回调
        countDownLatch.await();
    }

在这里插入图片描述
在这里插入图片描述

2、【消费者端】Spring消息可靠性投递配置

在这里插入图片描述
在这里插入图片描述
1、配置文件:配置监听类和交换机、队列的关系

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>

   

    <bean id="ackListener" class="com.jihu.rabbitmq.listener.AckListener"/>

    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
        <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/>
    </rabbit:listener-container>
</beans>

2、监听类

@Component
public class AckListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        // 获取消息的id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        // 获取消息内容
        System.out.println("message" + new String(message.getBody()));

        // 消息消费,业务处理
        System.out.println("===== 业务处理 ======");

        // 业务正常执行完成后,完成签收. true代表签收多条消息
        channel.basicAck(deliveryTag, true);
    }

    @Override
    public void onMessage(Message message) {}
}

启动类,初始化spring容器:

public class Test {
    public static void main(String[] args) {
        // 初始化spring容器
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("classpath:spring-rabbitmq-consumer.xml");
    }
}

随后我们来启动这个启动类。

然后我们再使用生产者发送消息:

 	@Test
    public void testReturn() throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        // 设置交换机处理失败消息的模式.设置为true的时候,会将将发送到队列的失败消息返回给生产者。默认为false,不返回
        rabbitTemplate.setMandatory(true);

        // 定义回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             *
             * @param message 消息对象
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange  交换机
             * @param routingKey  路由key
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("return 执行了...");

                System.out.println("message:"+message);
                System.out.println("replyCode:"+replyCode);
                System.out.println("replyText:"+replyText);
                System.out.println("exchange:"+exchange);
                System.out.println("routingKey:"+routingKey);

                //处理
            }
        });


        // 交换机名称正确,设置一个错误的路由key来模拟消息发送到队列失败的场景
        rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm ...");

        // 不要让程序结束,否则可能无法来得及执行回调
        countDownLatch.await();
    }

在这里插入图片描述
可以看到,消费端成功接收到消息后,处理了业务,在没有异常的情况下进行了确认消费。

模拟执行业务时发生异常并手动拒绝签收

@Component
public class AckListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        // 获取消息的id
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        // 获取消息内容
        System.out.println("message" + new String(message.getBody()));

        try {
            // 消息消费,业务处理
            System.out.println("===== 业务处理 ======");

            int error_cause = 5 / 0;

            // 业务正常执行完成后,完成签收. true代表签收多条消息
            channel.basicAck(deliveryTag, true);
        } catch (Exception e) {
            System.out.println("业务处理异常!!!");

            // 拒绝签收
            // 参数3:设置为true,则将消息重新添加到队列中去,broker会重新发送该消息给消费者
            // 设置为false, 会丢弃该消息
            channel.basicNack(deliveryTag, true, true);
        }
    }

    @Override
    public void onMessage(Message message) {}
}

我们此时设置的是处理消息的时候如果发生异常,将消息放到队列中去,重新发送给消费者。但是这样处理很明显不对,因为错误一直存在,导致消息一直被重新发送!!!
在这里插入图片描述
比较理想的状态是,我们可以尝试着重新发送一定的次数,比如3次或者5次。如果超过固定次数后仍然无法被消费,考虑将该条消息写入到redis进行持久化处理或者加入到log文件中去。
在这里插入图片描述

消息可靠性总结

在这里插入图片描述

二、削锋限流

1、消费端限流

在这里插入图片描述
即,当我们系统的请求瞬间增多的时候,可以将请求发送到MQ中,然后我们再根据性能和需求从MQ中消费消息。

在这里插入图片描述

<!--
        acknowledge: manual 手动签收
        prefetch: 每次拉取多少条消息
    -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
     <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>

接下来我们模拟一下,生产者同时发送10条消息,消费端逐条消费的场景:

交换机队列配置

<bean id="qosListener" class="com.jihu.rabbitmq.listener.QosListener"/>

<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
    <rabbit:listener ref="qosListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>

生产者代码

/**
     * 批量放消息,让消费者每次拉取指定的数量
     */
    @Test
    public void testQos() {
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm " + i + " ...");
        }   
    }

启动该测试方法,然后我们查看rabbitmq页面中这个队列的消息:
在这里插入图片描述
之前存在1条,加上现在生产的10条,总共11条消息。

消费者代码:

因为此时是手动签收,如果手动不签收的话,就应该不会去拉取新的消息来进行消费。我们测试一下不签收消息,看看会怎么样:

@Component
public class QosListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        System.out.println("收到的消息: " + new String(message.getBody()));

        System.out.println("=== 业务处理 ===");

        // 进行消息的签收
        // channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
    }

    @Override
    public void onMessage(Message message) {}
}

随后我们来启动消费端的spring容器,让监听器运行起来:

public class Test {
    public static void main(String[] args) {
        // 初始化spring容器
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("classpath:spring-rabbitmq-consumer.xml");
    }
}

在这里插入图片描述
可以看到,此时因为没有手动签收,只显示处理的第一条消息。我们再来看看rabbitmq的页面的队列信息:
在这里插入图片描述
可以看到,此时有一条消息的状态是Unacked。就是因为我们没有手动的去ack导致的。而且也没有继续消费其他消息了,因为我们设置的是每一次拉取一条。

那,如果我们修改成每一次拉取2条呢?

<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="2">
    <!--<rabbit:listener ref="ackListener" queue-names="test_queue_confirm"/>-->
    <rabbit:listener ref="qosListener" queue-names="test_queue_confirm"/>
</rabbit:listener-container>

在这里插入图片描述
在这里插入图片描述
下面我们将手动签收开启,然后看看是不是两条两条消费的:

@Component
public class QosListener implements ChannelAwareMessageListener {

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        System.out.println("收到的消息: " + new String(message.getBody()));

        System.out.println("=== 业务处理 ===");

        // 进行消息的签收
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);

        TimeUnit.SECONDS.sleep(2);
    }

    @Override
    public void onMessage(Message message) {}
}

在这里插入图片描述

经过验证,确实是!大家可以自己验证

三、TTL(存活时间 / 过期时间)

在这里插入图片描述

在这里插入图片描述

1、Rabbitmq页面测试

在这里插入图片描述
我们需要添加TTL参数,然后设置过期时间即可,单位是ms。

此时就意味着,队列中的消息如果在5s中内没有被消费,就会被自动清除。

然后我们新建一个交换机来测试:
在这里插入图片描述
绑定并设置路由key:
在这里插入图片描述
然后我们来使用这个交换机发送消息:
在这里插入图片描述
在这里插入图片描述
5s后,发现消息被自动清除了:
在这里插入图片描述

2、代码测试

ttl配置:

<!--  ================== 高级特性:TTL 开始 ======================== -->
    <rabbit:queue id="test_queue_ttl" name="test_queue_ttl">
        <rabbit:queue-arguments>
            <!-- -message-ttl指的是队列的过期时间 -->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>

    <rabbit:topic-exchange name="test_exchange_ttl">
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--  ================== 高级特性:TTL 结束 ======================== -->

消息发送:

	@Test
    public void testTTL() {
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.test", "message ttl ...");
        }
    }

10s中后发现数据消失了。

四、死信队列

1、定义

在这里插入图片描述

2、成为死信队列的条件

在这里插入图片描述

3、队列绑定死信交换机

在这里插入图片描述

4、代码测试

配置文件

	<!--  ================== 高级特性:死信队列 开始 ======================== -->
        <!--  主要步骤
            1、声明正常的队列(test_queue_dlx)和交换机(text_exchange_dlx)
            2、声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
            3、正常队列绑定死信交换机
                设置两个参数:
                    * x-dead-letter-exchange: 死信交换机名称
                    * x-dead-letter-routing-key: 发送给死信交换机的routing key
        -->

        <!--1、声明正常的队列(test_queue_dlx)& 绑定死信交换机(exchange_dlx) -->
    <rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
        <!--正常队列绑定死信交换机-->
        <rabbit:queue-arguments>
            <!--绑定死信交换机-->
            <entry key="x-dead-letter-exchange" value="exchange_dlx"/>
            <!-- 发送给死信交换机的routing key -->
            <entry key="x-dead-letter-routing-key" value="dlx.test"/>
            <!--设置队列过期时间-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
            <!--设置队列长度限制-->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>

        <!--2、声明正常的交换机(test_exchange_dlx) & 绑定正常队列(test_queue_dlx)-->
    <rabbit:topic-exchange name="test_exchange_dlx">
        <rabbit:bindings>
            <!--正常队列和正常交换机之间的路由key-->
            <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>


        <!--3、声明死信队列-->
    <rabbit:queue name="queue_dlx" id="queue_dlx"/>
        <!--4、声明死信交换机-->
    <rabbit:topic-exchange name="exchange_dlx">
        <rabbit:bindings>
            <!--绑定死信队列和死信交换机,设置路由key-->
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--  ================== 高级特性:死信队列 结束 ======================== -->

其实,死信队列和死信交换机也只是普通的队列和交换机,只不过是正常的队列会在过期时间之后将消息通过路由key转发到死信交换机中,死信交换机又将消息发送到死信队列中去。

生产者测试代码
我们运行此代码,会向正常交换机“test_exchange_dlx”中发送一条消息,然后交换机通过路由key将消息转发给正常队列“test_queue_dlx”。

然后该消息会在10s后过期,此时正常队列“test_queue_dlx”会将过期消息发送到死信交换机“exchange_dlx”中,然后死信交换机又通过路由key将消息发送给死信队列“queue_dlx”。

	@Test
    public void testDeadLetter() {
        rabbitTemplate.convertAndSend("test_exchange_dlx", "test.dlx.haha", "死信消息!!!");
    }

可以看到消息被成功发送到正常队列中:
在这里插入图片描述
10s后,发现消息被转发到死信队列中了:
在这里插入图片描述

此时,我们如果用一个消费者来接收死信队列中的消息,那是不是就实现了延迟队列的功能了呢?类似于下单后,我们将订单消息丢入一个订单队列中去,如果超过30min,则将消息转发到订单的死信队列中去,然后由订单服务消费死信消息,执行取消订单,添加库存等业务操作!!!

总结

在这里插入图片描述

五、延迟队列

1、定义

在这里插入图片描述
在这里插入图片描述
使用定时器其实是存在问题的!!!

2、实现原理

在这里插入图片描述

其实延迟队列就是我们使用消费者监听死信队列!

在这里插入图片描述

3、代码测试

生产者配置

	<!--  ================== 高级特性:延迟队列 开始 ======================== -->
    <!--
        延迟队列:
            1、定义正常交换机(order_exchange)和队列(order_queue)
            2、定义死信交换机(order_exchange_dlx)和队列(order_queue_dlx)
            3、绑定,设置正常队列过期时间为业务订单时间30分钟
    -->
        <!--定义正常队列(order_queue)& 绑定死信交换机(order_exchange_dlx)-->
    <rabbit:queue name="order_queue" id="order_queue">
        <!--绑定,设置正常队列过期时间为30min-->
        <rabbit:queue-arguments>
            <!--绑定死信交换机-->
            <entry key="x-dead-letter-exchange" value="order_exchange_dlx"/>
            <!--绑定死信交换机和正常队列的路由key-->
            <entry key="x-dead-letter-routing-key" value="dlx.order.cancle"/>
            <!--设置队列过期时间-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>

    <!--定义正常交换机-->
    <rabbit:topic-exchange name="order_exchange">
        <rabbit:bindings>
            <!--绑定正常交换机和队列并设置路由key-->
            <rabbit:binding pattern="order.#" queue="order_queue"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--定义死信队列-->
    <rabbit:queue name="order_queue_dlx" id="order_queue_dlx"/>
    <!--定义死信交换机-->
    <rabbit:topic-exchange name="order_exchange_dlx">
        <rabbit:bindings>
            <!--绑定死信交换机和死信队列并设置路由key-->
            <rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--  ================== 高级特性:延迟队列 结束 ======================== -->

消费者代码
在这里插入图片描述
注意,此时要实现延迟队列,我们只需要定义一个消费者去监听死信队列即可实现延时功能。

消费者监听配置:

<bean id="orderCancelListener" class="com.jihu.rabbitmq.listener.OrderCancelListener"/>
	<!--
        acknowledge: manual 手动签收
        prefetch: 每次拉取多少条消息
    -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">            
     <rabbit:listener ref="orderCancelListener" queue-names="order_queue_dlx"/>
</rabbit:listener-container>

监听类,业务处理:

public class OrderCancelListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        // 消息传递tagId
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            // 1、接收转换消息
            System.out.println("message: " + new String(message.getBody()));

            // 2、处理业务逻辑
            System.out.println("处理业务逻辑...");
            System.out.println("根据订单id查询其状态...");
            System.out.println("判断状态是否为支付成功...");
            System.out.println("取消订单,回滚库存...");

            // 3、手动签收
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            System.out.println("取消订单业务处理异常!!! " + e);
            // 拒绝签收
            // 参数三:将处理失败的消息重新放入到队列
            channel.basicNack(deliveryTag, false, true);
        }
    }
 	@Override
    public void onMessage(Message message) {
    }
}

生产者代码:

	@Test
    public void testDelay() throws InterruptedException {
        // 1、发送订单消息,将来是在订单系统中,下单成功后发送消息
        rabbitTemplate.convertAndSend("order_exchange", "order.msg", "订单消息:id=10086, user:jihu, phone:18888888888...");


        // 2、打印倒计时10s(10s后过期)
        for (int i = 10; i > 0; i--) {
            System.out.println(i + "...");
            TimeUnit.SECONDS.sleep(1);
        }
    }

接下来我们生产者发送消息,然后再启动消费者(必须先启动生产者创建交换机和队列,否则会报错):
1、启动生产者

2、监听者启动spring容器,然后会自动启动监听类:

public class Test {
    public static void main(String[] args) {
        // 初始化spring容器
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("classpath:spring-rabbitmq-consumer.xml");
    }
}

在这里插入图片描述

在这里插入图片描述
可以看到,10s后消费者已经从死信队列中获取到延时消息了。

总结

在这里插入图片描述

六、消息积压

积压原因
在这里插入图片描述
将消息记录到数据库,慢慢处理,不要让消息持续积压!从而影响到正常服务。

七、如何防止消息重复消费

1、定义

在这里插入图片描述

2、解决方案

乐观锁机制
在这里插入图片描述
如果是分布式系统,需要使用redis。

利用Redis的原子性去实现
我们都知道redis是单线程的,并且性能也非常好,提供了很多原子性的命令。比如可以使用 setnx 命令。

在接收到消息后将消息ID作为key执行 setnx 命令,如果执行成功就表示没有处理过这条消息(每一次消费都会执行setnx,如果失败,这个值下一次无法插入),可以进行消费了,执行失败表示消息已经被消费了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值