RabbitMQ(六)发布确认高级与其他

本文详细探讨了RabbitMQ的高级特性,包括发布确认机制(生产者确认与消息回退)、消息备份策略(使用备份交换机)以及如何处理消息重复消费和幂等性问题。此外,还介绍了优先级队列的使用场景和配置方法,以确保关键消息优先处理。最后提到了惰性队列在防止内存满溢和处理大量消息时的作用。
摘要由CSDN通过智能技术生成

一、发布确认高级(整合springboot)

前面的发布消息和订阅都建立在MQ可用的情况下。回调的时机两种:一种是MQ服务器收到消息然后给生产者回调;另一种是MQ内部交换机到队列也可能造成消息的丢失,这个时候也会触发回调。

在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息(下面的p->b)。只能表名MQ服务器接收到了消息。

如果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的(消息的回退)。然而在Broker内部也有消息的发送(e->q)。

在生产环境中由于一些不明原因,导致 rabbitmq 重启,在 RabbitMQ 重启期间生产者消息投递失败, 导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行 RabbitMQ 的消息可靠投递呢? 特别是在这样比较极端的情况,RabbitMQ 集群不可用的时候,无法投递的消息该如何处理呢?

1.1确认机制方案

1.2模拟交换机应答成功

首先在配置文件中配置,开启交换机应答。不配置这个,写的回调函数将无法被触发

spring.rabbitmq.publisher-confirm-type=correlated

三个参数

  • NONE-------禁用发布确认模式,是默认值
  • CORRELATED-------发布消息成功到交换器后会触发回调方法
  • SIMPLE ----------经测试有两种效果,其一效果和 CORRELATED 值一样会触发回调方法。  第二种:在发布消息成功后使用 rabbitTemplate 调用 waitForConfirms 或 waitForConfirmsOrDie 方法 等待 broker 节点返回发送结果,根据返回结果来判定下一步的逻辑。                                                                                                                                          注意的点是 waitForConfirmsOrDie 方法如果返回 false 则会关闭 channel,则接下来无法发送消息到 broker

配置类:

@Configuration
public class ConfirmConfig {
    //交换机名
    public static final String CONFIRM_EXCHANGE_NAME="confirm.exchange";
    //队列名
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
    //声明交换机
    @Bean("confirmE")
    public DirectExchange confirmExchange(){
        return new DirectExchange(CONFIRM_EXCHANGE_NAME);
    }
    //声明队列
    @Bean("confirmQ")
    public Queue confirmQueue(){
        return  QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }
    //绑定
    @Bean
    public Binding queueBinding(@Qualifier("confirmQ")Queue queue,@Qualifier("confirmE") DirectExchange directExchange){
        return BindingBuilder.bind(queue).to(directExchange).with("key111");
    }
}

生产者:

@Slf4j
@RequestMapping("/confirm")
@RestController
public class Producer {
    //交换机名
    public static final String CONFIRM_EXCHANGE_NAME="confirm.exchange";
     @Autowired
     private RabbitTemplate rabbitTemplate;

    @GetMapping("sendMessage/{message}")
    public void sendMessage(@PathVariable("message") String message){
        //携带消息内容和消息id
        CorrelationData correlationData=new CorrelationData("1");
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,"key1",message,correlationData);
    }
}

消费者:

@Slf4j
@Component
public class ConfirmConsumer {
    //队列名
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    @RabbitListener(queues = CONFIRM_QUEUE_NAME)
    public void receiveMessage(Message message) throws UnsupportedEncodingException {
        String msg = new String(message.getBody(),"UTF-8");
        log.info("接收队列是confirm.queue,消费的消息:{}",msg);
    }
}

ConfirmCallback回调接口:

@Slf4j
@Component
public class MyconfirmCalback implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
    }
    /**
     *  交换机确认回到方法
     * @param correlationData ------保存了回到消息的id及相关消息
     * @param b------------------交换机是否收到消息 true false
     * @param s--------------------失败原因 (成功为null,没原因)
     */

    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
      String id=  correlationData!=null?correlationData.getId():"";
          if(b){
              log.info("交换机已经接收到了消息,ID为:{}",id);
          }else {
              log.info("交换机还未收到id:{}的消息,由于:{}",id,s);
          }
    }
}

结果分析:

发送成功消息,设置好id,且接收的消息无误,调用回调接口。

 发送消息,回调接口打印确认失败的信息给生产者。

 当设置路由错误的时候并且仅在设置生产者确认的机制下,交换机接收到消息后,会直接给消息生产者发送确认消息如果。发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的(消息的回退)。

1.3回退消息

在仅开启了生产者确认机制的情况下,交换机接收到消息后,会直接给消息生产者发送确认消息,如 果发现该消息不可路由,那么消息会被直接丢弃,此时生产者是不知道消息被丢弃这个事件的。那么如何 让无法被路由的消息帮我想办法处理一下?最起码通知我一声,我好自己处理啊。通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。

#打开消息回退
spring.rabbitmq.publisher-returns=true

配置类:

@Slf4j
@Component
public class MyconfirmCalback implements RabbitTemplate.ReturnsCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;
     //实现的是内部类接口,所以要将这个bean作为rabbitTemplate的属性注入
    @PostConstruct
    public void init(){
            rabbitTemplate.setReturnsCallback(this);
    }
   

    //通过设置 mandatory 参数可以在当消息传递过程中不可达目的地时将消息返回给生产者。
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
       log.info("消息:{},被交换机{}退回;退回原因:{},路由:{}",returnedMessage.getMessage().getBody().toString(),
               returnedMessage.getExchange(),returnedMessage.getReplyText(),returnedMessage.getRoutingKey());
    }

}

 生产者:

@Slf4j
@RequestMapping("/confirm")
@RestController
public class Producer {
    //交换机名
    public static final String CONFIRM_EXCHANGE_NAME="confirm.exchange";
     @Autowired
     private RabbitTemplate rabbitTemplate;

    @GetMapping("sendMessage/{message}")
    public void sendMessage(@PathVariable("message") String message){
        //携带消息内容和消息id,confirmCallback的参数
        CorrelationData correlationData=new CorrelationData("1");
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,"key1",message,correlationData);
        CorrelationData correlationData1=new CorrelationData("2");
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME,"key2",message,correlationData1);
    }
}

 从下图中看出,id为”1“的消息被交换机接收到并且也被队列接收了。但id为“2”的消息交换机接收了,但是队列将消息回退给了生产者,消息没有被丢失,理由是路由错误。

 

1.4消息备份

有了 mandatory 参数和回退消息,我们获得了对无法投递消息的感知能力,有机会在生产者的消息无法被投递时发现并处理。但有时候,我们并不知道该如何处理这些无法路由的消息,最多打个日志,然 后触发报警,再来手动处理。而通过日志来处理这些无法路由的消息是很不优雅的做法,特别是当生产者 所在的服务有多台机器的时候,手动复制日志会更加麻烦而且容易出错。

而且设置 mandatory 参数会增 加生产者的复杂性,需要添加处理这些被退回的消息的逻辑。如果既不想丢失消息,又不想增加生产者的 复杂性,该怎么做呢?

前面在设置死信队列的文章中,我们提到,可以为队列设置死信交换机来存储那些 处理失败的消息,可是这些不可路由消息根本没有机会进入到队列,因此无法使用死信队列来保存消息。

