RabbitMQ消息持久化总结

目录

一、简介

二、要点

三、使用方法

四、总结


一、简介

在RabbitMQ中,如果遇到RabbitMQ服务停止或者挂掉,那么我们的消息将会出现丢失的情况,为了在RabbitMQ服务重启的情况下,不丢失消息,我们可以将Exchange(交换机)、Queue(队列)与Message(消息)都设置为可持久化的(durable)。这样的话,能够保证绝大部分的消息不会被丢失,但是还有有一些小概率会发生消息丢失的情况。下面通过一个简单的示例总结在RabbitMQ中如何进行消息持久化。

二、要点

消息持久化主要是将交换机、队列以及消息设置为durable = true(可持久化的),要点主要有三个:

  • a. 声明交换机Exchange的时候设置 durable=true;
//public TopicExchange(String name, boolean durable, boolean autoDelete)
public TopicExchange exchange() {
        return new TopicExchange(EXCHANGE_NAME,true,false);
    }
  • b. 声明队列Queue的时候设置 durable=true;
//public Queue(String name, boolean durable)
@Bean
    public Queue queue() {
        //durable:是否将队列持久化 true表示需要持久化 false表示不需要持久化
        return new Queue(QUEUE_NAME, false);
    }
  • c. 发送消息的时候设置消息的 deliveryMode = 2;
使用convertAndSend方式发送消息,消息默认就是持久化的.
new MessageProperties() --> DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT --> deliveryMode = 2;

三、使用方法

这里我们声明两种队列,一个是持久化的,一个是非持久化的,我们测试在RabbitMQ服务重启的情况下,未被消费的消息是否还存在,消费者重启之后能否重新消费之前的消息。在RabbitMQ中,通过管理员方式(必须是管理员方式,否则可能会报错)运行CMD命令行执行下面的命令:

net stop RabbitMQ && net start RabbitMQ

【a】RabbitMQ配置类: RabbitMQ配置信息,绑定交换器、队列、路由键设置

package com.wsh.springboot.springbooy_rabbitmq_message_persistence.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @Description: RabbitMQ配置信息,绑定交换器、队列、路由键设置
 * @author: weishihuai
 * @Date: 2019/6/30 15:38
 * <p>
 * 如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,可以将交换机、队列、消息都进行持久化,这样可以保证绝大部分情况下消息不会丢失。
 * 但还是会有小概率发生消息丢失的情况(比如RabbitMQ服务器已经接收到生产者的消息,但还没来得及持久化该消息时RabbitMQ服务器就断电了),
 * 如果我们需要对这种小概率事件也要管理起来,那么我们要用到事务。(transaction/confirm机制)
 *
 * <p>
 * 说明:
 * 1. 队列持久化:需要在声明队列的时候设置durable=true,如果只对队列进行持久化,那么mq重启之后队列里面的消息不会保存
 * 如果需要队列里面的消息也保存下来,那么还需要对消息进行持久化;
 * <p>
 * 2. 消息持久化:设置消息的deliveryMode = 2,消费者重启之后还能够继续消费持久化之后的消息;
 * 使用convertAndSend方式发送消息,消息默认就是持久化的,下面是源码:
 * new MessageProperties() --> DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT --> deliveryMode = 2;
 * <p>
 * 3.重启mq: CMD命令行下执行 net stop RabbitMQ && net start RabbitMQ
 */
@Component
public class RabbitMQConfig {
    private static final String DURABLE_QUEUE_NAME = "durable_queue_name";
    private static final String DURABLE_EXCHANGE_NAME = "durable_exchange_name";
    private static final String ROUTING_KEY = "user.#";

    private static final String QUEUE_NAME = "not_durable_queue_name";
    private static final String EXCHANGE_NAME = "not_durable_exchange_name";

    @Bean
    public Queue durableQueue() {
//        public Queue(String name) {
//            this(name, true, false, false);
//        }
//        public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete)
        //不指定durable的话默认好像也是true

        //public Queue(String name, boolean durable)
        //durable:是否将队列持久化 true表示需要持久化 false表示不需要持久化
        return new Queue(DURABLE_QUEUE_NAME, true);
    }

    @Bean
    public TopicExchange durableExchange() {
//        public AbstractExchange(String name) {
//            this(name, true, false);
//        }
//        public AbstractExchange(String name, boolean durable, boolean autoDelete) {
//            this(name, durable, autoDelete, (Map)null);
//        }
        //声明交换机的时候默认也是持久化的
        return new TopicExchange(DURABLE_EXCHANGE_NAME);
    }

    @Bean
    public Binding durableBinding() {
        //如果exchange和queue都是持久化的,那么它们之间的binding也是持久化的。如果exchange和queue两者之间有一个持久化,一个非持久化,就不允许建立绑定
        return BindingBuilder.bind(durableQueue()).to(durableExchange()).with(ROUTING_KEY);
    }


