RabbitMQ——SpringBoot整合、高级特性

SpringBoot整合RabbitMQ

生产者

  1. 创建生产者SpringBoot工程
  2. pom.xml引入amqp的starter
  3. 编写yml配置文件,配置基本信息
  4. 定义交换机,队列以及绑定关系的配置类
  5. 注入RabbitTemplate,调用方法,完成消息发送
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
# rabbitmq基本信息
spring:
  rabbitmq:
    host: 42.192.120.127
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
@Configuration
public class RabbitMqConfig {

    private static final String EXCHANGE_NAME = "boot_topic_exchange";
    private static final String QUEUE_NAME = "boot_queue";

    /**
     *  交换机 Exchange
     */
    @Bean("bootExchange")
    public Exchange bootExchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
    }

    /**
     * 队列 Queue
     */
    @Bean("bootQueue")
    public Queue bootQueue() {
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    /**
     * 队列和交换机绑定关系 Binding
     */
    @Bean
    public Binding bootBinding(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange")Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
    }
}
@SpringBootTest
public class SpringBootProducerTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSend() {
        rabbitTemplate.convertAndSend("boot_topic_exchange", "boot.hh", "test_message");
    }
}

在这里插入图片描述

消费者

  1. 创建消费者工程
  2. 引入pom依赖
  3. 编写yml配置
  4. 定义监听类,使用@RabbitListener注解完成队列监听
@Component
public class RabbitMqListener {

    @RabbitListener(queues = "boot_queue")
    public void listenerQueue(Message message) {
        System.out.println("消息为:" + message);
    }
}

// 结果
消息为:(Body:'test_message' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=boot_topic_exchange, receivedRoutingKey=boot.hh, deliveryTag=1, consumerTag=amq.ctag-lQeTsqwVpQPwT_mguRiyCQ, consumerQueue=boot_queue])

RabbitMQ高级特性

消息可靠性投递

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

  • confirm 确认模式
  • return 退回模式

rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer

  • 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
  • 消息从 exchange 到 queue 投递失败则会返回一个 returnCallback 。

我们将利用这两个 callback 控制消息的可靠性投递

设置ConnectionFactory的publisher-confirms=“true” 开启 确认模式。

  • 使用rabbitTemplate.setConfirmCallback设置回调函数。当消息发送到exchange后回调confirm方法。在方法中判断ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。
  • 设置ConnectionFactory的publisher-returns=“true” 开启 退回模式。
  • 使用rabbitTemplate.setReturnCallback设置退回函数,当消息从exchange路由到queue失败后,如果设置了rabbitTemplate.setMandatory(true)参数,则会将消息退回给producer。并执行回调函数returnedMessage。

配置文件

# rabbitmq基本信息
spring:
  rabbitmq:
    host: 42.192.120.127
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    # 开启confirm模式
    publisher-confirm-type: correlated
    # 开启return模式
    publisher-returns: true

配置类

@Configuration
public class RabbitMqConfig {

    private static final String EXCHANGE_NAME = "test_exchange";
    private static final String QUEUE_NAME = "test_queue";

    /**
     *  交换机 Exchange
     */
    @Bean("exchange")
    public Exchange exchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
    }

    /**
     * 队列 Queue
     */
    @Bean("queue")
    public Queue queue() {
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    /**
     * 队列和交换机绑定关系 Binding
     */
    @Bean
    public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("").noargs();
    }
}

测试方法

    @Test
    public void testConfirm() {
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *
             * @param correlationData 相关配置信息
             * @param ack 交换机是否成功收到消息
             * @param cause 错误信息
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (ack) {
                    System.out.println("消息发送成功:" + cause);
                } else {
                    System.out.println("消息发送失败:" + cause);
                }
            }
        });
        // 从producer到exchange失败的话,会返回一个confirmCallback
        rabbitTemplate.convertAndSend("exchange111", "", "hh");
    }

