rabbitmq的ttl和死信交换机,以及使用ttl和死信交互机配合来实现延迟消息的发送

TTL:过期时间

消息的过期设置都是在生产者那一端进行的.
首先队列queue就会有永久的队列或者是带有时间的设置的队列.
然后我的队列中的消息也会存在永久或者带有时间的消息.
当我们队列和消息都设置了时间,就会以时间短的那个算起.
* 1. 队列统一过期(整个队列设置了时间的过期)
* 2. 消息单独过期(在发送消息的时候,其中有一条消息有过期时间,而其他的消息都是正常的消息没有设置过期的消息时间限制,只有设置时间消息在队列顶端,才会判断其是否移除掉)
* 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
* 队列过期后,会将队列所有消息全部移除。
* 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)

成为死信的三种情况

 * 1.进入队列的消息超过了队列本身的长度限制,这条消息会成为死信
 * 2.消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;(新建一个消费者,去监听正常的队列)
 * 3.原队列存在消息过期设置,消息到达超时时间未被消费

在生产者端

rabbitmq.properties

rabbitmq.host=192.168.88.133
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/

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"/>
    <!--ttl-->
    <rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
        <!--设置queue的参数-->
        <rabbit:queue-arguments>
            <!--x-message-ttl指队列的过期时间-->
            <entry key="x-message-ttl" value="60000" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="test_exchange_ttl" >
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--
      死信队列:
          1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
          2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
          3. 正常队列绑定死信交换机
              设置两个参数:
                  * x-dead-letter-exchange:死信交换机名称
                  * x-dead-letter-routing-key:发送给死信交换机的routingkey
  -->
    <!--
        1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
    -->
    <rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
        <!--3. 正常队列绑定死信交换机-->
        <rabbit:queue-arguments>
            <!--3.1 x-dead-letter-exchange:死信交换机名称-->
            <entry key="x-dead-letter-exchange" value="exchange_dlx" />
            <!--3.2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
            <entry key="x-dead-letter-routing-key" value="dlx.hehe" />
            <!--4.1 设置队列的过期时间 ttl-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
            <!--4.2 设置队列的长度限制 max-length -->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer" />
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="test_exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--
       2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
   -->
    <rabbit:queue name="queue_dlx" id="queue_dlx"></rabbit:queue>
    <rabbit:topic-exchange name="exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

测试方法用于检测时间的过期,和检测死信队列

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class TestProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    /**
     * TTL:过期时间
     * 1. 队列统一过期(整个队列设置了时间的过期)
     * <p>
     * 2. 消息单独过期(在发送消息的时候,其中有一条消息有过期时间,而其他的消息都是正常的消息没有设置过期的消息时间限制,只有设置时间消息在队列顶端,才会判断其是否移除掉)
     * <p>
     * <p>
     * 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
     * 队列过期后,会将队列所有消息全部移除。
     * 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
     */
    @Test
    public void testTtl() {
      /*  for (int i = 0; i < 10; i++) {
            // 发送消息
            rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
        }*/
        // 消息后处理对象,设置一些消息的参数信息
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //1.设置message的信息
                message.getMessageProperties().setExpiration("5000");//消息的过期时间
                //2.返回该消息
                return message;
            }
        };
        //消息单独过期
        //rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....",messagePostProcessor);
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                //消息单独过期
                rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....", messagePostProcessor);
            } else {
                //不过期的消息
                rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe", "message ttl....");
            }
        }
    }
    /**
     * 测试死信队列
     * 成为死信的三种情况
     * 1.进入队列的消息超过了队列本身的长度限制,这条消息会成为死信
     * 2.消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;(新建一个消费者,去监听正常的队列)
     * 3.原队列存在消息过期设置,消息到达超时时间未被消费
     */
    @Test
    public void testDlx() {
       /* //3.原队列存在消息过期设置,消息到达超时时间未被消费
       rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.dlx","我是一条消息,我会成为死信马?");*/
       /* //1.进入队列的消息超过了队列本身的长度限制,这条消息会成为死信
        for (int i = 0; i < 20; i++) {        rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.dlx","我是一条消息,我会成为死信马?");
        }*/
       rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.dlx","我是一条消息,我会成为死信马?");
    }

}

成为死信的第二种情况
消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;(新建一个消费者,去监听正常的队列)

@Component
public class SpringDlxListener implements ChannelAwareMessageListener {
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
//basicAck(long deliveryTag, boolean multiple)
        try {
            System.out.println(new String(message.getBody()));
            System.out.println("处理业务逻辑");
            //人为发送异常,消费者会拒绝接受消息
            int i = 1 / 0;
            //一次性设置1数据的传输
            if (deliveryTag % 1 == 0) {
                channel.basicAck(deliveryTag, true);
            }
        } catch (Exception e) {
        //消费者拒绝接受消息,并且不让消息重新进入队列,此时消息就会成为死信消息
            channel.basicNack(deliveryTag, true, false);
        }
    }
}

