SpringBoot整合RabbitMQ, 过期时间TTL, 死信队列, 延迟队列

SpringBoot整合RabbitMQ简单示例

简介

  1. 在Spring项目中, 可以使用Spring-Rabbit去操作RabbitMQ, 尤其在spring-boot项目中, 只需要引入对应的amqp启动器即可, 方便的使用RabbitTemplate发消息, 使用注解接收消息.
  2. 搭建生产者工程
  • application.yml/properties文件配置RabbitMQ相关信息.
  • 在生产者工程中编写配置类, 用于创建交换机和队列, 并进行绑定.
  • 注入RabbitTemplate对象, 通过RabbitTemplate对象发送消息到交换机.
  1. 搭建消费者工程
  • application.yml配置RabbitMQ相关信息.
  • 创建消息处理类, 用于接收队列中的消息并进行处理.

生产者工程

  1. 添加如下依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  1. 创建application.yml, 内容如下
#tomcat端口
server:
  port: 8888

#RabbitMq的配置
spring:
  rabbitmq:
    host: 192.168.47.132
    port: 5672
    username: yellowstreak
    password: 123456
    virtual-host: /yellowstreak
  1. 创建配置类绑定交换机与队列
@Configuration
public class RabbitMQConfig {

    //交换机名称
    public static final String ITEM_TOPIC_EXCHANGE = "item_topic_exchange";

    //队列名称
    public static final String ITEM_QUEUE = "item_queue";

    //声明交换机
    @Bean("itemTopicExchange")
    public Exchange topicExchange() {

        //durable(true): 持久化交换机
        return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
    }

    //声明队列
    @Bean("itemQueue")
    public Queue itemQueue() {
        return QueueBuilder.durable(ITEM_QUEUE).build();
    }

    //绑定队列与交换机
    @Bean
    public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue, @Qualifier("itemTopicExchange") Exchange exchange) {

        return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
    }
}
  1. 写一个Controller方便测试
@RestController
public class SendMessageController {

    //注入Rabbit模板
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/send/message")
    public String sendMessage(@RequestParam String message, @RequestParam String key) {

        /**
         * 发送消息
         * 参数1: 交换机名称
         * 参数2: Routing Key
         * 参数3: 发送的消息.
         */
        rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, key, message);

        return "发送消息成功";
    }
}

搭建消费者工程

  1. 创建application.yml配置文件
#tomcat端口
server:
  port: 9999

#RabbitMq的配置
spring:
  rabbitmq:
    host: 192.168.47.132
    port: 5672
    username: yellowstreak
    password: 123456
    virtual-host: /yellowstreak
  1. 消息监听处理类
package com.example.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * 消费者的监听类
 */
@Component
public class MyListener {

    @RabbitListener(queues = "item_queue")
    public void message(String message) {
        System.out.println("消费者消费消息了: " + message );
    }
}

SpringBoot高级部分

过期时间TTL

  1. 过期时间TTL表示对消息设置预期时间, 在该时间内可以被消费者获取, 过了该事件就会被自动删除. 
  2. RabbitMQ可以对消息和队列设置TTL, 有两种方法
  • 通过队列的属性去设置, 队列中所有消息都有相同的过期时间.
  • 还可以对消息进行单独设置, 每条消息的TTL可以不同.
  1. 消息在队列的生存时间一旦超过设置的TTL值, 就称为dead message被扔到死信队列, 消费者将无法再收到该消息.
  2. 在生产者工程中加入rabbitmq.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: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/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    

</beans>
  • 该配置文件必须被主启动类引入: 
  • @ImportResource(locations = "classpath:/spring/spring-rabbitmq.xml")
  1. 设置队列的TTL: 在rabbitmq.xml中配置
  • 如果不设置x-message-ttl, 则此消息不会过期, 若设置为0, 表示除非此时可以直接将消息投递到消费者, 否则该消息被立即丢弃.
<!--定义过期队列及其属性,不存在则自动创建-->
    <rabbit:queue id="my_ttl_queue" name="my_ttl_queue" auto-declare="true">
        <rabbit:queue-arguments>
            <!--投递到该队列的消息如果没有消费都将在6秒之后被删除-->
            <entry key="x-message-ttl" value-type="long" value="6000"/>
        </rabbit:queue-arguments>
    </rabbit:queue>
  • 测试
@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 过期队列消息
     * 投递到该队列的消息如果没有消费都将在6秒之后被删除
     */
    @Test
    public void ttlQueueTest(){
        //发送消息
        rabbitTemplate.convertAndSend("my_ttl_queu", "发送到过期队列my_ttl_queue,6秒内不消费则不能再被消费。");
    }
}
  1. 设置消息的TTL - 这里的队列, 交换机用上边的示例
@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    

    @Test
    public void ttlMessageTest() {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setExpiration("5000");
        Message message = new Message("消息5s过期".getBytes(), messageProperties);

        //发送消息
        rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE, "item.update", message);
    }
}