// 结果
消息发送失败:clean channel shutdown; protocol method: #method<channel.close>(reply-code=200, reply-text=OK, class-id=0, method-id=0)
    
    
    
    
    @Test
    public void testReturn() {
    	// 设置交换机处理失败消息的模式
        // 如果消息没有路由到queue,设置为false丢弃消息,设置为true返回给消息发送方
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                System.out.println("message: " + returnedMessage.getMessage());
                System.out.println("exchange: " + returnedMessage.getExchange());
                System.out.println("replyCode: " + returnedMessage.getReplyCode());
                System.out.println("replyText: " + returnedMessage.getReplyText());
                System.out.println("routingKey: " + returnedMessage.getRoutingKey());
            }
        }); 
    	// exchange -> queue失败的话,会返回一个returnsCallback
        rabbitTemplate.convertAndSend("test_exchange", "testReturn", "haha");
    }


// 结果
message: (Body:'haha' MessageProperties [headers={}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
exchange: test_exchange
replyCode: 312
replyText: NO_ROUTE
routingKey: testReturn

Consumer ACK

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式:

  • 自动确认:acknowledge=“none”
  • 手动确认:acknowledge=“manual”
  • 根据异常情况确认:acknowledge=“auto”

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

消费者端yml配置

spring:
  rabbitmq:
    host: 42.192.120.127
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    listener:
      simple:
        acknowledge-mode: manual # 手动
        retry:
          enabled: true # 是否允许重试

消费者代码

@Component
public class RabbitMqListener {

    private static Logger logger = LoggerFactory.getLogger(RabbitMqListener.class);

    @RabbitListener(queues = "test_queue")
    public void listenerQueue(Message message, Channel channel) throws IOException {
        long tag = message.getMessageProperties().getDeliveryTag();
        try {
            // 消息
            logger.info("**** {}", new String(message.getBody()));
            // 手动接收
            /*
                basicAck(long deliveryTag, boolean multiple)
                deliveryTag:取出来当前消息在队列中的的索引;
                multiple:为true的话就是批量确认,如果当前deliveryTag为5,那么就会确认
                deliveryTag为5及其以下的消息;一般设置为false
             */
            channel.basicAck(tag, false);
        } catch (Exception e) {
            /*
                basicNack(long deliveryTag, boolean multiple, boolean requeue)
                requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
                        false:将消息丢弃
             */
            channel.basicNack(tag, false, true);
        }
    }
}

生产者代码

@Configuration
public class RabbitMqConfig {

    private static final String EXCHANGE_NAME = "test_exchange";
    private static final String QUEUE_NAME = "test_queue";

    /**
     *  交换机 Exchange
     */
    @Bean("exchange")
    public Exchange exchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
    }

    /**
     * 队列 Queue
     */
    @Bean("queue")
    public Queue queue() {
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    /**
     * 队列和交换机绑定关系 Binding
     */
    @Bean
    public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("#.test").noargs();
    }
}
    @Test
    public void testAck() {
        rabbitTemplate.convertAndSend("test_exchange", "hello.test", "test_ack");
    }

消息已被接收
在这里插入图片描述

如果消费者在处理业务逻辑的时候,出现了异常,那么就不会接收该消息

@Component
public class RabbitMqListener {

    private static Logger logger = LoggerFactory.getLogger(RabbitMqListener.class);

    @RabbitListener(queues = "test_queue")
    public void listenerQueue(Message message, Channel channel) throws IOException {
        long tag = message.getMessageProperties().getDeliveryTag();
        try {
            // 消息
            logger.info("**** {}", new String(message.getBody()));
            // 手动接收
            /*
                basicAck(long deliveryTag, boolean multiple)
                deliveryTag:取出来当前消息在队列中的的索引;
                multiple:为true的话就是批量确认,如果当前deliveryTag为5,那么就会确认
                deliveryTag为5及其以下的消息;一般设置为false
             */
            int i = 10/0 ;
            channel.basicAck(tag, false);
        } catch (Exception e) {
            /*
                basicNack(long deliveryTag, boolean multiple, boolean requeue)
                requeue:true为将消息重返当前消息队列,还可以重新发送给消费者;
                        false:将消息丢弃
             */
            channel.basicNack(tag, false, true);
        }
    }
}

生产者只发送了一条消息,没有被接收,所以会出现下列情况
在这里插入图片描述

消费端限流

MQ有一个特性就是削峰限流,限流机制如下:

  1. 确认consumer的接收机制为:手动接收
  2. 配置listener.perfetch:表示消费端每次从mq拉取n条消息来消费,直到手动确认消费完毕后,才会继续拉取下一条消息

在这里插入图片描述

消费者application.yml配置

spring:
  rabbitmq:
    host: 42.192.120.127
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    listener:
      simple:
        acknowledge-mode: manual # 手动
        concurrency: 1 # 指定的最小的消费者数量
        max-concurrency: 10 # 指定的最大的消费者数量
        prefetch: 2 # 每次的消费数量
        retry:
          enabled: true # 是否允许重试

消费端JAVA代码

@Component
public class QosListener {

    private static final Logger logger = LoggerFactory.getLogger(RabbitMqListener.class);

    @RabbitListener(queues = "test_queue")
    public void listener(Message message, Channel channel) throws InterruptedException {
        long tag = message.getMessageProperties().getDeliveryTag();
        // 观察效果
        Thread.sleep(3000);
        try {
            String msg = new String(message.getBody());
            // 接收转换消息
            logger.info("收到消息:{},编号:{}", msg, tag);
            channel.basicAck(tag, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

观察控制台Unack的数量,代表未接收
在这里插入图片描述

TTL

TTL 全称 Time To Live(存活时间/过期时间)。
当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
在这里插入图片描述

TTL有两种过期设置

  • 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。
  • 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。
  • 如果两者都进行了设置,以时间短的为准。

队列统一过期

消费者端配置

@Configuration
public class RabbitMqConfig {

    private static final String EXCHANGE_NAME = "test_exchange";
    private static final String QUEUE_NAME = "test_queue";

    private static final int TTL_EXPIRATION = 10000;

    /**
     *  交换机 Exchange
     */
    @Bean("exchange")
    public Exchange exchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
    }

    /**
     * 队列 Queue
     */
    @Bean("queue")
    public Queue queue() {
        return QueueBuilder.durable(QUEUE_NAME).withArgument("x-message-ttl", TTL_EXPIRATION).build();
    }

    /**
     * 队列和交换机绑定关系 Binding
     */
    @Bean
    public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("#.test").noargs();
    }
}

队列中有一个TTL的标识
在这里插入图片描述

消息单独过期

    @Test
    public void testExpiration() {
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        };
        rabbitTemplate.convertAndSend("test_exchange", "hello.expire", "测试过期", messagePostProcessor);
    }

死信队列

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机)(因为交换机是RabbitMQ中的东西,其它的消息队列中直接叫死信队列),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
在这里插入图片描述

消息成为死信的三种情况:

  1. 队列消息长度到达限制;
  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

队列如何绑定死信交换机?
给队列设置参数: x-dead-letter-exchangex-dead-letter-routing-key

消息长时间未被消费

@Configuration
public class RabbitMqConfig {

    private static final String EXCHANGE_NAME = "test_exchange";
    private static final String QUEUE_NAME = "test_queue";
    private static final String ROUTING_KEY = "test.dlx.#";
    // 绑定的死信交换机的路由key,与下方的要符合
    private static final String X_DEAD_LETTER_ROUTING_KEY = "dlx.haha";

    // 死信队列参数
    private static final String DLX_EXCHANGE = "dlx_exchange";
    private static final String DLX_QUEUE = "dlx_queue";
    private static final String DLX_ROUTING_KEY = "dlx.#";

    private static final int TTL_EXPIRATION = 10000;

    /**
     *  交换机 Exchange
     */
    @Bean("exchange")
    public Exchange exchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
    }

    /**
     * 队列 Queue
     */
    @Bean("queue")
    public Queue queue() {
        Map<String, Object> map = new HashMap<>(8);
        map.put("x-message-ttl", TTL_EXPIRATION);
        map.put("x-dead-letter-exchange", DLX_EXCHANGE);
        map.put("x-dead-letter-routing-key", X_DEAD_LETTER_ROUTING_KEY);
        return QueueBuilder.durable(QUEUE_NAME).withArguments(map).build();
    }

    /**
     * 队列和交换机绑定关系 Binding
     */
    @Bean
    public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
    }

    @Bean(name = "dlxExchange")
    public Exchange dlxExchange() {
        return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
    }

    @Bean(name = "dlxQueue")
    public Queue dlxQueue() {
        return QueueBuilder.durable(DLX_QUEUE).build();
    }

    @Bean
    public Binding dlxBinding(@Qualifier("dlxQueue") Queue queue, @Qualifier("dlxExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(DLX_ROUTING_KEY).noargs();
    }
}

