Skip to content

基于容器化搭建 RabbitMQ 集群

回顾

  1. Docker swarm

  2. 基于 Docker Image: rabbitmq:3.11.3-management 部署单节点 RabbitMQ

    • rabbitmq - Official Image | Docker Hub

    • 部署节点

      shell
      # 端口说明,参见 https://www.rabbitmq.com/networking.html#ports
      docker run -d --name rabbitmq -e RABBITMQ_DEFAULT_VHOST=my_vhost -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=123456 -p5671:5671 -p5672:5672 -p15671:15671 -p15672:15672 rabbitmq:3.11.3-management
    • 访问 host:15672 使用账号密码登录可看到 RabbitMQ Management 页面

常见 RabbitMQ 分布式模式

Distributed RabbitMQ — RabbitMQ

  1. 主备模式
    • 主备模式
    1. 通过 haproxy 来配置
    2. 主节点提供读写功能,当主节点宕机后,从节点升级为主节点,提供服务
  2. 主从副本集群
    • 主从副本集群
    1. 队列数据只存在 Master 节点
    2. Slave 节点拥有除了队列数据外与 Master 节点相同的所有数据和状态
    3. Slave 节点需要连接到 Master 节点实现队列的转发
    4. 消费者可以连接到 Master 或 Slave 节点
    5. 只能保证了服务可以用,无法达到高可用,没有故障自动切换功能
    6. 适合于消息无需持久化的场合。当队列非持久化,且创建该队列的节点宕机,客户端才可以重连集群其他节点,并重新创建队列。若为持久化,只能等故障节点恢复。
  3. 镜像集群
    1. 镜像集群
    2. 镜像集群基于 主从副本集群,需要先搭建普通集群, 然后才能设置镜像队列
    3. 队列机制是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步
    4. 如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升 MQ 集群的整体高可用性。
  4. 多活模式(Federation 模式)

    Federation Plugin — RabbitMQ

    • 复杂的异地模式
  5. 远程模式(Shovel 模式)

    Shovel Plugin — RabbitMQ

    • 简单的异地模式
  6. 对比
    Federation and/or ShovelClustering
    可能会有多个不同宿主的不同逻辑实例(Brokers)集群来自于一个独立的逻辑实例
    实例可以运行在互不兼容的 RabbitMQ 和 Erlang 版本上结点必须运行在 RabbitMQ 和 Erlang 的兼容版本上
    实例可通过不可靠的广域网连接
    通过可选 TLS 的 AMQP 0-9-1 协议连接,需要设置合适特用户和权限
    实例必须通过可靠的局域网连接
    节点将通过可选 TLS 的链接使用共享密钥进行彼此的身份验证
    实例可以以任何设定的拓扑结构进行连接, 链接可是单或双向的所有节点双向连接到其他全部节点
    强调 CAP 理论中的可用性和分区容忍性(AP)强调 CAP 理论中的一致性和分区容忍性(CP)
    实例中某些交换机(exchanges)可能是联合(federated)的,某些可能是本地的集群中只有一种形式
    连接到实例的客户端只能使用那个实例的非独占(non-exclusive)队列(queues)连接到任何节点的客户端都可以在所有节点上使用非独占队列。

RabbitMQ Cluster

Clustering Guide — RabbitMQ > Cluster Formation and Peer Discovery — RabbitMQ

  • 构建集群的方法
    1. 手动使用 rabbitmqctl
    2. 通过在配置文件中列出群集节点以声明方式
    3. 以声明方式使用基于 DNS 的发现
    4. 以声明方式使用AWS (EC2) 实例发现(通过插件)
    5. 声明式使用Kubernetes 发现(通过插件)
    6. 以声明方式使用基于 Consul 的发现(通过插件)
    7. 声明式使用基于 etcd 的发现(通过插件)
  • 注意
    1. 一个集群中,节点名必须唯一。在不手动指定节点名(环境变量RABBITMQ_NODENAME设置)时,默认使用前缀(Rabbit)+主机名(hostname)对节点进行命名
    2. 一个集群中,节点之间通过主机名进行解析 IP,需要提供解析方法:
      1. DNS
      2. hosts 文件
    3. 节点之间通过 Erlang-Cookie 进行认证
      1. Cookie 存储位置
        1. RabbitMQ 服务端 Cookie 文件路径:/var/lib/rabbitmq/.erlang.cookie
        2. RabbitMQ Cli Cookie 文件路径:$HOME/.erlang.cookie
      2. Cookie 设置方法
        1. 先存放 Cookie 文件到服务端路径 /var/lib/rabbitmq/.erlang.cookie,再启动 MQ
        2. Docker Image 环境变量 RABBITMQ_ERLANG_COOKIE (疑似 3.7+已失效,3.11 不可用)
        3. 在 Docker Swarm 中,通过使用 --secret source=my-erlang-cookie,target=/var/lib/rabbitmq/.erlang.cookie 参数可以设置 Cookie 信息

