RabbitMQ高级特性(二)

 

一、消息的可靠性投递-生产者

  在使用RabbitMQ的时候,作为消息的发送方希望杜绝任何消息丢失或者投递失败的场景。如果消息投递失败,RabbitMQ为我们提供了两种模式用来控制消息的可靠投递。 

  • confirm:确认模式

  • return:退回模式

我们都知道MQ消息投递的流程,producer--->exchange--->routingKey--->queue--->consumer。那么这两种模式又是如何工作的呢?

  • confirm模式:

    • 首先需要开启confirm模式

    • 消息从producer到达exchange后,会执行一个confirmCallback回调函数

    • 该回调函数的方法中有个ack参数

      • ack = true,则发送成功

      • ack = false,则发送失败

  • return模式:

    • 首先需要开启return模式

    • 消息从exchange路由到queue后

      • 如果投递成功,不会执行一个returnCallback回调函数

      • 如果投递失败,则会执行一个returnCallback回调函数

 

二、消费端ack机制-消费者  

  如果在处理消息的过程中,消费者的服务在处理消息的时候出现异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失。为了确保数据不会丢失,RabbitMQ支持确认机制ACK (Acknowledge)。

消费端接收到消息后有三种ack方式:

  • 不确认:ack = "none"

  • 手动确认:ack = "manual"

  • 自动确认:ack = "auto"

  自动确认是指,消息一旦被consumer接收到则自动确认收到,并将相应的message从RabbitMQ的消息缓存中移除。但是在实际的业务处理中,很可能是消息被接收到了,但是业务处理出现了异常,那么消息从缓存中移除即该消息就被丢弃了。如果设置了手动确认,则需要在业务处理成功后,调用channel.basicAck()方法手动签收,如果出现了异常,则调用channel.basicNack()方法,让其自动重发消息

 

三、消费端限流

  如果并发访问量大的情况下,生产方不停的发送消息,消费端可能处理不了那么多消息,此时消息在队列中堆积很多,当消费端启动,瞬间就会涌入很多消息,消费端有可能瞬间垮掉,这时我们可以在消费端进行限流操作,每秒钟拉取多少个消息。这样就可以进行并发量的控制,减轻系统的负载,提供系统的可用性,这种效果往往可以在秒杀和抢购中进行使用。rabbitmq中有限流的配置。

  RabbitMQ的限流操作:只需要在消费方配置每次拉取的消息数量。

 

 

 

 

 

 四、延时队列

    延时队列,即消息进入队列后不会被立即消费,只有到达指定的时间后才会被消费。比如,购买火车票:30分内完成支付,30分后,若不支付,则取消订单。比较遗憾的是,RabbitMQ本身没有直接支持延迟队列的功能,但是可以通过 TTL + DLX 组合来实现延时队列的效果。

 4.1、TTL 

  • TTL:Time To Live(存活时间/过期时间)

  • 当消息到达存活时间后,该消息还没有被消费,会自动被清除

  • RabbitMQ可以对消息设置过期时间也可以对整个队列设置过期时间

    • 如果都设置了,哪个时间先到则生效

  • 举个最常见了栗子:当我们在某个平台购买商品或者火车票、机票等,如果半个小时内没有支付,则该订单失效。

 

 

 

环境搭建:

  1、创建 rabbitmq-delay 工程并且添加依赖

<!--起步依赖-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
</parent>
<dependencies>
    <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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>
View Code

  2、编写启动类

@SpringBootApplication
public class DelayApplication {

    public static void main(String[] args) {
        SpringApplication.run(DelayApplication.class, args);
    }
}
View Code

  3、编写application.yml文件

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
View Code

 

代码实现 TTL:

  1、在工程中创建队列的配置类 TTLConfig

@Configuration
public class TTLConfig {

    // 创建队列
    @Bean
    public Queue ttlQueue(){
        // 创建队列,并且指定队列的过期时间
        return QueueBuilder.durable("ttl-queue").withArgument("x-message-ttl", 10000).build();
    }

    @Bean
    public Exchange ttlExchange(){
        // 注意,由于routingkey我们使用了匹配,因此我们要创建topic类型的交换机
        return new TopicExchange("ttl-exchange", true, false);
    }