生产者发送消息

    @Test
    public void testDLX() {
        rabbitTemplate.convertAndSend("test_exchange", "test.dlx.hello", "测试死信队列");
    }

可以看见,刚开始是在test_queue队列中,但是10s之后,过期了,就转而到死信队列中了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

消息队列长度超出限制

RabbitMq配置

@Configuration
public class RabbitMqConfig {

    private static final String EXCHANGE_NAME = "test_exchange";
    private static final String QUEUE_NAME = "test_queue";
    private static final String ROUTING_KEY = "test.dlx.#";
    // 绑定的死信交换机的路由key,与下方的要符合
    private static final String X_DEAD_LETTER_ROUTING_KEY = "dlx.haha";

    // 死信队列参数
    private static final String DLX_EXCHANGE = "dlx_exchange";
    private static final String DLX_QUEUE = "dlx_queue";
    private static final String DLX_ROUTING_KEY = "dlx.#";

    private static final int TTL_EXPIRATION = 10000;
    private static final int MAX_LENGTH = 10;

    /**
     *  交换机 Exchange
     */
    @Bean("exchange")
    public Exchange exchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
    }

    /**
     * 队列 Queue
     */
    @Bean("queue")
    public Queue queue() {
        Map<String, Object> map = new HashMap<>(8);
//        map.put("x-message-ttl", TTL_EXPIRATION);
        // 设置队列的最大长度为10
        map.put("x-max-length", MAX_LENGTH);
        map.put("x-dead-letter-exchange", DLX_EXCHANGE);
        map.put("x-dead-letter-routing-key", X_DEAD_LETTER_ROUTING_KEY);
        return QueueBuilder.durable(QUEUE_NAME).withArguments(map).build();
    }

    /**
     * 队列和交换机绑定关系 Binding
     */
    @Bean
    public Binding binding(@Qualifier("queue") Queue queue, @Qualifier("exchange")Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
    }

    @Bean(name = "dlxExchange")
    public Exchange dlxExchange() {
        return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
    }

    @Bean(name = "dlxQueue")
    public Queue dlxQueue() {
        return QueueBuilder.durable(DLX_QUEUE).build();
    }

    @Bean
    public Binding dlxBinding(@Qualifier("dlxQueue") Queue queue, @Qualifier("dlxExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(DLX_ROUTING_KEY).noargs();
    }
}

