RabbitMQ集群与高可用

1 集群配置-docker

1)拉取RabbitMQ镜像(带managment)

1
docker pull rabbitmq:3.7.17-management

2)创建docker网络(让容器可以和主机通信)

1
docker network create rabbitmqnet

3)创建三个容器,端口分别是 5673 5674 5675 ,管理端口是 15673 15674 15675

1
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
docker run -d \
--name=rabbitmq1 \
-p 5673:5672 \
-p 15673:15672 \
-e RABBITMQ_NODENAME=rabbitmq1 \
-e RABBITMQ_ERLANG_COOKIE='GUPAOEDUFORBETTERYOU' \
-h rabbitmq1 \
--net=rabbitmqnet \
rabbitmq:3.7.17-management

docker run -d \
--name=rabbitmq2 \
-p 5674:5672 \
-p 15674:15672 \
-e RABBITMQ_NODENAME=rabbitmq1 \
-e RABBITMQ_ERLANG_COOKIE='GUPAOEDUFORBETTERYOU' \
-h rabbitmq2 \
--net=rabbitmqnet \
rabbitmq:3.7.17-management

docker run -d \
--name=rabbitmq3 \
-p 5675:5672 \
-p 15675:15672 \
-e RABBITMQ_NODENAME=rabbitmq1 \
-e RABBITMQ_ERLANG_COOKIE='GUPAOEDUFORBETTERYOU' \
-h rabbitmq3 \
--net=rabbitmqnet \
rabbitmq:3.7.17-management

4)后两个节点作为内存节点加入集群

1
2
3
4
5
6
7
8
9
10
11
docker exec -it rabbitmq2 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbitmq1@rabbitmq1
rabbitmqctl start_app

docker exec -it rabbitmq3 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbitmq1@rabbitmq1
rabbitmqctl start_app

5)启动访问

2 集群节点类型

集群有两种节点类型,一种是磁盘节点(Disc Node),一种是内存节点(RAM Node)。

​ 磁盘节点:将元数据(包括队列名字属性,减缓及的类型名字属性,绑定,vhost)放在磁盘中。未指定类型的情况下,默认为磁盘节点。集群中至少需要一个磁盘节点用来持久化元数据,否则全部内存节点奔溃时,就无从同步元数据。

​ 内存节点:将元数据放在内存中。

​ PS:内存节点会将磁盘节点的地址存放在磁盘(不然重启后就没有办法同步数据了)。如果是持久化的消息,会同时存放在内存和磁盘。我们一般把应用连接到内存节点(读写快),磁盘节点用来备份。

​ 集群的配置步骤:

​ 1、配置hosts以便相互通信

​ 2、同步erlang.cookie

​ 3、加入集群(join cluster命令)

元数据包含以下内容:

  1. 队列元数据:队列的名称及属性
  2. 交换器:交换器的名称及属性
  3. 绑定关系元数据:交换器与队列或者交换器与交换器
  4. vhost 元数据:为 vhost 内的队列、交换器和绑定提供命名空间及安全属性之间的绑定关系

3 集群模式

3.1 普通模式

普通集群模式下,不同的节点之间只会相互同步元数据(交换机,队列,绑定关系,Vhost的定义),而不会同步消息。

比如队列1的消息只存储在节点1上。节点2和节点3同步了队列1的定义,但是没有同步消息。假如生产者连接的是节点3,要将消息通过交换机A路由到队列1,最终消息还是会转发到节点1上存储,因为队列1的内容只在节点1上。同理,如果消费者连接是节点2,要从队列1上拉取消息,消息会从节点1转发到节点2。其他节点起到一个路由的作用,类似于指针。

​ 这样有一个问题:如果节点1挂了,队列1的所有数据就全部丢失了,为什么不直接把消息在所有节点上复制一份?

​ 主要是出于存储和同步数据的网络开销的考虑,如果所有节点都存储相同的数据,就无法达到线性的增加性能和存储容量的目的(堆机器)。这就是一种分片存储的思想。当然,如果需要保证队列的高可用性,就需要第二种集群模式了。

3.2 镜像模式

镜像队列模式下,消息内容会在镜像节点间同步,可用性更高。不过也有一定的副作用,系统性能会降低,节点过多的情况下同步的代价比较大。集群模式可以通过UI或者CLI或者HTTP操作。镜像模式的集群是在普通模式的基础上,通过policy来实现,使用镜像模式可以实现RabbitMQ的 高可用方案。

