RabbitMQ学习总结(二)——Confirm消息确认与可靠性投递

RabbitMQ学习总结

前面讲到了使用消息队列解耦时,生产端只需负责将消息投递到消息队列就完事了,那么必须要考虑的问题就是如何保证生产端将消息可靠的投递到了MQ中

AMQP协议以及RabbitMQ考虑到了这一问题,因此在设计时添加了Confirm消息确认机制和Return机制,首先先讲一下这两个机制

一. Confirm消息确认机制和Return机制

Confirm消息确认机制: 生产者向MQ投递完消息后,要求MQ返回一个应答,生产者异步接收该应答,用来确定该消息是否正常的发送到了Broker, 从而保障消息的可靠性投递

Return消息返回机制:该机制用于处理一些不可路由的消息。如果生产在发送消息时,发现当前的exchange不存在或者指定的路由key找不到时,生产者可以开启该模式来监听这种不可达的消息,以进行后续。(如果不开启的话,broker会自动删除该消息)

这里要注意的是,只要消息到达了MQ就换返回Confirm消息,接下来MQ再去判断能不能找到路由方式,找不到再返回Return消息

Confirm消息确认机制的实现
  1. 首先在配置文件中设置
spring:
  rabbitmq:
    publisher-confirm-type: correlated

开启消息确认模式

  1. 实现ConfirmCallback接口
RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (ack){
                    log.info(correlationData.toString() + "发送成功");
                }else {
                    log.error(correlationData.toString() + "发送失败, 原因: " + cause);
                }
            }
        };

其中重写的方法包含三个参数:

  • correlationData:发送消息时设置的correlationData。由于confirm消息是异步监听的,因此需要在发送消息时传递一个correlationData,从而在返回confirm消息时判断其属于哪个消息,所以correlationData通常设置为消息的唯一ID;
  • ack:broker返回的应答,如果broker成功接收消息,则返回true代表接收成功,如果因为各种原因没有成功接收(如消息队列满了),则返回false

这里要注意,由于各种原因(如网络波动),生产端可能并没有收到confirm消息,因此不能将后续的补偿处理仅仅寄希望于在else内完成,else内做的补偿仅仅是在生产端收到confirm消息后nack的情况

  • cause: 如果没有被成功接收,则返回原因
  1. 为rabbitTemplate添加刚刚的Confirm监听器
rabbitTemplate.setConfirmCallback(confirmCallback());
  1. 利用该rabbitTemplate发送消息即可
rabbitTemplate.convertAndSend(exchange, routingkey, order, new CorrelationData(order.getId());
Return消息返回机制的实现

Return消息返回机制的实现与上面的Confirm消息确认机制的实现类似

  1. 在配置文件中设置
spring:
  rabbitmq:
    publisher-returns: true
    template:
      mandatory: true

注意,mandatory一定要设置为true,否则找不到路由规则的消息会被broker直接抛弃

  1. 实现returnCallback接口
RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyTest, String exchange, String routingKey) {
               log.error("消息{}路由失败,失败原因:{}",message.getMessageProperties().getMessageId(),replyTest);
            }
};
  1. 为rabbitTemplate添加刚刚的returnCallback监听器
rabbitTemplate.setReturnCallback(returnCallback);
  1. 利用该rabbitTemplate发送消息即可
rabbitTemplate.convertAndSend(exchange, routingkey, order, new CorrelationData(order.getId());

消息可靠性投递方案

方案一:利用Confirm消息确认机制

前面讲完了RabbitMQ自带的Confirm消息确认机制和Return机制,而实现消息可靠性投递的第一个方案就是利用该确认机制

实现思路如下:

  1. 在生产端向消息队列发送消息前,首先将业务信息和对应的消息信息入库(如生成订单时,需要修改数据库中的订单表和订单消息表),其中订单消息表中有一个记录该消息是否发送成功的字段
  2. 向消息队列发送该消息,并在发送前设置好Confirm消息的监听器
  3. 如果收到Confirm消息,代表该消息已发送成功,那么就可以将订单消息表中的发送状态改为发送成功
  4. 设置一定时任务去抓取订单消息表中没有没有发送成功的消息,并进行重新发送
  5. 如果重新发送了几次后消息都没有发送成功,则将其状态修改为发送失败,后续进行人工补偿

该方案的缺点是在发送消息前,需要进行两次落库操作(修改数据库中的订单表和订单消息表),因此会对性能造成一定影响

方案二:消息延迟投递,做二次确认,回调检查

实现思路如下:

  1. 生产者消息发送前,只需要将业务信息入库(如修改订单表)
  2. 向MQ发送该消息以及一条延迟消息(其中第一条消息由消费者接收,第二条消息由独立的Callback服务接收)
  3. 消费者收到第一条消息后,将接收成功的消息回送给MQ(该回送消息也由Callback服务接收)
  4. Callback服务收到消费者接收成功的消息,将该消息入库
  5. 一段时间后,Callback服务又收到生产者的延迟消息,它根据该延迟消息的id信息去查找数据库中有没有该条记录
  6. 如果查到了,说明该消息已成功投递且被消费者成功消费
  7. 如果没查到,说明该消息没有被消费者成功消费,可能是没有投递成功,这时Callback服务再去远程调用生产者告知其重新发送消息

该方案优点是在生产者端只需要入库一次,而将消息的入库操作独立到了Callback服务中去,提升了生产者端的性能。但是该方案实现较复杂,里面还有很多的细节值得考虑

你可能感兴趣的:(RabbitMQ)