    // 队列绑定到交换机
    @Bean
    public Binding queueBindToExchangeByTTL(Queue ttlQueue, Exchange ttlExchange){
        return BindingBuilder.bind(ttlQueue).to(ttlExchange).with("ttl.#").noargs();
    }
}
View Code

  2、在工程的test包下创建测试类 DelayTest

@Test
public void testTTL(){
    // 可以设置消息的属性消息
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            // 设置消息的过期时间 5s
            message.getMessageProperties().setExpiration("5000");
            return message;
        }
    };
    rabbitTemplate.convertAndSend("test-ttl-exchange","ttl.hehe", "ttl消息", messagePostProcessor);
}
View Code

 

PS:温馨提示,如果同时设置了队列过期时间和消息的过期时间,那么时间越短的先生效。

 

 4.2 、DLX

  4.2.1 概念 

  DLX:Dead-Letter-Exchange,死信交换机。当消息成为Dead Message后,可以被重新发送到另一个交换机,这个交换机就称为死信交换机。

  DLX其始就是一个Exchange,和一般的Exchange没有区别,仅仅只是设置某个队列的属性而已。当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。消费端可以监听这个队列中的消息做相应的处理。

  4.2.1 处理过程 

  1、生成者将消息发送到交换机后,由交换机路由到指定的队列

  2、当该消息成为了死信后并且将该消息发送给DLX。PS:成为死信的三种情况:

    • 队列消息长度达到限制

    • 消费者拒签消息

    • 原队列中存在消息过期设置,消息到达超时时间未被消费

  3、DLX再将这个消息路由给死信队列,并且由对应的消费者消费

 

 

 

 

  4.2.1 代码实现

创建配置类:

@Configuration
public class DLXConfig {

    // 创建交换机
    @Bean
    public Exchange delayExchange(){
        return new DirectExchange("delay-exchange");
    }
    // 创建队列
    @Bean
    public Queue delayQueue(){
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 20000);           // 队列过期时间
        args.put("x-max-length", 10000000);         // 队列中消息数量
        args.put("x-dead-letter-exchange", "dlx-exchange");       // 绑定死信交换机
        args.put("x-dead-letter-routing-key", "dlx-routing-key");    // 绑定死信路由器
        return QueueBuilder.durable("delay-queue").withArguments(args).build();
    }

    // 将队列绑定到交换机
    @Bean
    public Binding delayBinding(Queue delayQueue, Exchange delayExchange){
        return BindingBuilder.bind(delayQueue).to(delayExchange).with("delay-routing-key").noargs();
    }

    // 创建死信交换机
    @Bean
    public Exchange dlxExhange(){
        return new DirectExchange("dlx-exchange");
    }

    // 创建死信队列
    @Bean
    public Queue dlxQueue(){
        return new Queue("dlx-queue");
    }

    // 将死信队列绑定到死信交换机上
    @Bean
    public Binding dlxBinding(Queue dlxQueue, Exchange dlxExhange){
        return BindingBuilder.bind(dlxQueue).to(dlxExhange).with("dlx-routing-key").noargs();
    }

}
View Code

    

单元测试:

@Test
public void testDLX(){
    // 发送消息
    rabbitTemplate.convertAndSend("delay-exchange","delay-routing-key", "死信消息");
}
View Code

    

发送消息:

 

 

     

20s后再看:

 

 

 

 

  4.3 代码实现延时队列

    前面介绍中说过,通过 TTL + DLX 组合来实现延时队列的效果。在 4.2 章节中其实已经实现了延时队列了,我们只需要去监听死信队列去消费消息即可。

    创建监听器,如下:

@Component
@RabbitListener(queues = {"dlx-queue"})
public class DelayListener {

    @RabbitHandler
    public void readMsg(String msg){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        System.out.println("消费消息时间:" + sdf.format(new Date()));
        System.out.println("获取到的消息为:" + msg);
    }
}

    单元测试:

@Test
public void testDLX(){
    // 发送消息
    rabbitTemplate.convertAndSend("delay-exchange","delay-routing-key", "死信消息");
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    System.out.println("发送消息时间:" + sdf.format(new Date()));
}

 

 

 

posted @ 2020-10-20 21:40  ZFAblog  阅读(107)  评论(0)    收藏  举报