    @Bean
    public Queue queue() {
        //public Queue(String name, boolean durable)
        //durable:是否将队列持久化 true表示需要持久化 false表示不需要持久化
        return new Queue(QUEUE_NAME, false);
    }

    @Bean
    public TopicExchange exchange() {
        return new TopicExchange(EXCHANGE_NAME,true,false);
    }

    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(exchange()).with(ROUTING_KEY);
    }

}

比较主要的点 都在代码中写了注释,这里就不用过多说明。

【b】生产者:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * @Description: 生产者
 * @author: weishihuai
 * @Date: 2019/6/30 15:38
 */
@Component
public class Producer {
    private static final Logger logger = LoggerFactory.getLogger(Producer.class);
    private static final String DURABLE_EXCHANGE_NAME = "durable_exchange_name";
    private static final String ROUTING_KEY = "user.add";

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage() {
        for (int i = 1; i <= 3; i++) {
            String message = "消息" + i;
            logger.info("【生产者】发送消息:" + message);
            rabbitTemplate.convertAndSend(DURABLE_EXCHANGE_NAME, ROUTING_KEY, message, new CorrelationData(UUID.randomUUID().toString()));
        }
    }

}

【c】消费者:

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @Description: 消费者
 * @author: weishihuai
 * @Date: 2019/6/30 15:38
 */
@Component
public class Consumer {
    private static final Logger logger = LoggerFactory.getLogger(Consumer.class);

    @RabbitListener(queues = "durable_queue_name")
    public void receiveMessage(String msg, Message message, Channel channel) {
        try {
            logger.info("【Consumer  receiveMessage】接收到消息为:[{}]", msg);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            logger.info("【Consumer  receiveMessage】接收消息后的处理发生异常", e);
        }
    }

}

【d】应用配置文件

server:
  port: 9999
spring:
  application:
    name: mq-message-persistence
  rabbitmq:
    host: 127.0.0.1
    virtual-host: /vhost
    username: wsh
    password: wsh
    port: 5672
    connection-timeout: 10000

【e】测试用例


@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbooyRabbitmqMessagePersistenceApplicationTests {

    @Autowired
    private Producer producer;

    @Test
    public void contextLoads() {
        producer.sendMessage();
    }

}

【f】运行结果

为了测试在MQ重启之后,消费者能够消费持久化之后的消息,这里可以先将消费者监听队列暂时注释掉,让生产者发送三条消息,但是没有消费者去消费,这样MQ重启之后,队列中还是存在三条消息。

启动项目,通过管理控制台可以看到成功创建两个交换机以及两个队列。

这时候,我们重启RabbitMQ服务,检查一下重启之后队列、消息是否还存在。

通过控制台,可见只剩下一个持久化的队列durable_queue_name,并且队列里面的消息还存在,另外一个not_durable_queue_name已经丢失,说明在该队列上的消息也已经丢失。

同理,交换机也类似。

这时候放开之前注释的消费者代码块,重启项目,通过控制台可以看到消费者成功消费了之前持久化的三条消息,由此证明了在MQ重启之后,消费者可以继续消费之前的消息

【g】关于默认持久化的说明

  • 我们使用new Queue()创建队列的时候,默认就是持久化的,即durable=true,下面是部分源码:

  • 创建交换机的时候,也是默认持久化交换机的,下面是构造器源码:

  • 使用convertAndSend方式发送消息,消息默认就是持久化的,下面是源码:
  public void convertAndSend(String exchange, String routingKey, Object object, CorrelationData correlationData) throws AmqpException {
        this.send(exchange, routingKey, this.convertMessageIfNecessary(object), correlationData);
    }
   

 protected Message convertMessageIfNecessary(Object object) {
        return object instanceof Message ? (Message)object : this.getRequiredMessageConverter().toMessage(object, new MessageProperties());
    }


 public MessageProperties() {
        this.deliveryMode = DEFAULT_DELIVERY_MODE;
        this.priority = DEFAULT_PRIORITY;
    }


public static final MessageDeliveryMode DEFAULT_DELIVERY_MODE;

 static {
        DEFAULT_DELIVERY_MODE = MessageDeliveryMode.PERSISTENT;
        DEFAULT_PRIORITY = 0;
    }

public static int toInt(MessageDeliveryMode mode) {
        switch(mode) {
        case NON_PERSISTENT:
            return 1;
        case PERSISTENT:
            return 2;
        default:
            return -1;
        }
    }

四、总结

以上就是关于在RabbitMQ中如何实现消息和队列的持久化,虽然不能说百分之百保证消息不会丢失,但是能够保证绝大部分不会丢失。在实际项目中,通常需要对消息进行持久化,因为不可能保证服务器永远不会出现down机情况。以上只是笔者学习的一些总结,希望能够对大家有所帮助,欢迎大家进行补充,一起学习。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值