生产者发送消息

    @Test
    void test() {
        for (int i = 0; i < 50; i++) {
            rabbitTemplate.convertAndSend("test_exchange", "test.dlx.hello", "测试死信队列2");
        }
    }

有40条信息进入了死信队列
在这里插入图片描述

消费者拒绝接收消息

生产者发送十条消息
在这里插入图片描述

消费者开启手动确认模式

spring:
  rabbitmq:
    host: 42.192.120.127
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    publisher-confirm-type: correlated # 开启确认模式
    publisher-returns: true # 开启返回模式
    listener:
      simple:
        acknowledge-mode: manual # 采用手动应答
        concurrency: 1 # 指定最小的消费者数量
        max-concurrency: 1 # 指定最大的消费者数量
        retry:
          enabled: false # 重试
          # max-attempts: 3 # 重试次数

拒绝消息

@Component
@Slf4j
public class RabbitListener {

    @org.springframework.amqp.rabbit.annotation.RabbitListener(queues = "test_queue")
    public void listener(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            log.info("收到了一条消息...");
            // 拒绝消息,且不重回队列
            // basicNack(long deliveryTag, boolean multiple, boolean requeue)
            // 设置requeue为false
            channel.basicNack(deliveryTag, true, false);
        } catch (IOException e) {
            log.info("{}", e.getMessage());
        }
    }
}

消息进入死信队列
在这里插入图片描述

延迟队列(Todo)

通过TTL和死信队列来实现延迟队列

  • TTL设置任务过期时间
  • 任务过期后进入延迟队列

消息可靠性分析与追踪(Todo)

RabbitMQ应用问题(Todo)

消息可靠性保障(Todo)

消息幂等性处理(Todo)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值