RabbitMq如何保证消息可靠性?

我们知道在涉及网络IO操作的时候,可能要面临失败的问题,如在RabbitMQ中,消息的可靠性是个很大的问题,可能会发生消息丢失,还有RabbitMQ 的消息默认存放在内存上面,如果不进行配置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外挂掉,消息就会丢失,所以就要对消息进行持久化处理。

下面对RabbitMQ可能发生消息丢失的场景进行说明。

一、生产者发送消息到MQ失败

在生产者发送消息时,可能由于网络等其他原因导致消息没有到MQ中,但是生产者又不知道消息到底到达没有,这就会造成消息的丢失,

解决这个问题可以使用事务机制和发送方确认机制,由于事务机制过于耗费性能所以一般不用,这里主要说说发送方确认机制。这个机制就是消息发送到MQ后,MQ会回一个确认收到的消息给我们,这个消息回调又分两个,ConfirmCallback和ReturnCallback.

1.ConfirmCallback:当消息成功到达exchange的时候触发的回调,但是他不能保证消息投递到目标 queue 里。
2.ReturnCallback:当消息成功到达exchange,但是没有队列与之绑定的时候触发的回调。

下面将演示这两个回调的使用。




进行基本配置
下面这段是配置Queue和DirectExchange,并且进行绑定,这里想说一下ConfirmCallback接口下的confirm方法参数,好吧,其实重点想说CorrelationData对象,我们在发送消息的时候需要配置一个CorrelationData对象,CorrelationData对象内部有 id 属性,用来表示当前消息唯一性,如果不去指定的话,会传null,回调时候confirm中的CorrelationData对象就是我们发送时候的对象,所以,在一些场景下如果用消息id做逻辑的话,需要在发送的时候指定。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class RabbitMqConfig {
    private Logger logger = LoggerFactory.getLogger(RabbitMqConfig.class);
    @Bean
    public Queue queue() {
        return new Queue("TestDirectQueue", true);
    }


    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange("TestDirectExchange", true, false);
    }
    @Bean
    Binding bindingDirect() {
        return BindingBuilder
                .bind(queue())
                .to(directExchange())
                .with("TestDirectRouting");
    }


    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {

        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String s) {
                if (ack) {
                   logger.info("{}发送{}",correlationData.getId(),ack?"成功":"失败");
                }
            }
        });
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message,
                                        int replyCode,
                                        String replyText,
                                        String exchange,
                                        String routingKey) {

                logger.info("returnedMessage回调消息={},错误码={},回复文字={},exchange={},routingKey={}",message,replyCode,replyText,exchange,routingKey);
            }
        });
        return rabbitTemplate;
    }
}

注意最后两个,否则接收不到回调。

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true

发送测试
这里使用UUID生成一个id,发送的时候发Map对象。


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
@CrossOrigin()
public class TestController {
    private Logger log = LoggerFactory.getLogger(TestController.class);

    @Autowired
    RabbitTemplate rabbitTemplate;

    @GetMapping("send")
    public String test(){
        Map<String,String> map =new HashMap<>();
        map.put("data","data");
        map.put("id", UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend("TestDirectExchange","TestDirectRouting",map,new CorrelationData(map.get("id")));
        return "OK";

    }
}

接收端

@Configuration
public class RabbitMQListener {
    private Logger logger = LoggerFactory.getLogger(RabbitMQListener.class);

    @RabbitListener(queues="TestDirectQueue")
    public void process(@Payload Map<String,String> map) {
        logger.info("收到消息{}",map);
    }
}

测试
在这里插入图片描述
如果此时进行解绑后在发送,就会收到ReturnCallback回调。

在这里插入图片描述
在这里插入图片描述

但是这个和rabbitTemplate.setMandatory(true);有关,如果不设置为true,ReturnCallback是不进行回调的,另外ReturnCallback也比ConfirmCallback先回调,即使ReturnCallback回调了找不到队列,那么ConfirmCallback中ack还是为true,这里要注意。

二、消息入队之后MQ宕机

这种情况就是MQ突然宕机了或者被关闭,所以就必须要对消息做持久化,当MQ重新启动之后消息还能重新恢复过来,当然还要对队列、交换器进行持久化。

我们在声明队列和交换器的时候,参数durable就是代表是否持久化,为true代表持久化,否则在RabbitMq重启后将丢失这些信息。

还有autoDelete 参数:当所有消费客户端连接断开后,是否自动删除队列,一般设置成false就行。

 @Bean
 public Queue queue() {
     return new Queue("TestDirectQueue", true);
 }
 @Bean
 public DirectExchange directExchange() {
     return new DirectExchange("TestDirectExchange", true, false);
 }
// 参数1 name :队列名
// 参数2 durable :是否持久化
// 参数3 exclusive :仅创建者可以使用的私有队列,断开后自动删除
// 参数4 autoDelete : 当所有消费客户端连接断开后,是否自动删除队列
new Queue(name, durable, exclusive, autoDelete);
// 参数1 name :交换器名
// 参数2 durable :是否持久化
// 参数3 autoDelete :当所有消费客户端连接断开后,是否自动删除队列
new DirectExchange(name, durable, autoDelete)

发送的消息默认就是持久化消息,当Rabbitmq中还有数据时候关闭他,在重启后依然存在。

三、消费者消费

最后就是消费者端的问题了,解决这个问题就是消费者进行消息确认,开启的配置如下:

spring.rabbitmq.listener.simple.acknowledge-mode=manual

他还有其他的模式:

AcknowledgeMode.NONE:不确认
AcknowledgeMode.AUTO:自动确认
AcknowledgeMode.MANUAL:手动确认

开启后,如果我们不手动确认消息,可能会重复的收到同样的消息,这就需要我们做系统幂等性。

channel.basicAck的作用就是告诉RabbitMq服务器我收到这条消息,已经被我消费了,可以在队列删掉了, 以后就不会再发了,参数deliveryTag代表了消息的唯一标识ID, 是一个单调递增的正整数,也可以通过message.getMessageProperties().getDeliveryTag()获取,multiple表示是否批量,true会一次性ack所有小于deliveryTag的消息。

@RabbitListener(queues="TestDirectQueue")
public void process(Message message, @Payload Map<String,String> map,
                    @Header(AmqpHeaders.DELIVERY_TAG) long tag,
                    Channel channel) throws IOException {
    logger.info("收到消息{}",map);
    channel.basicAck(tag,false);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值