articleList

11-RabbitMQ 高可用集群

2025/03/17 posted in  RabbitMQ
Tags: 

RabbitMQ 高可用普通集群模式介绍

  • 背景

    • 掌握了消息的可靠性投递,还有消费,假如 mq 节点宕机了怎么办?
    • 因此需要做多节点集群配置
  • 普通集群

    • 默认的集群模式, 比如有节点 node1 和 node2、node3 三个节点是普通集群,但是他们仅有相同的元数据,即交换机、队列的结构

    • 案例

      • 消息只存在其中的一个节点里面,假如消息 A,存储在 node1 节点,消费者连接 node1 节点消费消息时,可以直接取出来
      • 但如果消费者是连接的是其他节点,那 rabbitmq 会把 queue 中的消息从存储它的节点中取出,并经过连接节点转发后再发送给消费者
    • 问题

      • 假如 node1 故障,那 node2 无法获取 node1 存储未被消费的消息
      • 如果 node1 持久化后故障,那需要等 node1 恢复后才可以正常消费
      • 如果 ndoe1 没做持久化后故障,那消息将会丢失
      • 这个情况无法实现高可用性,且节点间会增加通讯获取消息,性能存在瓶颈
    • 项目中 springboot+amqp 里面需要写多个节点的配置,比如下面:

      • spring.rabbitmq.addresses = 192.168.1.1:5672,192.168.1.2:5672,192.168.1.3:5672
    • 该模式更适合于消息无需持久化的场景,如日志传输的队列

    • 注意:集群需要保证各个节点有相同的 token 令牌

      • erlang.cookie 是 erlang 的分布式 token 文件,集群内各个节点的 erlang.cookie 需要相同,才可以互相通信

RabbitMQ 高可用 mirror 镜像集群模式介绍

  • 镜像集群

    • 队列做成镜像队列,让各队列存在于多个节点中.和普通集群比较大的区别就是【队列 queue 的消息 message】会在集群各节点之间同步,且并不是在 consumer 获取数据时临时拉取,而普通集群则是临时从存储的节点里面拉取对应的数据

    • 结论

      • 实现了高可用性,部分节点挂掉后,不影响正常的消费
      • 可以保证 100% 消息不丢失,推荐 3 个奇数节点,结合 LVS+Keepalive 进行 IP 漂移,防止单点故障
    • 缺点

      • 由于镜像队列模式下,消息数量过多,大量的消息同步也会加大网络带宽开销,适合高可用要求比较高的项目
      • 过多节点的话,性能则更加受影响
    • 注意:集群需要保证各个节点有相同的 token 令牌

      • erlang.cookie 是 erlang 的分布式 token 文件,集群内各个节点的 erlang.cookie 需要相同,才可以互相通信
  • 其他集群

    • 还有其他通过插件形成的集群,比如 Federation 集群,大家有兴趣可以去了解下

RabbitMQ 高可用普通集群搭建

  • 清理单机和网络开发

    • 关闭原先的单节点
    • 阿里网络安全组开放对应的端口
    • 防火墙一定要关闭
  • 准备 3 个节点安装好 rabbitmq,形成集群

    • 记得每个节点间隔几十秒再启动,如果失败删除宿主机文件重新搭建
    #节点一,主节点,创建-v映射目录
    docker run -d --hostname rabbit_host1 --name rabbitmq1 -p 15672:15672 -p 5672:5672 -e RABBITMQ_NODENAME=rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=xdclass.net168  -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie_xdclass' --privileged=true -v /usr/local/rabbitmq/1/lib:/var/lib/rabbitmq -v /usr/local/rabbitmq/1/log:/var/log/rabbitmq rabbitmq:management
    
    #节点二,创建-v映射目录
    docker run -d --hostname rabbit_host2 --name rabbitmq2 -p 15673:15672 -p 5673:5672 --link rabbitmq1:rabbit_host1 -e RABBITMQ_NODENAME=rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=xdclass.net168 -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie_xdclass' --privileged=true -v /usr/local/rabbitmq/2/lib:/var/lib/rabbitmq -v /usr/local/rabbitmq/2/log:/var/log/rabbitmq rabbitmq:management
    
    #节点三,创建-v映射目录
    docker run -d --hostname rabbit_host3 --name rabbitmq3 -p 15674:15672 -p 5674:5672 --link rabbitmq1:rabbit_host1 --link rabbitmq2:rabbit_host2 -e RABBITMQ_NODENAME=rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=xdclass.net168 -e RABBITMQ_ERLANG_COOKIE='rabbitmq_cookie_xdclass' --privileged=true -v /usr/local/rabbitmq/3/lib:/var/lib/rabbitmq -v /usr/local/rabbitmq/3/log:/var/log/rabbitmq rabbitmq:management
    
  • 参数说明

    --hostname 自定义 Docker 容器的 hostname
    ​
    --link 容器之间连接,link 不可或缺,使得三个容器能互相通信
    ​
    --privileged=true 使用该参数,container 内的 root 拥有真正的 root 权限,否则容器出现 permission denied
    ​
    -v 宿主机和容器路径映射
    ​
    参数 RABBITMQ_NODENAME,缺省 Unix*: rabbit@$HOSTNAME
    参数 RABBITMQ_DEFAULT_USER=admin
    参数 RABBITMQ_DEFAULT_PASS=xdclass.net168
    ​
    Erlang Cookie 值必须相同,也就是一个集群内 RABBITMQ_ERLANG_COOKIE 参数的值必须相同, 相当于不同节点之间通讯的密钥,erlang.cookie 是 erlang 的分布式 token 文件,集群内各个节点的 erlang.cookie 需要相同,才可以互相通信
    
  • 配置集群

    节点一配置集群
    docker exec -it rabbitmq1 bash
    rabbitmqctl stop_app
    rabbitmqctl reset
    rabbitmqctl start_app
    exit
    
    节点二加入集群,--ram是以内存方式加入,忽略该参数默认为磁盘节点。
    docker exec -it rabbitmq2 bash
    rabbitmqctl stop_app
    rabbitmqctl join_cluster --ram rabbit@rabbit_host1
    rabbitmqctl start_app
    exit
    
    节点三加入集群,--ram是以内存方式加入,忽略该参数默认为磁盘节点。
    docker exec -it rabbitmq3 bash
    rabbitmqctl stop_app
    rabbitmqctl reset
    rabbitmqctl join_cluster --ram rabbit@rabbit_host1
    rabbitmqctl start_app
    exit
    
    #查看集群节点状态,配置启动了3个节点,1个磁盘节点和2个内存节点
    rabbitmqctl cluster_status
    
  • 访问节点一的 web 管控台,可以看到多个节点

    • 到此为止,我们已经完成了 RabbitMQ 普通模式集群的建立,启动了 3 个节点,1 个磁盘节点和 2 个内存节点
  • 测试

    • node1 主节点创建队列,发送消息(可以选择消息是否持久化)
    • node2 和 node3 通过节点自身的 web 管控台可以看到队列和消息
    • 问题:如果把 node1 节点停止,node2 和 node3 会收不到消息
    • 备注:如果是在非主节点(非磁盘节点)创建队列和发送消息,则其他队列可以显示
  • RabbitMQ 高可用普通集群 SpringBoot 测试

    • SpringBoot 项目配置集群

      #配置文件修改
      #消息队列
      spring:
        rabbitmq:
          addresses: 10.211.55.13:5672,10.211.55.13:5673,10.211.55.13:5674
          virtual-host: /dev
          password: xdclass.net168
          username: admin
          #开启消息二次确认,生产者到broker的交换机
          publisher-confirm-type: correlated
      
          #开启消息二次确认,交换机到队列的可靠性投递
          publisher-returns: true
          #为true,则交换机处理消息到路由失败,则会返回给生产者
          template:
            mandatory: true
      
          #消息手工确认ACK
          listener:
            simple:
              acknowledge-mode: manual
      
    • 其他不用变动,正常的发消息

      • 模拟异常

        • 先发送消息,然后停止 node1,启动 SpringBoot 项目,消息则消费不了,且报错
        • 重新启动 node1,启动 SpringBoot 项目,恢复正常