死信队列

  1. DLX: Dead-Letter-Exchange, 死信交换机
  • 当消息在一个队列中变成死信后, 它能被重新发送到另一个交换机中, 即DLX, 绑定DLX的队列就叫死信队列.
  1. 消息变成死信, 原因有三种
  • 消息被拒绝
  • 消息过期
  • 队列达到最大长度.
  1. DLX其实也是一个正常的交换机, 它能在任何的队列上被指定, 实际就是设置某个队列的属性.
  • 当被指定的队列存在死信时, RabbitMQ就自动将这个消息发布到DLX上去, 进而被路由到死信队列.
  1. 要想使用死信队列, 只需在定义队列参数的时候设置队列参数x-dead-letter-exchange指定交换机即可.
  • 定义死信交换机
<!-- 定义定向交换机中的持久化死信队列,不存在则自动创建 -->
    <rabbit:queue id="my_dlx_queue" name="my_dlx_queue" auto-declare="true" />

    <!--定义广播类型交换机;并绑定上述队列-->
    <rabbit:direct-exchange id="my_dlx_exchange" name="my_dlx_exchange" auto-declare="true">
        <rabbit:bindings>
            <!--绑定路由键my_ttl_dlx、my_max_dlx,可以将过期的消息转移到my_dlx_queue队列-->
            <rabbit:binding key="my_ttl_dlx" queue="my_dlx_queue" />
            <rabbit:binding key="my_max_dlx" queue="my_dlx_queue"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>
  • 队列设置死信交换机
<!--定义过期队列及其属性,不存在则自动创建-->
    <rabbit:queue id="my_ttl_dlx_queue" name="my_ttl_dlx_queue" auto-declare="true">
        <rabbit:queue-arguments>
            <!--投递到该队列的消息如果没有消费都将在6秒之后被投递到死信交换机-->
            <entry key="x-message-ttl" value-type="long" value="6000"/>
            <!--设置当消息过期后投递到对应的死信交换机-->
            <entry key="x-dead-letter-exchange" value="my_dlx_exchange"/>
        </rabbit:queue-arguments>
    </rabbit:queue>

    <!--定义限制长度的队列及其属性,不存在则自动创建-->
    <rabbit:queue id="my_max_dlx_queue" name="my_max_dlx_queue" auto-declare="true">
        <rabbit:queue-arguments>
            <!--投递到该队列的消息最多2个消息,如果超过则最早的消息被删除投递到死信交换机-->
            <entry key="x-max-length" value-type="long" value="2"/>
            <!--设置当消息过期后投递到对应的死信交换机-->
            <entry key="x-dead-letter-exchange" value="my_dlx_exchange"/>
        </rabbit:queue-arguments>
    </rabbit:queue>

    <!--定义定向交换机 根据不同的路由key投递消息-->
    <rabbit:direct-exchange id="my_normal_exchange" name="my_normal_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding key="my_ttl_dlx" queue="my_ttl_dlx_queue"/>
            <rabbit:binding key="my_max_dlx" queue="my_max_dlx_queue"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>
  1. 消息过期的死信队列测试
@Test
    public void dlxTTLMessageTest() {
        rabbitTemplate.convertAndSend("my_normal_exchange", "my_ttl_dlx", "六秒过期后投递给死信交换机");
    }
  • 消费者从死信队列中接收消息

  • /**
     * 消费者的监听类
     */
    @Component
    public class MyListener {
    
        @RabbitListener(queues = "my_dlx_queue")
        public void message(String message) {
            System.out.println("消费者消费消息了: " + message );
        }
    }


  1. 消息过长的死信队列测试
/**
     * 超过队列长度消息投递到死信队列
     * 投递到一个正常的队列,但是该队列有设置最大消息数,到最大消息数之后队列中最早的消息会被投递到死信交换机(队列)
     */
    @Test
    public void dlxMaxMessageTest() {
        rabbitTemplate.convertAndSend("my_normal_exchange", "my_max_dlx", "这是第1个消息");
        rabbitTemplate.convertAndSend("my_normal_exchange", "my_max_dlx", "这是第2个消息");
        rabbitTemplate.convertAndSend("my_normal_exchange", "my_max_dlx", "这是第3个消息");
    }
  1. 流程

rabbittemplate 绑定_spring

 延迟队列

  1. 延迟队列存储的对象是对应的延迟消息, 所谓延迟消息, 就是生产者发出消息后, 不想让消费者立即拿到消息, 而是等待一定时间后再拿到.
  2. RabbitMQ中, 延迟队列可以通过过期时间+死信队列来实现.

rabbittemplate 绑定_rabbittemplate 绑定_02

  • 上述案例中, 我们不能去监听那两个正常的队列, 只能监听死信队列, 否则就达不到效果了.
  1. 延迟队列的应用场景
  • 电商支付场景, 若用户下单几十分钟内还未支付, 那么这个支付的订单算支付失败, 要进行支付失败的异常处理(将库存加回去), 这时候就能用延迟队列来处理.
  • 系统中如有需要在指定的某个时间后执行的任务都可以通过延迟队列处理.

###