MQ消息队列之RabbitMQ的安装和原理

1. RabbitMQ安装

1.1 推荐方法:docker安装

  • 一行命令搞定:

    docker pull rabbitmq:management
    
  • 开启宿主机与容器的两个重要的端口映射即可:

    docker run -id -p 5672:5672 15672:15672 rabbitmq:management
    
  • RabbitMQ在安装好后,可以访问宿主机IP地址:15672使用其默认用户名和密码均为guest进行后台管理界面登录。

1.2. 在宿主机上安装(不推荐)

  • 安装包地址:http://tmp.forwardxiang.cn:9999/download/rabbitmq.tar.gz

1.2.1 安装环境依赖

  • 如果本地没有编译相关环境也需要通过包管理器安装:

    yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
    
  • 由于该中间件RabbitMQ是由Erlang语言编写的,因此需要安装对应环境:

    #下载安装包后安装 或者 可以通过Shell前端软件包管理器安装(例如yum和apt)
    rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
    
  • 如果安装Erlang时报错,请自行百度解决相关依赖问题,例如glibc版本过低等问题。

1.2.2 安装RabbitMQ包

  • Socat 是 Linux 下的一个多功能的网络工具,名字来由是 「Socket CAT」。其功能与有瑞士军刀之称的 Netcat 类似,可以看做是 Netcat 的加强版。RabbitMQ依赖该工具,需要先安装:

    rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
    
  • 安装Rabbit包:

    rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
    

1.2.3 开启管理界面及配置

  • 设置默认配置文件:

    cd /usr/share/doc/rabbitmq-server-3.6.5/
    cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
    
  • 开启管理界面并且使用默认提供的账号和密码:

    # 开启管理界⾯
    rabbitmq-plugins enable rabbitmq_management
    # 修改默认配置信息
    vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
    # ⽐如修改密码、配置等等,例如:
    #启用guest账号(未启动前有尖括号):loopback_users: guest
    
  • 重启这个RabbitMQ服务:

    service rabbitmq-server start # 启动服务
    service rabbitmq-server stop # 停⽌服务
    service rabbitmq-server restart # 重启服务
    

1.2.4 管理界面重要配置

  • 用户管理:不同用户对应可以操作的权限不同。最高:(administrator)

  • 虚拟主机:类似于MySQL中的不同数据库,是隔离开的不同空间。Virtual Hosts。

2. 消息队列中间件

  • 类似于计算机系统里的告诉缓存Cache的引入,是为了解决CPU和IO操作的效率不对等的问题,引入Cache后,能够提高整体的操作效率。

  • 假设A系统负责向B系统发送消息,如果A发送的速度远远高于B接收的速度,这会导致A系统的效率严重降低,A系统响应会变慢,而且无法处理其他问题

    • 这时候可以引入中间件C,他有两个特点,一方面他的速度可以匹配A系统,另一方面,他可以存储多条消息,并且以B系统可以接受的速度向B转发消息。这就解决了A系统面临的上述问题。

    • 引入C中间件后,尽管A系统在某个时间段内会有一个尖峰的消息发送速度,但经过C处理后,在B这边以B能接受的最大速度成一个平缓的速度趋势消费消息。

    • 由于中间件有一定的标准,使得应用扩展变得更容易,新增的应用只需要对接中间件即可完成需求。

2.1 消息队列优势

2.1.1 应用解耦

  • 使用MQ使得应用间解耦,提升容错性和可维护性。为引入中间件之前一个系统失败将导致生产者失败,进而导致其他消费者也失败,引入之后,只需等待或者修复出问题的消费者即可恢复整个系统,且其他系统并不会受影响。

    MQ消息队列之RabbitMQ的安装和原理_第1张图片

2.1.2 任务异步处理

  • 将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间,在生产者这端可以即使响应用户,提升用户体验和系统吞吐量(单位时间内处理请求的数目):

    MQ消息队列之RabbitMQ的安装和原理_第2张图片

MQ消息队列之RabbitMQ的安装和原理_第3张图片

