RabbitMQ高级特性

本文介绍了RabbitMQ的高级特性,包括消息的confirm确认模式和return退回模式确保消息可靠性,消费者端的手动ACK确认,以及限流、TTL过期管理和死信队列的设置,还讨论了如何利用这些特性实现延迟队列和消息幂等性。
摘要由CSDN通过智能技术生成

RabbitMQ的高级特性

消息可靠性投递

在使用RabbitMQ的时候,为了防止消息丢失或者投递失败,RabbitMQ为我们提供了两种方式来控制消息的投递可靠性模式

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

RabbitMQ的消息投递路径为

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcuDKMFi-1677664248058)(.\images\image-20221124150935109.png)]

  • 消息从Producer到exchange则会返回一个confirmCallback
  • 消息从exchange到queue 投递失败会返回一个returnCallBack

这两个Callback帮助我们实现消息的可靠性投递

Confirm

以之前创建的Springboot的生产者为例

  1. 如果要开启confirm,那么可以先在yaml中开启配置

    #配置RabbitMQ的基本信息
    spring:
      rabbitmq:
        host: 123.123.1.221
        port: 5672
        username: hzp
        password: root
        publisher-confirm-type: correlated #配置confirm
    
  2. 编写测试类,定义ConfirmCallback函数

    @SpringBootTest
    class ProducerApplicationTests {
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        /*
            确认模式:
                步骤:
                    1.开启确认模式:在yaml中配置publisher-confirm-type: correlated
                    2.在rabbitTemplate定义ConfirmCallback回调函数
         */
        @Test
        void contextLoads() {
            rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
                /*
                    参数 correlationData 相关配置信息
                        b exchange交换机是否成功收到消息 true成功 false 失败
                        s 失败的原因
                 */
                @Override
                public void confirm(CorrelationData correlationData, boolean b, String s) {
                    System.out.println("Confirm方法被执行了-----------");
                    if(b){
                        //接收成功
                        System.out.println("消息接收成功"+s);
                    }else {
                        //失败,可以在这里对失败的消息进行处理
                        System.out.println("消息接收失败"+s);
                    }
                }
            });
            rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.hello","Message,Confirm");
    
        }
    
    }
    

Return

同样以之前的Springboot生产者为例

  1. 配置yaml开启return

    #配置RabbitMQ的基本信息
    spring:
      rabbitmq:
        host: 123.123.1.221
        port: 5672
        username: hzp
        password: root
        publisher-confirm-type: correlated #配置confirm
        publisher-returns: true #配置return
    
  2. 设置ReturnCallback

    /*
        回退模式:
            步骤:
                1.开启回退模式
                2.设置ReturnCallback
                3.设置Exchange处理消息的模式
                    3.1.如果消息没有路由到Queue,则丢弃消息(默认)
                    3.2.如果消息没有路由到Queue,返回给消息发送方ReturnCallback
     */
    
    @Test
    void testReturn() throws InterruptedException {
        //设置交换机处理失败消息的模式
        rabbitTemplate.setMandatory(true);//失败后返回给生产者
        //设置returnCallback
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                System.out.println("return 执行了");
            }
        });
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"hello111","Message,Confirm");
    
    
    }
    

    这里的Routingkey设置错误因此无法发送到队列,会产生回退并且将消息返回

Consumer ACK

Ack表示Acknowledge,指消费端接收到消息之后的确认方式

一共有三种确认方式

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

设置yaml开启手动确认

#配置RabbitMQ
spring:
  rabbitmq:
    host: 123.123.1.221
    port: 5672
    username: hzp
    password: root
    listener:
      direct:
        acknowledge-mode: manual #设置手动确认

编写监听类

/*
    Consumer Ack机制
    手动确认
        1.设置手动签收
        2.让监听器类实现ChannelAwareMessageListener
        3.如果消息成功处理,就调用channel 的 basicAck()签收
        4.如果消息处理失败,就调用channel 的 basicNack()拒绝签收,broker重新发送给consumer

 */
@Component
public class RabbitMQListener implements ChannelAwareMessageListener {
    //@RabbitListener(queues = "boot_queue_confirm")
//    public void ListenerQueue(Message message){
//        System.out.println(new String(message.getBody()));
//    }

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {

        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try{
            //1.接收转换消息
            System.out.println(new String(message.getBody()));
            //2.处理业务逻辑
            System.out.println("处理业务逻辑");
            //3.手动签收
            /*
                参数1:消息的标签
                参数2:是否批处理
            */
            channel.basicAck(deliveryTag,true);
        }catch (Exception e){
            //e.printStackTrace();
            //拒绝签收
            /*
                参数1:消息标签
                参数2:是否批处理
                参数3:是否重回队列,如果为true那么消息重新回到queue,broker会重新发送消息给消费者
             */
            channel.basicNack(deliveryTag,true,true);
        }
    }
}

消费端限流

设置一次拉取未确认消息的最大数量

#配置RabbitMQ
spring:
  rabbitmq:
    host: 123.123.1.221
    port: 5672
    username: hzp
    password: root
    listener:
      direct:
        acknowledge-mode: manual #设置手动确认
        prefetch: 1 #一次拉取一条消息

编写监听器

