RabbitMQ 的高级特性

目录

前言

一、重试机制

[1. 配置](#1. 配置)

[2. 自动确认](#2. 自动确认)

[3. 手动确认](#3. 手动确认)

二、TTL

[1. 设置消息的 TTL](#1. 设置消息的 TTL)

[2. 设置队列的 TTL](#2. 设置队列的 TTL)

[3. 两种设置 TTL 方式的区别](#3. 两种设置 TTL 方式的区别)

三、死信队列

[1. 消息过期](#1. 消息过期)

[2. 消息数量超过队列限制](#2. 消息数量超过队列限制)

[3. 消费者否定确认,且 requeue 为 false](#3. 消费者否定确认,且 requeue 为 false)

[4. 死信队列的应用](#4. 死信队列的应用)

四、延迟队列

[1. TTL + 死信队列](#1. TTL + 死信队列)

[2. 延迟队列插件](#2. 延迟队列插件)

[3. 区别](#3. 区别)

五、事务

六、消息分发

[1. 限流](#1. 限流)

[2. 负载均衡](#2. 负载均衡)

总结


前言

RabbitMQ 除了要保证消息的可靠传递,还需要保证消息在异常的情况下避免消息死循环,队列堵塞以及单点故障等问题。因此还需要引入一些高级特性,如重试机制,死信队列,TTL等。本文就来介绍 RabbitMQ 的高级特性。


一、重试机制

在消息的传递过程中,可能会遇到各种异常情况(如:网络故障,服务不可用,资源不足等问题),导致消息处理失败。

RabbitMQ 提供了消息重试机制,允许消息在处理失败后重新发送;

异常情况分为两种:

  1. 第一种:临时性异常(比如网络波动,数据库短暂不可用等):
  • RabbitMQ 可以通过重试机制解决,保证系统可靠;
  1. 第二种:非临时性异常(比如程序逻辑错误):
  • RabbitMQ 多次重试也不能解决问题,但可以设置重试次数,避免持续重试,导致消息积压;

1. 配置

spring:

rabbitmq:

host: 49.233.162.74

port: 5672

username: study

password: 123456

virtual-host: ackhost

listener:

simple:

acknowledge-mode: auto # 消息接收确认

retry:

enabled: true # 开启消费者失败重试

initial-interval: 5000ms # 初始失败等待时长为 5 秒

max-attempts: 5 # 最大重试次数 ( 包括自身消费的⼀次 )

2. 自动确认

配置交换机和队列

java 复制代码
    public static final String RETRY_QUEUE = "retry.queue";
    public static final String RETRY_EXCHANGE = "retry.exchange";
java 复制代码
    /**
     * 重试机制
     */
    @Bean("retryQueue")
    public Queue retryQueue(){
        return QueueBuilder.durable(Constants.RETRY_QUEUE).build();
    }

    @Bean("retryExchange")
    public DirectExchange retryExchange(){
        return ExchangeBuilder.directExchange(Constants.RETRY_EXCHANGE).build();
    }

    @Bean("retryBinding")
    public Binding retryBinding(@Qualifier("retryQueue") Queue queue,
                                  @Qualifier("retryExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with("retry");
    }

发送消息

java 复制代码
    @RequestMapping("/retry")
    public String retry(){
        String message = "hello, this is a message for retry...";
        rabbitTemplate.convertAndSend(Constants.RETRY_EXCHANGE, "retry", message);
        return "消息发送成功!";
    }

消费消息

java 复制代码
    @RabbitListener(queues = Constants.RETRY_QUEUE)
    public void retry(Message message) throws UnsupportedEncodingException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.printf("[" + Constants.RETRY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n",
                new String(message.getBody(), "utf8"), deliveryTag);
        int n = 3 / 0;
        System.out.println("业务逻辑处理完毕!");
    }

运行结果

可以看到,消息重试 5 次后,抛出异常。

消费者代码中,并没有对异常进行捕获。如果异常被捕获,则不会进行重试

生产者代码:

java 复制代码
    @RabbitListener(queues = Constants.RETRY_QUEUE)
    public void retry(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try{
            System.out.printf("[" + Constants.RETRY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n",
                    new String(message.getBody(), "utf8"), deliveryTag);
            int n = 3 / 0;
            System.out.println("业务逻辑处理完毕!");
        }catch (Exception e){
            System.out.println("发生异常!");
        }
    }

运行结果:

3. 手动确认

**重试机制只在自动确认模式下生效,**重试次数和重试间隔可在配置文件中配置;

消费者采用手动确认的方式,重试机制将不会生效,是否重试取决于消费者的逻辑;

消费时出现异常:

  1. 消费者否定确认,并将消息重新入队:将会持续重试;
  2. 消费者否定确认,但不将消息重新入队:不会进行重试;

消费者代码改成手动确认,发生异常时,消息重新入队:

java 复制代码
    @RabbitListener(queues = Constants.RETRY_QUEUE)
    public void retry(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try{
            System.out.printf("[" + Constants.RETRY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n",
                    new String(message.getBody(), "utf8"), deliveryTag);
            int n = 3 / 0;
            System.out.println("业务逻辑处理完毕!");
            channel.basicAck(deliveryTag, false);
        }catch (Exception e){
            System.out.println("发生异常!");
            channel.basicNack(deliveryTag, false, true);
        }
    }

运行结果:

注意事项:

  • 1. 自动确认模式下,重试后,消息消费失败,消息会被自动确认,消息就丢失了;
  • 2. 手动确认模式下,程序逻辑异常,消息失败后重新入队,消息将无法被确认,导致消息积压;

二、TTL

TTL 是 Time to Live,表示过期时间;RabbitMQ 可以对消息和队列设置 TTL

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

1. 设置消息的 TTL

设置消息的 TTL 有两种方式:

  • 一是针对所有消息,单独设置消息的 TTL;
  • 二是设置队列的 TTL;

如果两种方式一起用,则以两者之间 TTL 小的为准;

配置交换机和队列:

java 复制代码
    public static final String TTL_QUEUE = "ttl.queue";
    public static final String TTL_QUEUE2 = "ttl2.queue";
    public static final String TTL_EXCHANGE = "ttl.exchange";
java 复制代码
    /**
     * TTL
     */
    @Bean("ttlQueue")
    public Queue ttlQueue(){
        return QueueBuilder.durable(Constants.TTL_QUEUE).build();
    }

    @Bean("ttl2Queue")
    public Queue ttl2Queue(){
        return QueueBuilder.durable(Constants.TTL_QUEUE2).ttl(20000).build();
    }

    @Bean("ttl3Queue")
    public Queue ttl3Queue(){
        Map<String, Object> map = new HashMap<>();
        map.put("x-message-ttl", 20000);
        return QueueBuilder.durable(Constants.TTL_QUEUE2).withArguments(map).build();
    }

    @Bean("ttlExchange")
    public DirectExchange ttlExchange(){
        return ExchangeBuilder.directExchange(Constants.TTL_EXCHANGE).build();
    }

    @Bean("ttlBinding")
    public Binding ttlBinding(@Qualifier("ttlQueue") Queue queue,
                              @Qualifier("ttlExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("ttl").noargs();
    }

发送消息:

java 复制代码
    @RequestMapping("ttl")
    public String ttl(){
        rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "hello, this is ttl test...",
                new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setExpiration("10000");
                        return message;
                    }
                });
        return "发送消息成功!";
    }

发送消息后,可以看到 ttl.queue 中 Ready 消息为 1;

过 10s 钟后,消息自动被删除:

不设置消息的 TTL,消息不会过期;

2. 设置队列的 TTL

设置队列的 TTL 是在创建队列时,加入 x-message-ttl 参数实现的,单位是毫秒;

发送消息:

java 复制代码
    @RequestMapping("ttl2")
    public String ttl2(){
        rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "hello, this is ttl test...");
        return "发送消息成功!";
    }

发送消息后,可以看到 ttl2.queue 中 Ready 消息为 1;

过 10s 钟后,消息自动被删除:

3. 两种设置 TTL 方式的区别

区别:

设置队列的 TTL,一旦消息过期,就会从队列中删除;

设置消息的 TTL,即使消息过期,也不会马上从队列中删除,而是在即将投递到消费者时进行判定;

原因分析:

设置队列的 TTL,队列中先过期的消息在队列的头部,RabbitMQ 只要定期扫描队头即可;

设置消息的 TTL,每条消息的过期时间不同,删除过期消息需要扫描整个队列。RabbitMQ 没有直接扫描队列,而是等到消息被消费时,判断消息是否过期,如果过期再进行删除;

三、死信队列

死信(dead message)是因为各种原因,无法被消费的消息;

死信队列:当消息变成死信后,被重新发送到另一个交换机(死信交换机 - Dead Letter Exchange),绑定死信交换机的队列,称为死信队列(Dead Letter Queue);

死信的原因:

    1. 消息被拒绝或被否定确认,并设置 requeue 参数为 false;
    1. 消息过期;
    1. 队列达到最大长度;

1. 消息过期

声明队列和交换机:

java 复制代码
    public static final String NORMAL_QUEUE = "normal.queue";
    public static final String NORMAL_EXCHANGE = "normal.exchange";
    public static final String DL_QUEUE = "dl.queue";
    public static final String DL_EXCHANGE = "dl.exchange";
java 复制代码
     @Bean("normalQueue")
    public Queue normalQueue(){
                return QueueBuilder.durable(Constants.NORMAL_QUEUE)
                .deadLetterExchange(Constants.DL_EXCHANGE)
                .deadLetterRoutingKey("dlx")
                .build();
    }

    @Bean("normalExchange")
    public DirectExchange normalExchange(){
        return ExchangeBuilder.directExchange(Constants.NORMAL_EXCHANGE).build();
    }
    @Bean("normalBinding")
    public Binding normalBinding(@Qualifier("normalQueue") Queue queue,
                                 @Qualifier("normalExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with("normal");
    }

    @Bean("dlQueue")
    public Queue dlQueue(){
        return QueueBuilder.durable(Constants.DL_QUEUE).build();
    }
    @Bean("dlExchange")
    public DirectExchange dlExchange(){
        return ExchangeBuilder.directExchange(Constants.DL_EXCHANGE).build();
    }
    @Bean("dlBinding")
    public Binding dlBinding(@Qualifier("dlQueue") Queue queue,
                             @Qualifier("dlExchange") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with("dlx");
    }

设置产生死信的条件:

java 复制代码
    @Bean("normalQueue")
    public Queue normalQueue(){
        return QueueBuilder.durable(Constants.NORMAL_QUEUE)
                .deadLetterExchange(Constants.DL_EXCHANGE)
                .deadLetterRoutingKey("dlx")
                .ttl(10000)
                .maxLength(10)
                .build();
    }

生产者:

java 复制代码
    @RequestMapping("/dlx")
    public String dlx(){
        rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a dlx test...");
        System.out.printf("%tc 消息发送成功!\n", new Date());
        return "消息发送成功!";
    }

发送之后,normal.queue 中有一条消息 ready:

等待 10s,消息进入死信队列:

2. 消息数量超过队列限制

生产者:

java 复制代码
    @RequestMapping("/dlx")
    public String dlx(){
        System.out.printf("%tc 消息发送成功!\n", new Date());
        for (int i = 0; i < 20; i++){
            rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal",
                    i + " hello, this is a dlx test...");
        }

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

连续发送 20 条消息, 可以看到超出队列大小限制的消息,被放到死信队列里了:

3. 消费者否定确认,且 requeue 为 false

生产者代码:

java 复制代码
@RequestMapping("/dlx")
    public String dlx(){
        rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal",
                "hello, this is a dlx test...");
        System.out.printf("%tc 消息发送成功!\n", new Date());

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

消费者代码:

java 复制代码
    @RabbitListener(queues = Constants.NORMAL_QUEUE)
    public void normalListener(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try{
            System.out.printf("[" + Constants.NORMAL_QUEUE + "]接收到消息: %s, deliveryTag: %d\n", new String(message.getBody(), "utf8"), deliveryTag);
            int n = 3 / 0;
            System.out.println("业务逻辑处理完毕!");
            channel.basicAck(deliveryTag, false);
        }catch (Exception e){
            System.out.println("发生异常!");
            channel.basicNack(deliveryTag, false, false);
        }
    }

消息被否定确认后,直接进入死信队列:

4. 死信队列的应用

死信队列通常用于处理异常情况下消息不能被消费者正确消费为了保证消息不丢失,将消息置于死信队列;

应用场景:

  • 消息重试:将消息重新发回原队列或者另一个队列重新消费;
  • 消息丢弃:丢弃无法处理的消息,避免占用系统资源;
  • 日志收集:将死信消息作为日志收集起来,用于后续分析和问题定位;

四、延迟队列

延迟队列:消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费

RabbitMQ 本身并没有直接支持延迟队列,但可以通过 TTL + 死信队列的方式组合模拟实现延迟队列;

原理:将消息设置过期时间存入正常的工作队列中,而消费者通过订阅死信队列的方式,等到到达过期时间,消息进入死信队列,消费者就能拿到消息进行消费,就实现了延迟队列的功能;

1. TTL + 死信队列

可继续沿用上面死信队列的代码;

正常队列:

java 复制代码
    @Bean("normalQueue")
    public Queue normalQueue(){
                return QueueBuilder.durable(Constants.NORMAL_QUEUE)
                .deadLetterExchange(Constants.DL_EXCHANGE)
                .deadLetterRoutingKey("dlx")
                .build();
    }

生产者:

java 复制代码
    @RequestMapping("/delay")
    public String delay(){
        rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 10s ...", new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setExpiration("10000");
                        return message;
                    }
                });
        rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 30s ...", new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setExpiration("30000");
                        return message;
                    }
                });

        System.out.printf("%tc 消息发送成功!\n", new Date());
        return "消息发送成功!";
    }

消费者:

java 复制代码
    @RabbitListener(queues = Constants.DL_QUEUE)
    public void dlListener(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.printf("%tc [" + Constants.DL_QUEUE + "]接收到消息: %s, deliveryTag: %d\n", new Date(), new String(message.getBody(), "utf8"), deliveryTag);
    }

运行结果:

存在的问题:

将生产者发送消息的顺序调整一下,先发送过期时间 30s 的消息,再发送过期时间 10s 的消息;

生产者代码:

java 复制代码
    @RequestMapping("/delay")
    public String delay(){
        rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 10s ...", new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setExpiration("30000");
                        return message;
                    }
                });
        rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 30s ...", new MessagePostProcessor() {
                    @Override
                    public Message postProcessMessage(Message message) throws AmqpException {
                        message.getMessageProperties().setExpiration("10000");
                        return message;
                    }
                });

        System.out.printf("%tc 消息发送成功!\n", new Date());
        return "消息发送成功!";
    }

运行结果:

可以看到,10s 延迟和 30s 延迟的消息同时延迟了 30s 才到达消费者被消费;

原因:

RabbitMQ 只会检查队列头部消息是否过期,后面的消息即使已经过期,也会被队头消息阻塞,必须等待队头消息过期后才能进入死信队列。

因此,使用 TTL + 死信队列:只适合固定的延迟时间

2. 延迟队列插件

延迟队列插件下载地址:

Releases · rabbitmq/rabbitmq-delayed-message-exchange

上传插件installing Additional Plugins | RabbitMQ

将插件上传到指定目录:

  • /usr/lib/rabbitmq/plugins 是⼀个附加目录,RabbitMQ 包本身不会在此安装任何内容,如果没有这个路径,可以自己进行创建;

启动插件

查看插件列表

rabbitmq-plugins list

启动插件

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

重启服务

service rabbitmq-server restart

验证插件:新建交换机会有延迟消息的选项,表示插件已经安装成功

基于延迟插件实现延迟队列:

申明交换机,队列,绑定关系:

java 复制代码
    public static final String DELAY_QUEUE = "delay.queue";
    public static final String DELAY_EXCHANGE = "delay.exchange";
java 复制代码
@Configuration
public class DelayConfig {
    @Bean("delayQueue")
    public Queue delayQueue(){
        return QueueBuilder.durable(Constants.DELAY_QUEUE).build();
    }
    @Bean("delayExchange")
    public DirectExchange delayExchange(){
        return ExchangeBuilder.directExchange(Constants.DELAY_EXCHANGE).durable(true).delayed().build();
    }
    @Bean("delayBinding")
    public Binding delayBinding(@Qualifier("delayQueue") Queue queue,
                                @Qualifier("delayExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("delay");
    }
}

生产者:

java 复制代码
    @RequestMapping("/delay2")
    public String delay2(){
        rabbitTemplate.convertAndSend(Constants.DELAY_EXCHANGE, "delay",
                "hello, this is a delay test 30s ...", message -> {
                    message.getMessageProperties().setDelayLong(30000L);
                    return message;
                });
        rabbitTemplate.convertAndSend(Constants.DELAY_EXCHANGE, "delay",
                "hello, this is a delay test 10s ...", message -> {
                    message.getMessageProperties().setDelayLong(10000L);
                    return message;
                });

        System.out.printf("%tc 消息发送成功!\n", new Date());
        return "消息发送成功!";
    }

消费者:

java 复制代码
@Component
public class DelayListener {
    @RabbitListener(queues = Constants.DELAY_QUEUE)
    public void dlListener(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        System.out.printf("%tc [" + Constants.DELAY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n", new Date(), new String(message.getBody(), "utf8"), deliveryTag);
    }
}

运行结果:

消息能够按照设置的延迟时间,正确到达消费者;

3. 区别

延迟队列的应用场景:

  • 24 小时未处理,自动退款;
  • 注册 3 天后,发送调查问卷;
  • 订单 10 分钟未支付,自动取消;
  • ......

但 RabbitMQ 并未直接提供延迟队列的实现,实现延迟队列通常有两种方式:

    1. 使用 TTL + 死信队列;
    1. 使用延迟插件;

**TTL + 死信队列:**不需要额外插件的支持,但会存在队头消息阻塞的问题,还需要额外的逻辑处理死信队列的消息;

**延迟插件:**可以直接创建延迟队列,简化延迟消息的实现,但是只支持特定的版本,有运维工作;

五、事务

RabbitMQ 是基于 AMQP 协议实现的,该协议实现了事务机制,因此 RabbitMQ 也支持事务机制。

Spring AMQP 也提供了对事务相关的操作。RabbitMQ 事务允许开发者确保消息的发送和接收是原子性的,要么全部成功,要么全部失败。

配置事务管理器:

java 复制代码
    @Bean("transRabbitTemplate")
    public RabbitTemplate transRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setChannelTransacted(true);
        return rabbitTemplate;
    }

    @Bean
    public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory connectionFactory) {
        return new RabbitTransactionManager(connectionFactory);
    }

声明交换机,队列以及绑定关系:

java 复制代码
    public static final String TRANS_QUEUE = "trans.queue";
    public static final String TRANS_EXCHANGE = "trans.exchange";
java 复制代码
@Configuration
public class TransConfig {
    @Bean("transQueue")
    public Queue transQueue(){
        return QueueBuilder.durable(Constants.TRANS_QUEUE).build();
    }
    @Bean("transExchange")
    public DirectExchange transExchange(){
        return ExchangeBuilder.directExchange(Constants.TRANS_EXCHANGE).build();
    }
    @Bean("transBinding")
    public Binding transBinding(@Qualifier("transQueue") Queue queue,
                                @Qualifier("transExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("trans");
    }
}

生产者:

java 复制代码
    @Transactional
    @RequestMapping("/trans")
    public String trans(){
        transRabbitTemplate.convertAndSend(Constants.TRANS_EXCHANGE, "trans",
                "hello, this is trans test1...");
        int a = 10 / 0;
        transRabbitTemplate.convertAndSend(Constants.TRANS_EXCHANGE, "trans",
                "hello, this is trans test2...");
        return "消息发送成功!";
    }

带 @Transactional 注解测试:

不带 @Transactional 注解测试:

六、消息分发

RabbitMQ 有多个消费者的时候,会把消息派发给不同的消费者,每条消息派发一次。

当队列中消息数量较多时,可以增加消费者的数量,处理消息;

问题:

默认情况下,RabbitMQ 按照轮询的方式进行消息的分发,但消费者的处理速度有可能是不一致的,轮询分发有可能造成有的消费者空闲,有的消费者信道消息积压

解决方法:

使用 channel.basicQos(int prefetchCount) 方法,限制消费者信道上能保持的最大未确认消息的数量;

1. 限流

RabbitMQ 提供了限流机制,可以控制消费端一次只拉取 N 个请求;

通过设置 prefetchCount 参数,同时设置消息应答方式为手动应答

prefetchCount:消费者从队列中预取消息的数量,从而实现流控制和负载均衡;

配置:

复制代码
listener:
  simple:
    acknowledge-mode: manual
    prefetch: 5

配置交换机,队列和绑定关系:

java 复制代码
    public static final String QOS_QUEUE = "qos.queue";
    public static final String QOS_EXCHANGE = "qos.exchange";
java 复制代码
@Configuration
public class QosConfig {
    @Bean("qosQueue")
    public Queue qosQueue(){
        return QueueBuilder.durable(Constants.QOS_QUEUE).build();
    }
    @Bean("qosExchange")
    public DirectExchange qosExchange(){
        return ExchangeBuilder.directExchange(Constants.QOS_EXCHANGE).build();
    }
    @Bean("qosBinding")
    public Binding qosBinding(@Qualifier("qosQueue") Queue queue, @Qualifier("qosExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("qos");
    }
}

生产者:

java 复制代码
    @RequestMapping("/qos")
    public String qos(){
        String message = "hello, this is qos test...";
        for (int i = 0; i < 20; i++){
            rabbitTemplate.convertAndSend(Constants.QOS_EXCHANGE, "qos", i + " - " + message);
        }
        return "消息发送成功!";
    }

消费者:

java 复制代码
@Component
public class QosListener {
    @RabbitListener(queues = Constants.QOS_QUEUE)
    public void qos1(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.printf("qos1 [" + Constants.QOS_QUEUE + "]接收到消息: %s, deliveryTag =  %d\n", new String(message.getBody(), "utf8"), deliveryTag);
            Thread.sleep(2000);
        }catch (Exception e){
            channel.basicNack(deliveryTag, false, true);
        }
    }
}

运行结果:

生产者一次性发送 20 条消息,消费者每次拉取 5 条,可以看到队列中还剩 15条;

通过上述限流机制,可以保证系统在请求骤增的情况下,仍然可以正常工作,避免系统被打垮;

2. 负载均衡

当队列存在多个消费者,不同的消费者处理任务的速度不同,如果轮询分派消息,有可能有的消费者消息积压,有的消费者空闲;

可以设置 prefetch 为 1 ,给每个消费者都分派一个消息,消息确认后,再继续分派消息,保证所有消费者都按照自己的节奏工作,达到负载均衡的目的;

消费者:

java 复制代码
@Component
public class QosListener {
    @RabbitListener(queues = Constants.QOS_QUEUE)
    public void qos1(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.printf("qos1 [" + Constants.QOS_QUEUE + "]接收到消息: %s, deliveryTag =  %d\n", new String(message.getBody(), "utf8"), deliveryTag);
            Thread.sleep(2000);
            channel.basicAck(deliveryTag, false);
        }catch (Exception e){
            channel.basicNack(deliveryTag, false, true);
        }
    }

    @RabbitListener(queues = Constants.QOS_QUEUE)
    public void qos2(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            System.out.printf("qos2 [" + Constants.QOS_QUEUE + "]接收到消息: %s, deliveryTag =  %d\n", new String(message.getBody(), "utf8"), deliveryTag);
            Thread.sleep(1000);
            channel.basicAck(deliveryTag, false);
        }catch (Exception e){
            channel.basicNack(deliveryTag, false, true);
        }
    }
}

运行结果:

可以看到,消费者 1 处理慢,消费者 2 处理快,但两者都按照自己的能力处理消息,达到了负载均衡的效果;


总结

本文介绍了 RabbitMQ 的高级特性,包括:重试机制,TTL,死信队列,延迟队列,事务以及消息分发。

相关推荐
白晨并不是很能熬夜1 小时前
【RPC】第 1 篇:全景篇 — 一次 RPC 调用的完整旅程
java·网络·后端·网络协议·面试·rpc·java-zookeeper
z小天才b2 小时前
Java 设计模式完全指南:从入门到精通
java·开发语言·设计模式
烤麻辣烫2 小时前
算法--二分搜索
java·开发语言·学习·算法·intellij-idea
逍遥德2 小时前
MQTT教程详解-03. 高级知识点
java·物联网·中间件·信息与通信·iot·iotdb
Nice__J2 小时前
ISO26262功能安全——SafeOS
java·linux·安全
_F_y2 小时前
仿RabbitMQ实现消息队列-服务端核心模块实现(1)
分布式·rabbitmq
夹芯饼干3 小时前
虚拟机指令第六节
java·linux·服务器
A_aspectJ3 小时前
【Java基础开发】基于 Java Swing +MySQL + JDBC 版实现图书管理系统
java·开发语言·mysql
TE-茶叶蛋3 小时前
Spring最核心扩展点:BeanPostProcessor
java·后端·spring