2.1.3 削峰填谷

  • 消息被MQ保存起来了,然后系统就可以按照自己的消费能力来消费,比如每秒1000个消息,这样慢慢写入数据库,这样就不会卡死数据库了。

    MQ消息队列之RabbitMQ的安装和原理_第4张图片

MQ消息队列之RabbitMQ的安装和原理_第5张图片

2.2 消息队列劣势

  • 系统可用性降低

    • 系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用?
    • 高可用问题,一般来说都是通过建立集群来解决的。可以建立MQ集群。
  • 系统复杂度提高

    • MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
    • 保证消息没有被重复消费:保证消息队列的幂等性(一个请求(一条消息),不管重复来多少次,结果是不会改变的),给消息分配一个全局id,只要消费过该消息,将以K-V形式写入redis(或其他数据库主键操作)。那消费者开始消费前,先去redis中查询有没消费记录即可。
    • 保证消息顺序性:简单来说,给消费者固定队列,队列里的消息是顺序的,只要不出现多个消费者同时消费同一个消息即可,例如每个订单号的不同消息固定给一个消费者消费。
    • 处理消息丢失情况:以RabbitMQ为例
      • 生产者丢数据,未投放到MQ中,可以使用transaction机制或者confirm模式(推荐)
        • transaction机制就是说,发送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。
        • 一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后RabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了.如果RabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。
      • 消息队列丢数据:一般是开启持久化磁盘的配置。这个持久化配置可以confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,RabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。RabbitMQ重启后也能恢复数据。
      • 消费者丢数据:使用手动确认模式。自动确认模式虽然也不会丢失消息,但是如果一旦发生异常,会导致消息重新归队,且一直重试,直到成功,才能消费下一条消息。而手动确认模式,可以灵活处理是否回复确认,即使在某些异常的情况下也能进行。
  • 一致性问题

    • A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性?

    • **事务消息:**就是MQ提供的类似XA的分布式事务能力,通过事务消息可以达到分布式事务的最终一致性

    • **半事务消息:**就是MQ收到了生产者的消息,但是没有收到二次确认,不能投递的消息。

    • 其中第4步骤为二次确认,若没有收到则整个事务消息为半事务消息,MQ会在第5步骤进行回查,生产者会从数据库查询该状态并返回:

      MQ消息队列之RabbitMQ的安装和原理_第6张图片

2.3 常见MQ产品

  • RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,

  • 说几个特殊点:Kafka主要是为大数据准备。Rabbit是Erlang语言(Erlang 语言专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛)编写的产品,因此速度遥遥领先,为微妙级别(其他的为毫秒)。

  • 其他对比见下表:

    MQ消息队列之RabbitMQ的安装和原理_第7张图片

2.4 MQ实现原理

  • 实现MQ的大致有两种主流方式:AMQP( Advanced Message Queuing Protocol,高级消息队列协议)、JMS (Java Message Service ,Java 消息服务应用程序接口)
  • AMQP 与 JMS 区别:
    • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式。
    • JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此跨语言的。
    • JMS规定了两种消息模式;而AMQP的消息模式更加丰富

2.5 RabbitMQ结构

  • 完整结构图如下:

    MQ消息队列之RabbitMQ的安装和原理_第8张图片

  • Connection使用的是TCP连接,其中的Channel是基于此做的逻辑连接且相互隔离,本质上是共用同一个TCP连接的,以此节省TCP资源和开销。

  • Exchange是交换机,这个模块并不是MQ必须有的,在RabbitMQ里该模块负责决定哪些消息分配给哪些队列,因此,不同的分配策略导致有了6种不同的模式:简单模式work模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式RPC远程调用模式(不常用)

  • 模式总结:

    • 简单模式 HelloWorld 一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)
    • 工作队列模式 Work Queue 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
    • 发布订阅模式 Publish/subscribe 需要设置类型为fanout的交换机,并且交换和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
    • 路由模式 Routing 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列。
    • 通配符模式 Topic 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列。
  • Exchange交换机常用类型有:

    • Fanout:广播,将消息交给所有绑定到交换机的队列
    • Direct:定向,把消息交给符合指定routing key 的队列
    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