/*
    限流机制
        步骤:
            1.确保ack机制为手动确认
            2.listener-container 配置属性
                perfetch = 1,表示消费端每次从mq拉取一条消息,直到手动确认之后才会拉取下一条
 */
@Component
public class QosListener implements ChannelAwareMessageListener {
    @RabbitListener(queues = "boot_queue_confirm")
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        Thread.sleep(1000);
        //获取消息
        System.out.println(new String(message.getBody()));
        //处理业务逻辑
        System.out.println("处理中---");

        //签收
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        channel.basicAck(deliveryTag,true);

    }
}

TTL

Time To Live(存活时间/过期时间),单位是毫秒

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

队列的统一过期

直接在生产者的配置类中设置即可

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

消息的过期时间

MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
	//设置
    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
        //设置message的信息
        message.getMessageProperties().setExpiration("1000");//消息的过期时间
        //返回该消息
        return null;
    }
};
for (int i = 0; i < 10; i++) {
    rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.hello","Message,Confirm"+i,messagePostProcessor);//补充设置的参数
}

注意:如果设置了消息的过期时间也设置了队列的过期时间,以时间短的为主

队列过期之后所有的消息都会被清除。

死信队列

死信队列实际上指的是死信交换机,当消息成为Dead Message之后,可以被重新发送到另一个交换机,这个交换机就是死信交换机。也就是让Dead Message能够再次被利用。

在这里插入图片描述

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

  1. 队列消息长度达到限制,那么后面的消息就会成为死信
  2. 消费者拒绝消费消息,使用了basicNack或basicReject并且不把消息重新放回原目标队列
  3. 原队列存在消息过期设置,消息达到过期时间并且未被消费
死信队列
    1.声明正常的队列和交换机
    2.声明死信队列和死信交换机
    3.正常队列绑定死信交换机
        设置两个参数:
            死信交换机名称
            死信交换机使用的routing key

配置类,进行死信队列的设置

@Configuration
public class RabbitMQConfig {
    public static final String EXCHANGE_NAME = "boot_test_exchange";
    public static final String DEAD_EXCHANGE_NAME = "boot_dead_exchange";
    public static final String QUEUE_NAME = "boot_queue";
    public static final String DEAD_QUEUE_NAME = "boot_dead_queue";
    /*
        死信队列
            1.声明正常的队列和交换机
            2.声明死信队列和死信交换机
            3.正常队列绑定死信交换机
                设置两个参数:
                    死信交换机名称
                    死信交换机使用的routing key
     */
    //1.交换机
    @Bean("bootExchange")
    public Exchange bootExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }
    //死信交换机
    @Bean("bootDeadExchange")
    public Exchange bootDeadExchange(){
        return ExchangeBuilder.topicExchange(DEAD_EXCHANGE_NAME).durable(true).build();
    }

    //2.队列
    @Bean("bootQueue")
    public Queue bootQueue(){
        //正常队列绑定死信交换机
        return QueueBuilder.durable(QUEUE_NAME).deadLetterExchange(DEAD_EXCHANGE_NAME).deadLetterRoutingKey("dlx.haha").ttl(5000).build();
    }
    //死信队列
    @Bean("bootDeadQueue")
    public Queue bootDeadQueue(){
        return QueueBuilder.durable(DEAD_QUEUE_NAME).build();
    }
    //绑定
    @Bean
    public Binding bindQueueExchange(@Qualifier("bootQueue")Queue queue,@Qualifier("bootExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("boot.dlx.#").noargs();
    }
    //死信队列绑定死信交换机
    @Bean
    public Binding bindDeadQueueExchange(@Qualifier("bootDeadQueue")Queue queue,@Qualifier("bootDeadExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
    }
}

yaml

#配置RabbitMQ的基本信息
spring:
  rabbitmq:
    host: 123.123.1.221
    port: 5672
    username: hzp
    password: root
    publisher-confirm-type: correlated #配置confirm
    publisher-returns: true #配置return
    listener:
      simple:
        acknowledge-mode: manual

测试类

@Test
    void testSent(){
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.dlx.haha","Message,Confirm"+i);
        }
    }

这里会发送10条消息,然后原队列消息过期之后,会自动将消息转发到私信交换机然后到死信队列

在这里插入图片描述

图片中死信队列有20条消息,是因为连续测试了两次

延迟队列

延迟队列是指消息进入队列之后不会立即被消费,只有达到指定时间才会被消费

比如下单后,30分钟未支付就取消订单,回滚库存;或者新用户注册七天后,发送短信问候

RabbitMQ本身没有延迟队列,但是我们可以通过TTL过期时间和死信队列结合起来实现延迟队列,比如过期时间30分钟,30分钟后成为死信后再进行处理

日志与监控

RabbitMQ默认日志存放路径为: /var/log/rabbitmq/rabbit@xxx.log

除此之外还可以通过界面查看

在这里插入图片描述

消息追踪

RabbitMQ可以使用Firehose和rabbitmq_tracing插件功能实现消息追踪

消息幂等性

消息的幂等性:就是即使多次收到了消息,也不会重复消费。所以保证消息的幂等性就是保证消息不会重复消费,这在开发中是很重要的。比如客户点击付款,如果点击了多次,那也只能扣一次费。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值