操作方式 命令或步骤
rabbitmqctl (Windows) rabbitmqctl set_policy ha-add “^ha.” “{“”ha-mode””:””all””}”
HTTP API PUT /api/policies/%2f/ha-all {“pattern”:”^ha.”,”definition”:{“ha-mode”:”all”}}
Web UI 1. avigate to Admin > Policies > Add / update a policy
2. Name输入:mirror_image
3. Pattern输入:^ (代表匹配所有)
4. Definition点击HA mode,右边输入:all
5. Add policy

4 持久化机制

RabbitMQ 的持久化分为队列持久化、消息持久化和交换器持久化。 不管是持久化的消息还是非持久化的消息都可以被写入到磁盘。

4.1 队列持久化

队列的持久化是在定义队列时的durable参数来实现的,durable为true时,队列才会持久化。

1
2
3
4
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 第二个参数设置为true,即durable=true
channel.queueDeclare("queue1",true,false,false,null);

4.2 消息持久化

消息持久化通过消息的属性deliveryMode来设置是否持久化,在发送消息时通过basicPublish的参数传入。

1
2
// 通过传入MessageProperties.PERSISTENT_TEXT_PLAIN就可以实现消息持久化
channel.basicPublish("","queue1",MessageProperties.PERSISTENT_TEXT_PLAIN,"test".getBytes());

4.3 交换器持久化

同队列一样,交换器也需要在定义时设置持久化标识,否则在Broker重启后将丢失

5 内存控制

当内存使用超过配置的阈值或者磁盘剩余空间低于配置的阈值时,RabbitMQ 会暂时阻塞客户端的连接, 并停止接收从客户端发来的消息,以此避免服务崩溃,客户端与服务端的心跳检测也会失效。

当出现内存告警时,可以通过管理命令临时调整内存大小:

1
rabbitmqctl set_vm_memory_high_watermark <fraction>

fraction 为内存阈值,RabbitMQ 默认值为0.4,表示当RabbitMQ 使用的内存超过40%时, 就会产生告警并阻塞所有生产者连接。 通过此命令修改的阈值在Broker重启后将会失效,通过修改配置文件的方式设置的阈值则不会在重启后消失,但需要重启Broker才会生效。

配置文件地址:/etc/rabbitmq/rabbitmq.conf

1
2
vm_memory_high_watermark.relative = 0.4
# vm_memory_high_watermark.absolute = 1GB

RabbitMQ 提供relative或absolute两种配置方式

• relative 相对值,即前面的fraction,建议取值在0.4~0.66之间,不建议超过0.7

• absolute 绝对值,单位为KB、MB、GB,对应的命令是:

1
rabbitmqctl set_vm_memory_high_watermark absolute <value>

在某个 Broker 节点触及内存并阻塞生产者之前,它会尝试将队列中的消息换页到磁盘以释放内存空间。 持久化和非持久化的消息都会被转储到磁盘中,其中持久化的消息本身就在磁盘中有一份副本,这里会将持久化的消息从内存中清除掉。

默认情况下,在内存到达内存阈值的 50%时会进行换页动作。 也就是说,在默认的内存阈值为 0.4 的情况下,当内存超过 0.4 * 0.5=0.2时会进行换页动作

可以通过在配置文件中配置vm_memory_high_watermark_paging_ratio项来修改此值

1
2
vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.75

以上配置将会在RabbitMQ 内存使用率达到30%时进行换页动作,并在40%时阻塞生产者。当 vm_memory_high_watermark_paging_ratio的值大于1时,相当于禁用了换页功能。

6 磁盘控制

当磁盘剩余空间低于确定的阈值时,RabbitMQ 同样会阻塞生产者,这样可以避免因非持久化的消息持续换页而耗尽磁盘空间导致服务崩溃。

默认情况下,磁盘阈值为50MB,表示当磁盘剩余空间低于50MB 时会阻塞生产者并停止内存 中消息的换页动作 。 这个阈值的设置可以减小,但不能完全消除因磁盘耗尽而导致崩溃的可能性。比如在两次磁盘空间检测期间内,磁盘空间从大于50MB被耗尽到0MB 。一个相对谨慎的做法是将磁盘阈值设置为与操作系统所显示的内存大小一致。

通过命令可以临时调整磁盘阈值:

1
2
rabbitmqctl set_disk_free_limit <disk_limit>
rabbitmqctl set_disk_free_limit mem_relative <fraction>

disk_limit 为固定大小,单位为KB、MB、GB fraction 为相对比值,建议的取值为1.0~2.0之间

对应的配置如下:

1
2
disk_free_limit.relative = 2.0
# disk_free_limit.absolute = 50m

RabbitMQ集群与高可用
http://www.zivjie.cn/2023/06/10/消息队列/RabbitMQ/集群与高可用/
作者
Francis
发布于
2023年6月10日
许可协议