2.6 简单模式

  • 该模式下,一个生产者对应一个消费者,消息队列就类似一个邮箱,用于缓存消息:

    image-20210917134112695

  • 生产者只需要指定队列名称即可:

    channel.queueDeclare(QUEUE_NAME, true, false, false, null);
    channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
    
  • 消费者也一样,只需指定队列名称,并且在回调consumer里接收消息:

    channel.basicConsume(Producer.QUEUE_NAME, true, consumer);
    

2.7 Work queues模式

  • Work Queues 与入门程序的 简单模式 相比,多了一些消费者,多个消费端共同消费同一个队列中的消息,属于竞争关系。应用场景:对于任务过重任务较多情况使用工作队列可以提高任务处理的速度。

    MQ消息队列之RabbitMQ的安装和原理_第9张图片

  • 代码同简单模式,只不过,消费者从一个变成了多个。为了测试便于观察,可以将消费者设置为每次只消费一个消息:

    //一次只能接收并处理一个消息
    channel.basicQos(1);
    

2.8 发布订阅模式

  • 该模式下,引入了exchange的角色,且队列也增加到多个,生产者不再直接发送给队列,而是由exchange来决定转发到哪个队列中。Exchange(交换机)只负责转发消息不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

    MQ消息队列之RabbitMQ的安装和原理_第10张图片

  • 每个消费者监听自己的队列。 生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

  • 生产者代码里多了exchange的声明:

    channel.exchangeDeclare(FANOUT_EXCHAGE, BuiltinExchangeType.FANOUT);
    channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
    channel.queueDeclare(FANOUT_QUEUE_2, true, false, false, null);
    //队列绑定FANOUT交换机 无需指定routing key 指定了也无效
    channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHAGE, "");
    channel.queueBind(FANOUT_QUEUE_2, FANOUT_EXCHAGE, "");
    //指定了FANOUT交换机 无需指定队列名称了 队列已经和交换机绑定了
    channel.basicPublish(FANOUT_EXCHAGE, "", null, message.getBytes());
    
  • 消费者代码里也多了exchange与队列的绑定操作,但是还没有routing key:

    //声明交换机
    channel.exchangeDeclare(FANOUT_EXCHAGE,BuiltinExchangeType.FANOUT);
    channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
    //队列绑定交换机
    channel.queueBind(FANOUT_QUEUE_1, FANOUT_EXCHAGE, "");
    channel.basicConsume(FANOUT_QUEUE_1, true, consumer);
    
  • 交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。不存在竞争关系。

2.9 路由模式

  • 该模式下,除了引入Exchange外,还增加了队列与交换机的绑定的时候指定一个RoutingKey (路由key),也就是说,即使你将队列绑定在了交换机上,也不一定会收到消息,还需要匹配路由。

    MQ消息队列之RabbitMQ的安装和原理_第11张图片

  • 生产者代码中在队列绑定和发送消息时都多了指定route Key的部分:

    channel.exchangeDeclare(DIRECT_EXCHAGE, BuiltinExchangeType.DIRECT);
    channel.queueDeclare(DIRECT_QUEUE_INSERT, true, false, false, null);
    channel.queueDeclare(DIRECT_QUEUE_UPDATE, true, false, false, null);
    //队列绑定交换机
    channel.queueBind(DIRECT_QUEUE_INSERT, DIRECT_EXCHAGE, "insert");
    channel.queueBind(DIRECT_QUEUE_UPDATE, DIRECT_EXCHAGE, "update");
    channel.basicPublish(DIRECT_EXCHAGE, "insert", null,
    message.getBytes());
    channel.basicPublish(DIRECT_EXCHAGE, "update", null,
    message.getBytes());
    
  • 消费者代码中同样在绑定队列时指定了route Key:

    channel.exchangeDeclare(DIRECT_EXCHAGE,BuiltinExchangeType.DIRECT);
    channel.queueDeclare(DIRECT_QUEUE_INSERT, true, false, false,null);
    //队列绑定交换机
    channel.queueBind(DIRECT_QUEUE_INSERT,DIRECT_EXCHAGE,"insert");
    channel.basicConsume(DIRECT_QUEUE_INSERT, true, consumer);
    