rabbitmq.properties

rabbitmq.host=192.168.88.133
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
rabbitmq.virtual-host=/

spring-rabbitmq-consumer.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}"
    />
    <context:component-scan base-package="com.itheima.rabbitmq.listener"/>
    <!--设置手动的签收,acknowledge="manual"-->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1" auto-declare="true">
        <rabbit:listener ref="springDlxListener" queue-names="test_queue_dlx"/>
    </rabbit:listener-container>
</beans>

测试方法用于消费者一直监听消息

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class TestConsumer {
    @Test
    public void test1(){
        boolean flag = true;
        while (true){
        }
    }
}

环境基础:
当我们在一个订单系统中如果我们下了订单但是没有支付,超过30分钟后,订单自动结束,并且库存系统的数量要恢复没下单之前的样子.
这里我们可以使用两种机制
1.使用定时器,当一个订单系统产生的时候我们记录下此时间存入数据库中,每过一段时间我们就扫描这个数据库,当时间超过30分钟的订单我们就要判断此订单的状态如果这个订单的状态是未支付,我们就自动结束,并且库存系统的数量要恢复没下单之前的样子.使用定时器会造成性能的极大的降低,因为每隔一段时间都要从数据库中查询订单状态,会造成极大的性能损耗.
2.使用ttl+死信交换机实现了延迟消息我们发送一条消息,过期时间为30分钟,当三十分钟到了这条消息转发到了死信交换机中,再进行此订单的状态查询,这样就避免了和数据库的频繁的交互.
spring-rabbitmq-producer.xml


    <!--**********延迟队列中,正常的队列和交换机和使用正常队列来绑定延迟队列**********-->
    <rabbit:queue name="test_queue_delay" id="test_queue_delay">
        <!-- 正常队列绑定延迟交换机-->
        <rabbit:queue-arguments>
            <!--1 x-dead-letter-exchange:死信交换机名称-->
            <entry key="x-dead-letter-exchange" value="exchange_delay"/>
            <!--2 x-dead-letter-routing-key:发送给死信交换机的routingkey-->
            <entry key="x-dead-letter-routing-key" value="delay.hehe"/>

            <!--1 设置队列的过期时间 ttl-->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
            <!--2 设置队列的长度限制 max-length -->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>
  <rabbit:topic-exchange name="test_exchange_delay">
        <rabbit:bindings>
            <rabbit:binding pattern="test.delay.#" queue="test_queue_delay"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!--
      3. 声明延迟队列(queue_dlx)和延迟换机(exchange_dlx)
  -->
    <rabbit:queue name="queue_delay" id="queue_delay"></rabbit:queue>
    <rabbit:topic-exchange name="exchange_delay">
        <rabbit:bindings>
            <rabbit:binding pattern="delay.#" queue="queue_delay"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

测试方法用于测试延迟消息

   /**
     * 测试延迟队列
     */
    @Test
    public void testDelay() throws InterruptedException {
        rabbitTemplate.convertAndSend("test_exchange_delay", "test.delay.delay", "我要发送一条消息,在10秒钟之后进行处理");

        for (int i = 10; i > 0; i--) {
            System.out.println(i);
            Thread.sleep(1000);
        }
    }

消息中间件常见问题

消息补偿

在这里插入图片描述

如何保证我的消息100%不会丢失,就是消息的补偿机制所做的机制.
生产者在发送消息的同时向业务数据库中写入一条消息,发送消息和写入数据是同一事务,要么一起成功,要么失败进行事务的回滚.消息进入队列时候会有消费者监听此队列.消费者收到消息后将消息处理完后,保存到数据库中,并同时在mdb数据库中写入数据(图中是再次发送消息给队列,这里修改了下消费者处理消息完成后直接和mdb数据库进行绑定).生产者为了确保消息一定发送成功发送消失的时候还会发送一条延迟消息,由回调服务监听(类似于消费者的一个系统),它会将消息和mdb数据库进行比较如果发现消息不存数据库中会通知生产者再次发送消息和发送延迟消息,后期如是业务数据库和mdb数据库中存在差异说明有消息失败,会重新让生产者发送消息,一直到消息发送成功,也就是业务数据库和mdb数据一致的时候.

消息幂等性

由于消息传递过程中出现问题,导致消息被重复投递给消费者,消费者重复消费消息。如何保证一个消息被多次消费,其执行结果不变,这被称为消息的幂等性消费。
产生重复消费的情况
1、生产者已把消息发送到mq,在mq给生产者返回ack的时候网络中断,故生产者未收到确定信息,生产者认为消息未发送成功,但实际情况是,mq已成功接收到了消息,在网络重连后,生产者会重新发送刚才的消息,造成mq接收了重复的消息
2、消费者在消费mq中的消息时,mq已把消息发送给消费者,消费者在给mq返回ack时网络中断,故mq未收到确认信息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者消费了重复的消息;
如何解决
根本上我们要从业务逻辑出发保证我们的业务执行多次结果是相同的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值