RabbitMQ的高级特性

目录

数据导入

MQ的常见问题

消息可靠性问题

生产者确认机制

SpringAMQP实现生产者确认

消息持久化

消费者消息确认

失败重试机制

消费者失败消息处理策略

死信交换机

TTL

延时队列

安装插件

SpringAMQP使用插件

消息堆积问题

惰性队列

MQ的高可用

普通集群

获取Cookie

准备配置文件

创建实例文件夹

启动集群

测试创建队列

镜像集群

精确模式

all模式

nodes模式

测试

仲裁队列

使用AMQP实现仲裁队列


数据导入

资料下载地址:day05MQ高级

MQ的常见问题

  • 消息可靠性:如何确保消息至少被消费一次
  • 延迟消息问题:如何实现消息的延迟投递
  • 消息堆积问题:如何解决数百万消息堆积,无法及时消费的问题
  • 高可用问题:如何避免单点的MQ故障而导致的不可用问题

消息可靠性问题

消息丢失的三大类:

  • 发送时丢失:
    • 生产者发送的消息未送达到exchange
    • 消息到达exchange后未到达queue
  • MQ宕机,queue将消息丢失
  • consumer接收到消息后未消费就宕机

生产者确认机制

RabbitMQ提供了publisher confirm机制来避免消息发送到MQ过程中丢失。消息发送到MQ以后,会返回一个结果给发送者,表示消息是否处理成功。结果有两种请求:

  • publisher-confirm,发送者确认
    • 消息成功投递到交换机,返回ack。
    • 消息未投递到交换机,返回nack。
  • publisher-return,发送者回执
    • 消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。

RabbitMQ的高级特性_第1张图片

需要注意的是,确认机制发送消息时,需要给每个消息设置一个全局唯一id,以区分不同消息,避免ack冲突。

SpringAMQP实现生产者确认

在publisher模块中配置如下内容

spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true
    template:
      mandatory: true
  • publish-confirm-type:开启publisher-confirm,这里支持两种类型:
    • simple:同步等待confirm结果,直到超时
    • correlated:异步回调,定义ConfirmCallback,MQ返回结果时会回调这个ConfirmCallback
  • publish-returns:开启publish-return功能,同样是基于callback机制,不过是定义ReturnCallback
  • template.mandatory:定义消息路由失败时的策略。
    • true:则调用ReturnCallback;
    • false:则直接丢弃消点

在生产者模块中配置全局ReturnCallback(一个RabbitTemplate只能配置一个ReturnCallback)

@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate template = applicationContext.getBean(RabbitTemplate.class);
        template.setReturnCallback(((message, replyCode, replyText, exchange, routingKey) -> {
            log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                     replyCode,replyText,exchange,routingKey,message.toString());
        }));
    }
}

进行测试

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2SimpleQueue() throws InterruptedException {
        String routingKey = "simple";
        String message = "hello, spring amqp!";
        //准备消息id
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        correlationData.getFuture().addCallback(
                result->{
                    if (result.isAck()){
                        log.debug("消息发送成功,ID:{}",correlationData.getId());
                    }else {
                        log.error("消息发送失败,ID:{},原因{}",correlationData.getId(),result.getReason());
                    }
                },
                ex->{
                    log.error("消息发送异常,ID:{},原因:{}",correlationData.getId(),ex.getMessage());
                }
        );
        rabbitTemplate.convertAndSend("amq.topic", routingKey, message,correlationData);
    }
}

运行观察控制台

RabbitMQ的高级特性_第2张图片

测试一种路由失败的情况,这种情况可以正常发送到交换机,但是不能发送到Queue

RabbitMQ的高级特性_第3张图片

消息持久化

MQ默认是内存存储,当服务重启后,数据就会丢失。因此我们需要对交换机与队列进行持久化操作。在消费者模块添加如下代码

@Configuration
public class CommonConfig {
    @Bean
    public DirectExchange directExchange(){
        /**
         * name:交换机名称
         * durable:是否持久化
         * autoDelete:当没有队列绑定时是否删除
         */
        return new DirectExchange("direct.exchange",true,false);
    }

    @Bean
    public Queue simpleQueue(){
        /**
         * 使用Builder创建持久化队列
         * 使用 new Queue("名称")创建也可以,默认就是持久化的
         */
        return QueueBuilder.durable("simple.queue").build();
    }
}