2.10 主题模式

  • 该模式下,除了引入了Exchange(类型由direct变成了topic),另外还引入了通配符(#代表多个任意词,*代码一个任意词),用于更加灵活的绑定队列:

    MQ消息队列之RabbitMQ的安装和原理_第12张图片

  • 生产者代码和路由模式差不多,只不过exchange类型不同,发送消息时指定route Key:

    channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC);
    //绑定队列放在消费者 只需要有一方绑定即可
    channel.basicPublish(TOPIC_EXCHAGE, "item.insert", null,
    message.getBytes());
    channel.basicPublish(TOPIC_EXCHAGE, "item.update", null,
    message.getBytes());
    
  • 消费者代码中除了exchange类型改为topic外,在绑定接收队列时也可以使用通配符了:

    channel.queueDeclare(TOPIC_QUEUE_1, true, false, false, null);
    channel.queueDeclare(TOPIC_QUEUE_2, true, false, false, null);
    //队列绑定交换机 可以使用通配符了
    channel.queueBind(TOPIC_QUEUE_2, Producer.TOPIC_EXCHAGE,"item.*");
    channel.basicConsume(TOPIC_QUEUE_2, true, consumer);
    

2.11 Spring整合RabbitMQ

  • 引入依赖:

    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.1.7.RELEASEversion>
    dependency>
    <dependency>
        <groupId>org.springframework.amqpgroupId>
        <artifactId>spring-rabbitartifactId>
        <version>2.1.8.RELEASEversion>
    dependency>
    
  • 使用配置文件声明网络连接的重要参数(和JDBC参数类似),最后被spring配置文件读取和引用:

    rabbitmq.host=192.168.220.12
    rabbitmq.port=5672
    rabbitmq.username=xiangwei
    rabbitmq.password=xiangwei
    rabbitmq.virtual-host=/xiangwei
    
  • 生产者在spring配置文件里配置交换机、队列、以及通配符配置等:

    
    
    
    
    
    
    
    
    
  • 测试代码里如果需要加载配置文件,需要使用@ContextConfiguration注解:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:spring/spring-rabbitmq.xml")
    public class ProducerTest {
        @Autowired
        private RabbitTemplate rabbitTemplate;
        @Test
        public void topicTest(){
            /**
            * 参数1:交换机名称
            * 参数2:路由键名
            * 参数3:发送的消息内容
            */
            rabbitTemplate.convertAndSend("exchange", "key", "消息");
        }
    }
    
  • 消费者spring配置,定义队列后,设置监听器绑定这些队列即可:

    
    
    <bean id="springQueueListener"
          class="cn.forwardxiang.rabbitmq.listener.SpringQueueListener"/>
    <bean id="fanoutListener1"
          class="cn.forwardxiang.rabbitmq.listener.FanoutListener1"/>
    <rabbit:listener-container connection-factory="connectionFactory" autodeclare="true">
        <rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
        <rabbit:listener ref="fanoutListener1" queuenames="spring_fanout_queue_1"/>
    rabbit:listener-container>
    
  • 代码中实现监听器:

    public class TopicListenerStar implements MessageListener {
        public void onMessage(Message message) {
            try {
                String msg = new String(message.getBody(), "utf-8");
                System.out.printf("通配符*监听器:接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",                          message.getMessageProperties().getReceivedExchange(),                message.getMessageProperties().getReceivedRoutingKey(),              message.getMessageProperties().getConsumerQueue(),
                                  msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

2.12 Spring Boot整合RabbitMQ

  • 引入依赖,加入了启动器,无需引入context依赖了:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-amqpartifactId>
    dependency>
    <dependency>
    
  • application.yml配置文件:

    spring:
    rabbitmq:
    host: 192.168.220.12
    port: 5672
    virtual-host: /xiangwei
    username: xiangwei
    password: xiangwei
    
  • 使用全注解的方式用来配置队列和交换机:

    @Configuration
    public class RabbitMQConfig {
        //交换机名称
        public static final String ITEM_TOPIC_EXCHANGE =
            "springboot_item_topic_exchange";
        //队列名称
        public static final String ITEM_QUEUE = "springboot_item_queue";
        //声明交换机
        @Bean("itemTopicExchange")
        public Exchange topicExchange(){
            return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
        }
        //声明队列
        @Bean("itemQueue")
        public Queue itemQueue(){
            return QueueBuilder.durable(ITEM_QUEUE).build();
        }
        //绑定队列和交换机
        @Bean
        public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue,@Qualifier("itemTopicExchange") Exchange exchange){
            return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
        }
    }
    
  • 消费者一段使用全注解配置监听器:

    @Component
    public class MyListener {
    /**
    * 监听某个队列的消息
    * @param message 接收到的消息
    */
        @RabbitListener(queues = "springboot_item_queue")
        public void myListener1(String message){
            System.out.println("消费者接收到的消息为:" + message);
        }
    }
    

2.13 消息可靠投递

  • 确认模式:消息从 producer -->exchange 则会返回一个 confirmCallback
  • **退回模式:**消息从 exchange–>queue 投递失败则会返回一个 returnCallback

2.14 消费者回复

  • ack指Acknowledge,确认。 表示消费端收到消息后的确认方式

  • 有三种确认方式:

    • 自动确认:acknowledge=“none”
    • 手动确认:acknowledge=“manual”
    • 根据异常情况确认:acknowledge=“auto”,(不常用)
  • 自动确认,会在消费者收到消息后自动回复ack,即使后续的业务处理异常,该条消息也会丢失。而手动确认,则需要手动回复ack,这样MQ才会投递下一条消息,程序员可以灵活的决定是否回复ack。

2.15 消费端限流

  • 这个功能应用于削峰填谷很有用,需要开启ack手动确认,并在消费者监听器里配置perfetch属性:

    <rabbit:listener-container connection-factory="connectionFactory" concurrency="2" prefetch="3">
        <rabbit:listener ref="listener" queue-names="remoting.queue" />
    rabbit:listener-container>
    
  • 或者使用Java代码:

    channel.basicQos(10);//每次消费10条,相当于设置最大承受速度
    

2.16 消息过期时间

  • TTL(Time To Live,消息过期时间设置),可以针对某个队列设置所有消息的过期时间,也可以针对每个消息设置,RabbitMQ只对处于队头的消息判断是否过期(即不会扫描队列),所以,很可能队列中已存在死消息,但是队列并不知情。这会影响队列统计数据的正确性,妨碍队列及时释放资源。

  • 配置文件中对某个队列配置TTL参数:注意配置时间需要指定类型为Integer

    <rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
        
        <rabbit:queue-arguments>
            
            <entry key="x-message-ttl" value="100000" valuetype="java.lang.Integer">entry>
        rabbit:queue-arguments>
    rabbit:queue>
    
  • 利用MessagePostProcessor消息后处理对象, 配置消息单独过期代码:

    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws
            AmqpException {
            //1.设置message的信息 消息的过期时间5s
            message.getMessageProperties().setExpiration("5000");
            //2.返回该消息
            return message;
        }
    };
    //消息单独过期
    rabbitTemplate.convertAndSend("test_exchange_ttl", "ttl.hehe",
    "message ttl....",messagePostProcessor);
    

2.17 死信队列

  • 针对于RabbitMQ准确的说是,DLX(Dead Letter Exchange,死信交换机),因有的MQ没有交换机概念而统称为死信队列。当消息成为Dead message后,会从所在队列直接转给死信队列。

    MQ消息队列之RabbitMQ的安装和原理_第13张图片

  • 成为死信的三种情况:

    • 生产者到队列:消息队列长度达到最大的限制,超出长度后的消息成为死信。
    • 队列到消费者:消费者明确拒绝接收消息,并且未设置消息重新回到原队列。
    • **消息在队列里:**原队列存在消息过期设置,消息达到时间未被消费。
  • 正常消息成为死信的参数设置是绑定在正常队列设置里的,且需要指定死后的DLX以及route Key:

    <rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
        
        <rabbit:queue-arguments>
            
            <entry key="x-dead-letter-exchange" value="exchange_dlx" />
            
            <entry key="x-dead-letter-routing-key" value="dlx.hehe" />
            
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
            
            <entry key="x-max-length" value="10" value-type="java.lang.Integer" />
        rabbit:queue-arguments>
    rabbit:queue>
    
    <rabbit:queue name="queue_dlx" id="queue_dlx">rabbit:queue>
    <rabbit:topic-exchange name="exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx">rabbit:binding>
        rabbit:bindings>
    rabbit:topic-exchange>
    

2.18 延迟队列

  • 虽然RabbitMQ没有单独提供延迟队列功能,但是可以利用TTL+死信队列 组合实现延迟队列的效果。
  • 将某个需要延迟消费的消息或者其所在的队列设置TTL时间,且在这个期间不会有消费者来消费,当时间到达TTL时间后,会将其消息转给死信队列,在配置时指定专用来处理该业务消息的死信队列,就可以实现延迟队列的功能了。

2.19 集群配置

  • 找到配置集群的关键文件,magic cookie文件:

    MQ消息队列之RabbitMQ的安装和原理_第14张图片

  • **同步cookie:**其他节点如果要成为集群,就必须使得这些节点的该文件保持一致,请在各节点手动覆盖或新建该文件保持内容一致。

  • **设置hosts:**将每个节点的/etc/hosts添加映射:

    #(只是示例,请填写自己的ip和主机名称)
    172.18.8.157 live-mq-01
    172.18.8.158 live-mq-02
    172.18.8.161 live-mq-03
    
  • 重启服务并使用detached参数启动各节点:

    systemctl restart rabbitmq-server
    rabbitmqctl stop
    rabbitmq-server -detached
    
  • **组建集群:**让新的节点加入到最先启动的节点中去:

    rabbitmqctl stop_app
    rabbitmqctl join_cluster rabbit@live-mq-01#这里写第一个节点主机名称
    rabbitmqctl start_app
    
  • 每个集群至少要有一个disk节点,上面方式组建后的每个节点都是disk节点,更改为ram内存节点:

    rabbitmqctl stop_app
    rabbitmqctl change_cluster_node_type ram
    rabbitmqctl start_app
    
  • 查看集群状态:

    rabbitmqctl cluster_status
    
  • 设置镜像队列高可用:默认的普通集群,exchange,binding等数据可以复制到集群各节点,但是各节点只是拥有相同的队列元数据,即队列结构,队列实体只存在于创建该队列的节点,即队列内容不会复制。设置为镜像队列后队列内容会被复制到各个节点:

    #查看镜像队列
    rabbitmqctl list_policies
    #对队列名称以hello开头的所有队列进行镜像,并在集群的两个节点上完成镜像
    rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
    

    参数规则:rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]

    • -p Vhost: 可选参数,针对指定vhost下的queue进行设置
    • Name: policy的名称
    • Pattern: queue的匹配模式(正则表达式)
    • Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
      • ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes,all表示在集群所有的节点上进行镜像, exactly表示在指定个数的节点上进行镜像,节点的个数由ha-params指定,nodes表示在指定的节点上进行镜像,节点名称通过ha-params指定。
      • ha-params: ha-mode模式需要用到的参数
      • ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
      • Priority: 可选参数, policy的优先级

你可能感兴趣的:(运维相关,rabbitmq,linux,docker,消息队列)