在 RabbitMQ 中,有一种备份交换机的机制存在,可以很好的应对这个问题。什么是备份交换机呢?备份交换机可以理解为 RabbitMQ 中交换机的“备胎”,当我们为某一个交换机声明一个对应的备份交换机时, 就是为它创建一个备胎,当交换机接收到一条不可路由消息时,将会把这条消息转发到备份交换机中,由 备份交换机来进行转发和处理,通常备份交换机的类型为 Fanout ,这样就能把所有消息都投递到与其绑 定的队列中,然后我们在备份交换机下绑定一个队列,这样所有那些原交换机无法被路由的消息,就会都 进入这个队列了。

当然,我们还可以建立一个报警队列,用独立的消费者来进行监测和报警。

备份结构图:

 声明备份交换机和之前声明交换机一样,只是在为一个交换机创建备份交换机的时候,同样要指定,和创建死信队列一样,指定对应的参数。

 如图所示,mandatory 参数与备份交换机可以一起使用的时候,如果两者同时开启,消息究竟何去何从?谁优先级高,经过上面结果显示答案是备份交换机优先级高

二、其他知识点

2.1幂等性问题

幂等性问题为解决消息被重复消费。

2.2消息重复消费

消费者在消费 MQ 中的消息时,MQ 已把消息发送给消费者,消费者在给 MQ 返回 ack 时网络中断, 故 MQ 未收到确认信息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但 实际上该消费者已成功消费了该条消息,造成消费者消费了重复的消息。

2.3解决思路

MQ 消费者的幂等性的解决一般使用全局 ID 或者写个唯一标识比如时间戳 或者 UUID 或者订单消费 者消费 MQ 中的消息也可利用 MQ 的该 id 来判断,或者可按自己的规则生成一个全局唯一 id,每次消费消 息时用该 id 先判断该消息是否已消费过。

 2.4优先队列

2.4.1使用场景

在我们系统中有一个订单催付的场景,我们的客户在天猫下的订单,淘宝会及时将订单推送给我们,如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒,很简单的一个功能对吧。

但是,tmall 商家对我们来说,肯定是要分大客户和小客户的对吧,比如像苹果,小米这样大商家一年起码能给我们创 造很大的利润,所以理应当然,他们的订单必须得到优先处理,而曾经我们的后端系统是使用 redis 来存 放的定时轮询,大家都知道 redis 只能用 List 做一个简简单单的消息队列,并不能实现一个优先级的场景,所以订单量大了后采用 RabbitMQ 进行改造和优化,如果发现是大客户的订单给一个相对比较高的优先级, 否则就是默认优先级。

添加优先级:

a.队列中代码添加优先级

b.消息中代码添加优先级

c.注意事项 

要让队列实现优先级需要做的事情有如下事情:队列需要设置为优先级队列,消息需要设置消息的优先 级,消费者需要等待消息已经发送到队列中才去消费因为,这样才有机会对消息进行排序

2.5惰性队列

2.5.1使用场景

RabbitMQ 从 3.6.0 版本开始引入了惰性队列的概念。惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持 更多的消息存储。

默认情况下,当生产者将消息发送到 RabbitMQ 的时候,队列中的消息会尽可能的存储在内存之中, 这样可以更加快速的将消息发送给消费者。即使是持久化的消息,在被写入磁盘的同时也会在内存中驻留 一份备份。

2.5.2两种模式

队列具备两种模式:default 和 lazy。默认的为 default 模式,在 3.6.0 之前的版本无需做任何变更。lazy 模式即为惰性队列的模式,可以通过调用 channel.queueDeclare 方法的时候在参数中设置,也可以通过 Policy 的方式设置,如果一个队列同时使用这两种方式设置的话,那么 Policy 的方式具备更高的优先级。 如果要通过声明的方式改变已有队列的模式的话,那么只能先删除队列,然后再重新声明一个新的。

总结:

1.发布确认高级两次回调,确认回调和消息回退.

2.消息备份,使用备份交换机.

3.优先队列,设置优先级,将消息排序后重新入队被消费者消费.

4.惰性队列,以防消息积压队列造成内存满溢,消息存入磁盘.

5.消息重复消费,幂等性解决.

 

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值