启动消费者,就可以看到交换机与队列被持久到磁盘中,但需要注意的时,消息并没有持久化,当重启服务器消息还是会丢失。之前我们发送的消息是String类型,现在,我们使用AMQP的Message对消息进行持久化。

    @Test
    public void testDurableMessage() throws Exception {
        Message msg = MessageBuilder.withBody("hello spring".getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();
        rabbitTemplate.convertAndSend("simple.queue",msg);
    }

消费者消息确认

RabbitMQ支持消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。而SpringAMQP则允许配置三种确认模式:

  • manual:手动ack,需要在业务代码结束后,调用api发送ack。(业务处理成功后,调用channel.basicAck()手动签收,如果出现异常,则调用channle.basicNack()方法。)
  • auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack。
  • none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除。

manual模式对代码有一定入侵,需要添加发送ack的代码。因此不推荐使用

auto模式是通过Spring的AOP机制,来对消息进行自动确认。推荐使用

none模式不对消息进行确认,不使用

在消费者模块的配置文件中配置如下内容

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto

进行测试,在监听器处添加错误代码

@Component
public class SpringRabbitListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg) {
        System.out.println("消费者接收到simple.queue的消息:【" + msg + "】");
        System.out.println(1/0);
    }
}

进行debug观察Rabbit控制台

RabbitMQ的高级特性_第4张图片

对断点放行,会发现控制台抛出错误后立即再进入断点,那么就可以确定,MQ会再次投递失败的消息。取消断点放行,会发现控制台无休止进行打印错误,这种处理方式并不友好,因此我们可以自定义失败重试机制。

失败重试机制

当消费者消费消息抛出异常后,会将消息投递给MQ。而MQ又会立即投递给消费者。这样循环往复会导致MQ的消息处理飙升,带来不必要的压力。因此我们可以采用Spring的重试机制(在本地重试,不返回ack也不返回nack),来避免这种情况。

消费者模块的配置文件添加如下内容

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true #开启消费者失败重试
          initial-interval: 1000 #初始的失败等待时长为1s
          multiplier: 2 #下次失败的等待时长倍数,下次灯带时长 = multiplier * last-interval
          max-attempts: 3 #最大重试次数
          stateless: true # true无状态;false有状态,如果业务中包含事务,这里改为false

接下来进行测试

RabbitMQ的高级特性_第5张图片

首先是重试时间分别为1,2对应着配置中的1s与1s*2,如果还有下次重试次数那么重试时间就是1s*2*2。其次是在RabbitMQ中找不到这条错误的消息了。具体原因如下

消费者失败消息处理策略

在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecoverer接口来处理,它包含三种不同的实现:

  • RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
  • ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
  • RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机。流程图如下

RabbitMQ的高级特性_第6张图片

添加一个新的Config

@Configuration
public class ErrorMessageConfig {
    @Bean
    public DirectExchange errorMessageExchange(){
        return new DirectExchange("error.exchange");
    }

    @Bean
    public Queue errorQueue(){
        return new Queue("error.queue",true);
    }

    @Bean
    public Binding errorBinging(){
        return BindingBuilder.bind(errorQueue()).to(errorMessageExchange()).with("error");
    }

    @Bean
    public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate,"error.exchange","error");
    }
}

重启发送一条消息测试

观察Rabbit的控制台

RabbitMQ的高级特性_第7张图片

死信交换机

当一个队列中的消息满足下列情况之一时,可以成为死信 (dead letter):

  • 消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false。
  • 消息是一个过期消息,超时无人消费。
  • 要投递的队列消息堆积满了,最早的消息可能成为死信。

如果该队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中,而这个交换机称为死信交换机 (Dead Letter Exchange,简称DLX)

RabbitMQ的高级特性_第8张图片

与RepublishRecoverer的区别在于,该种方式是通过MQ进行转发,而RepublishRecoverer是通过消费者进行转发。如果只是保存失败的消息,那么推荐使用RepublishRecoverer。

TTL

TTL(time to live)超时时间分为两种情况:

  • 消息本身设置了超时时间
  • 消息所在的队列设置了超时时间

当消息到达存活时间后还没有被消费会被自动清除。如果同时设置了消息过期时间和队列过期时间,以时间短的为准,队列过期会将所有消息移除,如果一个已经过期的消息不在队列顶端时并不会立即移除,一旦它到了队列顶端则会进行判断是否移除。

延时队列

我们可以通过TTL来实现一个延时队列,对消息设置过期时间存放在ttl.queue,但是没有消费者监听该队列,等到过期之后,放入死信队列,而消费者监听死信队列,对过期消息进行消费,从而实现延时队列。具体流程如下

RabbitMQ的高级特性_第9张图片

接下来实现延时队列

编写ttl部分