RabbitMQ 高可用 mirror 镜像集群策略配置

  • 背景

    • 前面搭建了普通集群,如果磁盘节点挂掉后,如果没开启持久化数据就丢失了,其他节点也无法获取消息,所以我们这个集群方案需要进一步改造为镜像模式集群
  • 策略 policy 介绍

    • rabbitmq 的策略 policy 是用来控制和修改集群的 vhost 队列和 Exchange 复制行为
    • 就是要设置哪些 Exchange 或者 queue 的数据需要复制、同步,以及如何复制同步
  • 创建一个策略来匹配队列

    • 路径:rabbitmq 管理页面 —> Admin —> Policies —> Add / update a policy

    • 参数: 策略会同步同一个 VirtualHost 中的交换器和队列数据

      • name:自定义策略名称
      • Pattern:^ 匹配符,代表匹配所有
      • Definition:ha-mode=all 为匹配类型,分为 3 种模式:all(表示所有的 queue)
    ha-mode: 指明镜像队列的模式,可选下面的其中一个
      all:表示在集群中所有的节点上进行镜像同步(一般都用这个参数)
      exactly:表示在指定个数的节点上进行镜像同步,节点的个数由ha-params指定
      nodes:表示在指定的节点上进行镜像同步,节点名称通过ha-params指定
    
    ha-sync-mode:镜像消息同步方式 automatic(自动),manually(手动)
    

    • 配置好后,+2 的意思是有三个节点,一个节点本身和两个镜像节点,且可以看到策略名称 xdclass_mirror

  • 集群重启顺序

    • 集群重启的顺序是固定的,并且是相反的
    • 启动顺序:磁盘节点 => 内存节点
    • 关闭顺序:内存节点 => 磁盘节点
    • 最后关闭必须是磁盘节点,否则容易造成集群启动失败、数据丢失等异常情况
  • SpringBoot2.x 项目整合 RabbitMQ 高可用 mirror 镜像集群

    • SpringBoot AMQP 单机配置

      spring:
        rabbitmq:
          host: 10.211.55.13
          port: 5672
          virtual-host: /
          password: guest
          username: guest
          # 投递到交换机
          publisher-confirm-type: correlated
      
          # 交换机到队列
          publisher-returns: true
          ##指定消息在没有被队列接收时是否强行退回还是直接丢弃,true是退回
          template:
            mandatory: true
      ​
          #开启手动确认消息,如果消息重新入对则会一直重试,可以配置重试次数
          listener:
            simple:
              acknowledge-mode: manual
      
    • 高可用镜像集群配置

      #消息队列
      spring:
        rabbitmq:
          addresses: 10.211.55.13:5672,10.211.55.13:5673,10.211.55.13:5674
          virtual-host: /dev
          password: xdclass.net168
          username: admin
          #开启消息二次确认,生产者到broker的交换机
          publisher-confirm-type: correlated
      
          #开启消息二次确认,交换机到队列的可靠性投递
          publisher-returns: true
          #为true,则交换机处理消息到路由失败,则会返回给生产者
          template:
            mandatory: true
      
          #消息手工确认ACK
          listener:
            simple:
              acknowledge-mode: manual
      
    • 高可用集群测试

      • 关闭消费者监听
      • 生产者发送一个消息
      • 停止节点一和节点二,web 管控台访问不了
      • 启动消费者监听,可以消费到消息