手动使用 rabbitmqctl 创建集群

  1. Docker 创建网卡

    shell
    docker network create --driver bridge --subnet 172.18.0.0/16 --gateway 172.18.0.1 mynetwork
  2. 准备 Cookie 文件,并设置文件权限为 400

    shell
    echo 12345678 > /tmp/.erlang.cookie
    chmod 400 /tmp/.erlang.cookie
  3. 启动多个节点,并确保节点名不相同

    shell
    docker run -d --name rabbitmq_cluster1 --hostname mqcluster1 --network mynetwork --ip 172.18.0.5 -e RABBITMQ_DEFAULT_VHOST=my_vhost -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=123456 -v /tmp/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie -p5671:5671 -p5672:5672 -p15671:15671 -p15672:15672 rabbitmq:3.11.3-management
    docker run -d --name rabbitmq_cluster2 --hostname mqcluster2 --network mynetwork --ip 172.18.0.6 -e RABBITMQ_DEFAULT_VHOST=my_vhost -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=123456 -v /tmp/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie rabbitmq:3.11.3-management
    docker run -d --name rabbitmq_cluster3 --hostname mqcluster3 --network mynetwork --ip 172.18.0.7 -e RABBITMQ_DEFAULT_VHOST=my_vhost -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=123456 -v /tmp/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie rabbitmq:3.11.3-management
    
    # 查看节点的集群状态
    docker exec -it rabbitmq_cluster1 rabbitmqctl cluster_status
    docker exec -it rabbitmq_cluster2 rabbitmqctl cluster_status
    docker exec -it rabbitmq_cluster3 rabbitmqctl cluster_status
    # rabbitmq_cluster1 rabbit@mqcluster1
    # rabbitmq_cluster2 rabbit@mqcluster2
    # rabbitmq_cluster3 rabbit@mqcluster3
  4. 确定每个节点的 ip 信息

    shell
    docker inspect rabbitmq_cluster1 | grep IPAddress
    docker inspect rabbitmq_cluster2 | grep IPAddress
    docker inspect rabbitmq_cluster3 | grep IPAddress
    # rabbit@mqcluster1 172.18.0.5
    # rabbit@mqcluster2 172.18.0.6
    # rabbit@mqcluster3 172.18.0.7
  5. 修改 hosts 文件(如果是自定义网卡则不需要此步骤)

    shell
    docker exec -it rabbitmq_cluster1 /bin/bash -c "echo 172.18.0.6 mqcluster2 >> /etc/hosts"
    docker exec -it rabbitmq_cluster1 /bin/bash -c "echo 172.18.0.7 mqcluster3 >> /etc/hosts"
    
    docker exec -it rabbitmq_cluster2 /bin/bash -c "echo 172.18.0.5 mqcluster1 >> /etc/hosts"
    docker exec -it rabbitmq_cluster2 /bin/bash -c "echo 172.18.0.7 mqcluster3 >> /etc/hosts"
    
    docker exec -it rabbitmq_cluster3 /bin/bash -c "echo 172.18.0.5 mqcluster1 >> /etc/hosts"
    docker exec -it rabbitmq_cluster3 /bin/bash -c "echo 172.18.0.6 mqcluster2 >> /etc/hosts"
  6. 将节点加入集群

    shell
    docker exec -it rabbitmq_cluster2 rabbitmqctl stop_app
    # Stopping rabbit application on node rabbit@e8e0dfd712d1 ...
    docker exec -it rabbitmq_cluster2 rabbitmqctl reset
    # Resetting node rabbit@e8e0dfd712d1 ...
    docker exec -it rabbitmq_cluster2 rabbitmqctl join_cluster rabbit@mqcluster1
    # Clustering node rabbit@mqcluster2 with rabbit@mqcluster1
    docker exec -it rabbitmq_cluster2 rabbitmqctl start_app
    # Starting node rabbit@mqcluster2 ...
    # ...
  7. 完成 完成节点加入集群

  8. 删除一个节点

    shell
    docker exec -it rabbitmq_cluster3 rabbitmqctl stop_app
    # Stopping rabbit application on node rabbit@mqcluster3 ...
    docker exec -it rabbitmq_cluster3 rabbitmqctl reset
    # Resetting node rabbit@mqcluster3 ...
    docker exec -it rabbitmq_cluster3 rabbitmqctl start_app
    # Starting node rabbit@mqcluster3 ...
  9. 创建一个 RAM 节点

    RabbitMQ 集群中节点包括内存节点(RAM)、磁盘节点(Disk,消息持久化),集群中至少有一个 Disk 节点。

    1. 首次添加

      shell
      docker exec -it rabbitmq_cluster3 rabbitmqctl stop_app
      docker exec -it rabbitmq_cluster3 rabbitmqctl reset
      docker exec -it rabbitmq_cluster3 rabbitmqctl join_cluster --ram rabbit@mqcluster1
      docker exec -it rabbitmq_cluster3 rabbitmqctl start_app

      创建一个 RAM 节点

    2. 修改节点

      shell
       docker exec -it rabbitmq_cluster2 rabbitmqctl stop_app
       docker exec -it rabbitmq_cluster2 rabbitmqctl change_cluster_node_type ram
       # Turning rabbit@mqcluster2 into a ram node
       docker exec -it rabbitmq_cluster2 rabbitmqctl start_app

      转换一个 RAM 节点

  10. 当节点掉线 节点掉线

    • 队列不可用,数据只在该节点存放,也取不到了 节点掉线队列状态
  11. 当节点恢复

    • 节点队列数据恢复 节点恢复队列状态