@Slf4j
@Configuration
public class TTLMessageConfig {
    @Bean
    public Queue ttlQueue(){
        return QueueBuilder.durable("ttl.queue")
                .ttl(10000)//超时时间
                .deadLetterExchange("dl.exchange")//指定死信队列
                .deadLetterRoutingKey("dl")//死信队列的路由key
                .build();
    }

    @Bean
    public DirectExchange ttlExchange(){
        return new DirectExchange("ttl.exchange");
    }

    @Bean
    public Binding simpleBinding(){
        return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
    }
}

编写消费者方的监听

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "dl.queue",durable = "true"),
            exchange = @Exchange(name = "dl.exchange"),
            key = "dl"
    ))
    public void listenDlQueue(String msg){
        log.info("消费者接收到了延时消息:{}",msg);
    }

编写测试方法

    @Test
    public void testTTLMessage() throws Exception {
        Message msg = MessageBuilder.withBody("hello TTL".getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();
        rabbitTemplate.convertAndSend("ttl.exchange","ttl",msg);
        log.info("消息成功发送!");
    }

RabbitMQ的高级特性_第10张图片

RabbitMQ的高级特性_第11张图片

至此实现了延时处理消息。

但是通过死信队列来实现延迟队列的做法有点麻烦,我们可以使用RabbitMQ的原生插件DelayExchange来实现这个功能。

安装插件

基于Linux下载插件文档:Scheduling Messages with RabbitMQ | RabbitMQ - Blog

而我们是基于Docker下载插件,下载地址为:Community Plugins — RabbitMQ

RabbitMQ的高级特性_第12张图片

需要注意RabbitMQ与插件的版本对应关系即可。下载好之后,将其拖入mq的数据卷中。接下来安装插件,如要进入docker容器中

#进入容器
docker exec -it mq bash
#开启插件功能
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

RabbitMQ的高级特性_第13张图片

这样就安装完毕了。

DelayExchange插件的原理是对官方原生的Exchange做了功能的升级:

  • 将DelayExchange接受到的消息暂存在内存中(官方的Exchange是无法存储消息的)
  • 在DelayExchange中计时,超时后才投递消息到队列中

DelayExchange的声明是在RabbitMQ的控制台中

RabbitMQ的高级特性_第14张图片

其次,消息的延迟时间也需要在Exchange中指定

RabbitMQ的高级特性_第15张图片

控制台的使用方法肯定不符合开发中使用,因此我们接下来使用代码使用该插件

SpringAMQP使用插件

声明延迟队列交换机

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(value = "delay.queue",durable = "true"),
    exchange = @Exchange(name = "delay.exchange",delayed = "true"),
    key = "delay"
))
public void listenDelayQueue(String msg){
	log.info("接收到延迟消息:{}",msg);
}

编写测试类

    @Test
    public void testDelayMessage() throws Exception {
        Message msg = MessageBuilder.withBody("hello delay".getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .setHeader("x-delay",5000)
                .build();
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend("delay.exchange","delay",msg,correlationData);
        log.info("消息成功发送!");
    }

RabbitMQ的高级特性_第16张图片

因此,我们需要修改发送方的判断逻辑。判断是否存在receiveDelay值

@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate template = applicationContext.getBean(RabbitTemplate.class);
        template.setReturnCallback(((message, replyCode, replyText, exchange, routingKey) -> {
            //如果是延迟消息,则直接返回
            if (message.getMessageProperties().getReceivedDelay()>0) {
                return;
            }
            log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                    replyCode,replyText,exchange,routingKey,message.toString());
        }));
    }
}

消息堆积问题

当生产者发送消息的速度超过了消费者处理消息的速度,就会导致队列中的消息堆积,直到队列存储消息达到上限。最早接收到的消息,可能就会成为死信,会被丢弃,这就是消息堆积问题。

解决消息堆积有三种种思路:

  • 增加更多消费者,提高消费速度
  • 在消费者内开启线程池加快消息处理速度
  • 扩大队列容积,提高堆积上限

前两种情况是解决消息堆积问题,后一种是环节消息堆积问题。开启线程池也需要看情况,对于处理时间短的消息会因为频繁的CPU上下文切换导致CPU占用内存,因此开启线程池适用于处理消息时间长的情况。

惰性队列

从RabbitMQ的3.6.0版本开始,就增加了Lazy Queues的概念,也就是惰性队列。惰性队列的特征如下

  • 接收到消息后直接存入磁盘而非内存
  • 消费者要消费消息时才会从磁盘中读取并加载到内存
  • 支持数百万条的消息存储

