09.RabbitMQ集群
# 01.RabbitMQ各种模式说明
# 1.1 单机模式
一般就是本地启动进行开发,不用于生产用
# 1.2 主备模式
- 实现 rabbitMQ 的高可用集群,一般在并发和数据量不高的情况下,这种模式非常的好用且简单。
- 也就是一个主/备方案,主节点提供读写,备用节点不提供读写。
- 如果主节点挂了,就切换到备用节点,原来的备用节点升级为主节点提供读写服务
- 当原来的主节点恢复运行后,原来的主节点就变成备用节点,和 activeMQ 利用 zookeeper 做主/备一样,也可以一主多备。
# 1.3 远程模式
- 远程模式可以实现双活的一种模式,简称 shovel 模式
- 所谓的 shovel 就是把消息进行不同数据中心的复制工作,可以跨地域的让两个 MQ 集群互联,远距离通信和复制。
- Shovel 就是我们可以把消息进行数据中心的复制工作,我们可以跨地域的让两个 MQ 集群互联。
- rabbitMQ 比较早期的架构模型了,现在很少使用了。
# 1.4 多活模式
- 多活模式:这种模式也是实现异地数据复制的主流模式
- 因为Shovel模式配置比较复杂,所以一般来说实现异地集群都是使用这种双活或者多活模型来实现的。
- 这种模型需要依赖rabbitmq的
federation
插件,可以实现持续的可靠的AMQP数据通信 - RabbitMQ部署架构采用双中心模式(多中心) , 那么在两套(或多套)数据中心中各部署一套RabbitMQ集群
- 各中心的RabbitMQ服务除了需要为业务提供正常的消息服务外,中心之间还需要实现部分队列消息共享。
# 1.5 普通集群
- 默认模式,RabbitMQ集群中节点包括内存节点、磁盘节点。
- 内存节点就是将所有数据放在内存,磁盘节点将数据放在磁盘上。
- 如果在投递消息时,打开了消息的持久化,那么即使是内存节点,数据还是安全的放在磁盘。
- 那么内存节点的性能只能体现在资源管理上,比如增加或删除队列(queue),虚拟主机(vrtual hosts),交换机(exchange)等,发送和接受message速度同磁盘节点一样。
- 一个集群至少要有一个磁盘节点。
- 一个rabbitmq集群中可以共享user,vhost,exchange等
- 所有的数据和状态都是必须在所有节点上复制的,对于queue根据集群模式不同,应该有不同的表现。
- 在集群模式下只要有任何一个节点能够工作,RabbitMQ集群对外就能提供服务。
举例说明
- 以两个节点(rabbit01、rabbit02)为例来进行说明。
- 对于 Queue 来说,消息实体只存在于其中一个节点 rabbit01(或者 rabbit02)
- rabbit01 和 rabbit02 两个节点仅有相同的元数据,即队列的结构
- 当消息进入 rabbit01 节点的 Queue 后,consumer 从 rabbit02 节点消费时
- RabbitMQ 会临时在 rabbit01、rabbit02 间进行消息传输,把 A 中的消息实体取出并经过 B 发送给 consumer
- 所以 consumer 应尽量连接每一个节点,从中取消息。
- 即对于同一个逻辑队列,要在多个节点建立物理 Queue。
- 否则无论 consumer 连 rabbit01 或 rabbit02,出口总在 rabbit01,会产生瓶颈。
- 当 rabbit01 节点故障后,rabbit02 节点无法取到 rabbit01 节点中还未消费的消息实体。
- 如果做了消息持久化,那么得等 rabbit01 节点恢复,然后才可被消费;
- 如果没有持久化的话,就会产生消息丢失的现象。
# 1.6 镜像集群
非常经典的 mirror 镜像模式,保证 100% 数据不丢失。
在实际工作中也是用得最多的,并且实现非常的简单,一般互联网大厂都会构建这种镜像集群模式。
把需要的队列做成镜像队列,存在与多个节点属于 RabbitMQ 的 HA 方案。
消息实体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取
该模式带来的副作用也很明显,除了降低系统性能外
如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉
所以在对可靠性要求较高的场合中适用。
# 02.镜像集群原理
# 2.1 镜像队列服务提供方式
镜像模式会主动将各node内的数据同步到其他节点
保证集群内各主机数据一致性,集群之间会自动同步数据
对节点数量没有要求,两个或者三个都可以
# 2.2 关于node节点
- queue有master节点和slave节点。
- 要强调的是,
在RabbitMQ中master和slave是针对一个queue而言的
- 而不是一个node作为所有queue的master,其它node作为slave
- 一个queue第一次创建的node为它的master节点,其它node为slave节点。
# 2.3 获取数据方法
如上图所示,在镜像队列集群模式中,对某个queue来说,只有master对外提供服务,而其他slave只提供备份服务
在master所在节点不可用时,选出一个slave作为新的master继续对外提供服务
无论客户端的请求打到master还是slave最终数据都是从master节点获取。
当请求打到master节点时,master节点直接将消息返回给client
同时master节点会通过GM协议 (opens new window)将queue的最新状态广播到slave节点。
GM保证了广播消息的原子性,即要么都更新要么都不更新。
当请求打到slave节点时,slave节点需要将请求先重定向到master节点
master节点将将消息返回给client,同时master节点会通过GM协议将queue的最新状态广播到slave节点。
所以,多个客户端连接不同的镜像队列不会产生同一message被多次接受的情况。
# 2.4 RabbitMQ集群处理新增节点
- 如果有新节点加入,RabbitMQ不会同步之前的历史数据,新节点只会复制该节点加入到集群之后新增的消息。
- 既然master节点退出集群会选一个slave作为master
- 那么如果不幸选中了一个刚刚加入集群的节点怎么办?那消息不就丢了吗?
- 这里您可以把心放到肚子里,RabbitMQ集群内部会维护节点的状态是否已经同步
- 使用rabbitmqctl的synchronised_slave_pids参数,就可以查看状态。
- 如果slave_pids和synchronised_slave_pids里面的节点是一致的,那说明全都同步了
- 如果不一致很容易比较出来哪些还没有同步,集群只会在“最老”的slave节点之间选一个出来作为新的master节点
- 另外对于node节点的重启也是按照新节点来处理的。
# 2.5 镜像队列注意点
- 镜像队列不能作为负载均衡使用,因为每个声明和消息操作都要在所有节点复制一遍。
- ha-mode参数和durable declare对exclusive队列都不生效,因为exclusive队列是连接独占的,当连接断开,队列自动删除。
- 所以实际上这两个参数对exclusive队列没有意义。
- 每当一个节点加入或者重新加入(例如从网络分区中恢复回来)镜像队列,之前保存的队列内容会被清空。
- 对于镜像队列,客户端basic.publish操作会同步到所有节点;
- 而其他操作则是通过master中转,再由master将操作作用于salve。
- 比如一个basic.get操作,假如客户端与slave建立了TCP连接,首先是slave将basic.get请求发送至master
- 由master备好数据,返回至slave,投递给消费者。
# 03.镜像集群搭建
# 3.1 软件介质
软件 | 版本 | 备注 |
---|---|---|
rabbitmq | 3.8.8 | 需要安装对应的erlang版本 |
erlang | 23.x | rabbitmq对应erlang版本 (opens new window) |
# 3.2 主机资源
主机名 | 操作系统 | IP | 备注 |
---|---|---|---|
rabbitmq1 | centos7.4 | 192.168.1.1 | 磁盘节点,管理节点 |
rabbitmq2 | centos7.4 | 192.168.1.2 | 内存节点 |
rabbitmq3 | centos7.4 | 192.168.1.3 | 内存节点 |
# 3.3 hosts设置
[root@k8s-node2 aaa]# vim /etc/hosts
192.168.1.1 rabbitmq1
192.168.1.2 rabbitmq2
192.168.1.3 rabbitmq3
2
3
4
# 3.4 分别在以上三台主机上执行
# 1. erlang安装
# 下载erlang软件包源
wget https://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
sudo yum install epel-release -y
# 安装erlang软件包源
sudo rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
# 安装erlang环境
sudo yum install erlang -y
2
3
4
5
6
7
- 验证
[root@rabbitmq1 ~]# erl
Erlang/OTP 23 [erts-11.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Eshell V11.1 (abort with ^G)
2
3
# 2. rabbitmq安装
# 下载介质源
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.8/rabbitmq-server-3.8.8-1.el7.noarch.rpm
# 安装介质源
yum install -y rabbitmq-server-3.8.8-1.el7.noarch.rpm
# 打开开启动
systemctl enable rabbitmq-server
# 启动服务
systemctl start rabbitmq-server
# 查看服务状态
systemctl status rabbitmq-server
2
3
4
5
6
7
8
9
10
- 验证
[root@rabbitmq1 ~]$ systemctl status rabbitmq-server.service
● rabbitmq-server.service - RabbitMQ broker
Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; enabled; vendor preset: disabled)
Active: active (running) since Sat 2020-12-05 15:34:50 UTC; 13h ago
Main PID: 11663 (beam.smp)
Status: "Initialized"
CGroup: /system.slice/rabbitmq-server.service
├─11663 /usr/lib64/erlang/erts-11.1/bin/beam.smp -W w -K true -A 128 -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -P 1048576 -t 5000000 -stbt db -zdbbl 128000 -- -root ...
├─11844 erl_child_setup 32768
├─11918 inet_gethost 4
└─11919 inet_gethost 4
2
3
4
5
6
7
8
9
10
11
# 3.5 创建集群
- 将rabbitmq1作为群集的管理节点
# 1.配置集群基础环境
- copy erlang集群文件到rabbitmq2、rabbitmq3的
/var/lib/rabbitmq/
目录中 - 将重启
systemctl restart rabbitmq-server
rabbitmq2、rabbitmq3的服务
# 查看erlang分布式集群文件
[root@rabbitmq1 ~]# ls -al /var/lib/rabbitmq
total 8
drwxr-xr-x 3 rabbitmq rabbitmq 42 Oct 30 21:47 .
drwxr-xr-x. 27 root root 4096 Oct 30 21:20 ..
-r-------- 1 rabbitmq rabbitmq 20 Oct 30 00:00 .erlang.cookie
drwxr-x--- 4 rabbitmq rabbitmq 135 Oct 30 21:47 mnesia
# 查看.erlang.cookie内容
[root@rabbitmq1 rabbitmq]# cat .erlang.cookie
SFWVLUCDUUVPIVRJDWTE[root@rabbitmq1 rabbitmq]#
# copy .erlang.cookie 文件到其它主机对应的目录中
[root@rabbitmq1 rabbitmq]# scp .erlang.cookie root@rabbitmq2:/var/lib/rabbitmq/
root@rabbitmq2's password:
.erlang.cookie 100% 20 27.0KB/s 00:00
[root@rabbitmq1 rabbitmq]# scp .erlang.cookie root@rabbitmq3:/var/lib/rabbitmq/
root@rabbitmq3's password:
.erlang.cookie 100% 20 36.7KB/s 00:00s
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2.将节点加入集群中
- 将rabbitmq2、rabbitmq3加入群集
# 在rabbitmq1上看群集的状态
[root@rabbitmq1 ~]# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbitmq1 ...
Basics
Cluster name: rabbit@rabbitmq1
Disk Nodes
rabbit@rabbitmq1
Running Nodes
rabbit@rabbitmq1
Versions
rabbit@rabbitmq1: RabbitMQ 3.8.8 on Erlang 23.1
Maintenance status
Node: rabbit@rabbitmq1, status: not under maintenance
Alarms
(none)
Network Partitions
(none)
Listeners
Node: rabbit@rabbitmq1, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@rabbitmq1, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit@rabbitmq1, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Feature flags
Flag: drop_unroutable_metric, state: disabled
Flag: empty_basic_get_metric, state: disabled
Flag: implicit_default_bindings, state: enabled
Flag: maintenance_mode_status, state: enabled
Flag: quorum_queue, state: enabled
Flag: virtual_host_metadata, state: enabled
# 在rabbitmq2操作加入群集
# 1.停止rabbitmq2上的服务
[root@rabbitmq2 ~]# rabbitmqctl stop_app
Stopping rabbit application on node rabbit@rabbitmq2 ...
# 2.停止rabbitmq2上的服务
[root@rabbitmq2 ~]# rabbitmqctl reset
Resetting node rabbit@rabbitmq2 ...
# 2.将rabbitmq2加入集群,--ram是以内存方式加入
[root@rabbitmq2 ~]# rabbitmqctl join_cluster --ram rabbit@rabbitmq1
Clustering node rabbit@rabbitmq2 with rabbit@rabbitmq1
# 3.启动rabbitmq2上的服务
[root@rabbitmq2 ~]# rabbitmqctl start_app
Starting node rabbit@rabbitmq2 ...
# rabbitmq3以同样的方式加入即可
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- 验证:
# 查看群集状态
[root@rabbitmq1 ~]# rabbitmqctl cluster_status
Cluster status of node rabbit@rabbitmq1 ...
Basics
Cluster name: rabbit@rabbitmq1
Disk Nodes
rabbit@rabbitmq1
RAM Nodes
rabbit@rabbitmq2
rabbit@rabbitmq3
Running Nodes
rabbit@rabbitmq1
rabbit@rabbitmq2
rabbit@rabbitmq3
Versions
rabbit@rabbitmq1: RabbitMQ 3.8.9 on Erlang 21.3.8.18
rabbit@rabbitmq2: RabbitMQ 3.8.9 on Erlang 21.3.8.18
rabbit@rabbitmq3: RabbitMQ 3.8.9 on Erlang 21.3.8.18
Maintenance status
Node: rabbit@rabbitmq1, status: not under maintenance
Node: rabbit@rabbitmq2, status: not under maintenance
Node: rabbit@rabbitmq3, status: not under maintenance
Alarms
(none)
Network Partitions
(none)
Listeners
Node: rabbit@rabbitmq1, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@rabbitmq1, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit@rabbitmq1, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit@rabbitmq2, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit@rabbitmq2, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@rabbitmq2, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit@rabbitmq3, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit@rabbitmq3, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@rabbitmq3, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 3.将集群设为镜像模式
在我们使用 rabbitmq 作为消息服务时,在服务负载不是很大的情况下
一般我们只需要一个 rabbitmq 节点便能为我们提供服务
可这难免会发生单点故障,要解决这个问题,我们便需要配置 rabbitmq 的集群和镜像
镜像模式参数
rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
-p Vhost: 可选参数,针对指定vhost下的queue进行设置
Name: policy的名称
Pattern: exchanges或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和manual。automatic:新增加节点自动同步全量数据。manual: 新增节点只同步新增数据,全量数据需要手工同步。
Priority:可选参数,policy的优先级
2
3
4
5
6
7
8
9
10
11
12
13
- 设置示例
# 所有队列exchangess 或者 queue都为镜像模式
[root@rabbitmq1 ~]# rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'
Setting policy "ha-all" for pattern "^" to "{"ha-mode":"all"}" with priority "0" for vhost "/" ...
# 对队列名称以“queue_”开头的所有队列进行镜像,并在集群的两个节点上完成进行,policy的设置命令为:
[root@rabbitmq1 ~]# rabbitmqctl set_policy --priority 0 --apply-to queues mirror_queue "^queue_" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
2
3
4
5
6