创建镜像集群

  • 可以使用 rabbitmqctlmanagement 指定策略方式实现

  • 使用 rabbitmqctl 命令实现持久镜像队列

    rabbitmqctl(8) — RabbitMQ > Classic Queue Mirroring — RabbitMQ

    • `rabbitmqctl set_policy [-p vhost] [--priority priority] [--apply-to apply-to] name pattern definition

      shell
      # definition: JSON格式策略定义
      # 包括三个部分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/manual
      docker exec -it rabbitmq_cluster1 rabbitmqctl set_policy -p my_vhost  mypolicy '^' '{"ha-mode":"all","ha-sync-mode":"automatic"}'
      # Setting policy "mypolicy" for pattern "^" to "{"ha-mode":"all","ha-sync-mode":"automatic"}" with priority "0" for vhost "my_vhost" ...
  • 开启策略结果 策略制定队列信息

  • 当节点掉线 节点信息

    • 队列可用,且所属节点被变更。数据在别的节点也有,依然可以取到 队列信息

其他两种用来代替持久镜像队列的类型(使用镜像队列时可优先考虑)

  • Quorum Queues — RabbitMQ
    • 声明时类型选为 Quorum
    • 不需要设置策略,会自动选举 Leader
      • 队列信息
      • 队列详细信息
    • 当节点下线后,原节点被自动更新
      • 队列信息
      • 队列详细信息
  • Streams — RabbitMQ
    • 特点
      1. 持久化
      2. 不可变数据(append-only)
      3. 非破坏性消费者语义(non-destructive consumer semantics)
    • 使用场合
      1. 大扇出(Large fan-outs)
        1. 流将允许任意数量的消费者以非破坏性的方式使用来自同一队列的相同消息,从而不需要绑定多个队列
      2. 重放/时间旅行(Replay / Time-travelling)
        1. 流可以重复读取
        2. 流将允许用户在日志中的任何位置进行连接并从中进行读取
      3. 吞吐量性能(Throughput Performance)
        1. 基于日志的消息传递系统的吞吐量
        2. 流的设计以性能为主要目标
      4. 大型日志(Large logs)
        1. 流被设计为以最小的内存开销以有效的方式存储更大量的数据