之所以引用惰性队列,就是为了提高消息的堆积能力,传统的RabbitMQ的消息默认是存储在内存当中,当并发量高的时候,容易造成消息堆积,当占用内存百分之40时,MQ会暂时停止生产者的消息投递,将一部分消息保存在磁盘中,从到导致暂时的不可用状态,MQ的性能也就忽高忽低。而惰性队列是直接保存在磁盘当中,保证了MQ的稳定性,但损耗了性能。

使用AMQP实现惰性队列的声明

//基于注解
@RabbitListener(queuesToDeclare = @Queue(name = "lazy.queue"
        ,durable = "true",
        arguments = @Argument(name = "x-queue-mode",value = "lazy"))
)
public void listenLazyQueue(String msg){
    log.info("接收到延迟消息:{}",msg);
}

//基于Bean
@Configuration
public class LazyConfig {
    @Bean
    public Queue LazyQueue(){
        return QueueBuilder.durable("lazy.queue").lazy().build();
    }
}

MQ的高可用

同其他中间件解决高可用的方法一样,那就是搭建集群。

RabbitMQ的是基于Erlang语言编写,而Erlang又是一个面向并发的语言,天然支持集群模式。RabbitMQ的集群有两种模式:

  • 普通集群:是一种分布式集群,将队列分散到集群的各个节点,从而提高整个集群的并发能力。
  • 镜像集群:是一种主从集群,普通集群的基础上,添加了主从备份功能,提高集群的数据可用性。

镜像集群虽然支持主从,但主从同步并不是强一致的,在同步期间可能有数据丢失的风险。因此在RabbitMQ的3.8版本以后,推出了新的功能:仲裁队列来代替镜像集群,底层采用Raft协议确保主从的数据一致性。

普通集群

普通集群,或者叫标准集群 (classic cluster),具备下列特征:

  • 会在集群的各个节点间共享部分数据,包括:交换机、队列元信息。不包含队列中的消息。
  • 当访问集群某节点时,如果队列不在该节点,会从数据所在节点传递到当前节点并返回。
  • 队列所在节点宕机,队列中的消息就会丢失。

RabbitMQ的高级特性_第17张图片

现在开始搭建集群

获取Cookie

集群模式中的每个RabbitMQ节点使用Cookie来确定它们是否被允许相互通信。

要使两个节点能够通信,它们必须具有相同的共享秘密,称为Erlang cookie。Cookie只是一串最多255个字符的字母数字字符。

每个集群节点必须具有相同的Cookie。实例之间也需要它来相互通信。

从一个启动的MQ实例获取Cookie

docker exec -it mq cat /var/lib/rabbitmq/.erlang.cookie

获取内容为:XSLBYZHRKDOLAMZYTNML

准备配置文件

在/tmp目录新建一个配置文件 rabbitmq.conf:

cd /tmp
# 创建文件
touch rabbitmq.conf

文件内容如下:

loopback_users.guest = false #禁用guest用户访问
listeners.tcp.default = 5672 #访问端口
cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
cluster_formation.classic_config.nodes.1 = rabbit@mq1 #节点名称
cluster_formation.classic_config.nodes.2 = rabbit@mq2
cluster_formation.classic_config.nodes.3 = rabbit@mq3

接下来再创建一个配置文件用来存放Cookie信息

# 创建文件
touch .erlang.cookie
# 写入cookie
echo "XSLBYZHRKDOLAMZYTNML" > .erlang.cookie
# 修改cookie文件的权限为只读,不允许其他人修改
chmod 600 .erlang.cookie

创建实例文件夹

mkdir mq1 mq2 mq3
# 将配置文件拷贝到其他文件夹
cp rabbitmq.conf mq1
cp rabbitmq.conf mq2
cp rabbitmq.conf mq3
cp .erlang.cookie mq1
cp .erlang.cookie mq2
cp .erlang.cookie mq3

启动集群

准备一个docker网络

docker network create mq-net

启动容器

# 启动第一个容器
docker run -d --net mq-net \
-v ${PWD}/mq1/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin \
--name mq1 \
--hostname mq1 \
-p 8071:5672 \
-p 8081:15672 \
rabbitmq:tag
# 启动第二个容器
docker run -d --net mq-net \
-v ${PWD}/mq2/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin \
--name mq2 \
--hostname mq2 \
-p 8072:5672 \
-p 8082:15672 \
rabbitmq:3.8-management
# 启动第三个容器
docker run -d --net mq-net \
-v ${PWD}/mq3/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin \
--name mq3 \
--hostname mq3 \
-p 8073:5672 \
-p 8083:15672 \
rabbitmq:tag

访问8081端口

RabbitMQ的高级特性_第18张图片

测试创建队列

在8081中创建一个队列,在8082与8083中查看是否可以查看该队列信息

RabbitMQ的高级特性_第19张图片

RabbitMQ的高级特性_第20张图片

可以正常看到队列信息。

镜像集群

镜像集群:本质是主从模式,具备下面的特征

  • 交换机、队列、队列中的消息会在各个mq的镜像节点之间同步备份。
  • 创建队列的节点被称为该队列的主节点,备份到的其它节点叫做该队列的镜像节点。
  • 一个队列的主节点可能是另一个队列的镜像节点。
  • 所有操作都是主节点完成,然后同步给镜像节点。
  • 主节点宕机后,镜像节点会被当做主节点

RabbitMQ的高级特性_第21张图片

看图解Rabbit的镜像集群类似于ES的数据分片。

镜像集群有三个模式

ha-mode

ha-params

效果

准确模式

exactly

队列的副本量

count

集群中队列副本(主服务器和镜像服务器之和)的数量。count如果为1意味着单个副本:即队列主节点。count值为2表示2个副本:1个队列主和1个队列镜像。换句话说:count = 镜像数量 + 1。如果群集中的节点数少于count,则该队列将镜像到所有节点。如果有集群总数大于count+1,并且包含镜像的节点出现故障,则将在另一个节点上创建一个新的镜像。

all

(none)

队列在群集中的所有节点之间进行镜像。队列将镜像到任何新加入的节点。镜像到所有节点将对所有群集节点施加额外的压力,包括网络I / O,磁盘I / O和磁盘空间使用情况。推荐使用exactly,设置副本数为(N / 2 +1)。

nodes

node names

指定队列创建到哪些节点,如果指定的节点全部不存在,则会出现异常。如果指定的节点在集群中存在,但是暂时不可用,会创建节点到当前客户端连接到的节点。

精确模式

配置模式的命令

rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
  • rabbitmqctl set_policy:固定写法
  • ha-two:策略名称,自定义
  • "^two\.":匹配队列的正则表达式,符合命名规则的队列才生效,这里是任何以two.开头的队列名称
  • '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}': 策略内容
    • "ha-mode":"exactly":策略模式,此处是exactly模式,指定副本数量
    • "ha-params":2:策略参数,这里是2,就是副本数量为2,1主1镜像
    • "ha-sync-mode":"automatic":同步策略,默认是manual,即新加入的镜像节点不会同步旧的消息。如果设置为automatic,则新加入的镜像节点会把主节点中所有消息都同步,会带来额外的网络开销

all模式

rabbitmqctl set_policy ha-all "^all\." '{"ha-mode":"all"}'
  • ha-all:策略名称,自定义
  • "^all\.":匹配所有以all.开头的队列名
  • '{"ha-mode":"all"}':策略内容
    • "ha-mode":"all":策略模式,此处是all模式,即所有节点都会称为镜像节点

nodes模式

rabbitmqctl set_policy ha-nodes "^nodes\." '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
  • rabbitmqctl set_policy:固定写法
  • ha-nodes:策略名称,自定义
  • "^nodes\.":匹配队列的正则表达式,符合命名规则的队列才生效,这里是任何以nodes.开头的队列名称
  • '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}': 策略内容
    • "ha-mode":"nodes":策略模式,此处是nodes模式
    • "ha-params":["rabbit@mq1", "rabbit@mq2"]:策略参数,这里指定副本所在节点名称

测试

进入mq1容器

RabbitMQ的高级特性_第22张图片

RabbitMQ的高级特性_第23张图片

接下来宕机mq1观察队列变化

RabbitMQ的高级特性_第24张图片

随后再次启动mq1,two.queue队列也和mq1没有关系了。

仲裁队列

仲裁队列:仲裁队列是3.8版本以后才有的新功能,用来替代镜像队列,具备下列特征:

  • 与镜像队列一样,都是主从模式,支持主从数据同步(默认的count为5)
  • 使用非常简单,没有复杂的配置
  • 主从同步基于Raft协议,强一致

RabbitMQ的高级特性_第25张图片

RabbitMQ的高级特性_第26张图片

使用AMQP实现仲裁队列

修改配置文件,配置节点信息

spring:
  rabbitmq:
  	addresses: 192.168.116.131:8071,192.168.116.131:8072,192.168.116.131:8073
    username: admin
    password: admin
    virtual-host: /

创建队列

@Configuration
public class QuorumConfig {
    @Bean
    public Queue quorumQueue(){
        return QueueBuilder
        .durable("quorum.queue2")
        .quorum()
        .build();
    }
}

启动消费者就可以看到已经创建出quorum.queue2队列了

你可能感兴趣的:(rabbitmq,网络,分布式)