Debian 11 搭建 RabbitMQ 集群(超详细)
前言
集群模式
RabbitMQ 是用 Erang 开发的,集群模式分为两种普通模式
和镜像模式
,可以说镜像模式
是普通模式
的升级版,其中 RabbitMQ 默认使用的是 普通模式
。
普通模式:
以两个节点(rabbit01、rabbit02)为例来进行说明,rabbit01 和 rabbit02 两个节点仅有相同的元数据,即队列的结构,但消息实体只存在于其中一个节点 rabbit01(或者 rabbit02)中。当消息进入 rabbit01 节点的 Queue 后,consumer 从 rabbit02 节点消费时,RabbitMQ 会临时在 rabbit01、rabbit02 间进行消息传输,把 A 中的消息实体取出并经过 B 发送给 consumer。所以 consumer 应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理 Queue。否则无论 consumer 连 rabbit01 或 rabbit02,出口总在 rabbit01,会产生瓶颈。当 rabbit01 节点故障后,rabbit02 节点无法取到 rabbit01 节点中还未消费的消息实体。如果做了消息持久化,那么得等 rabbit01 节点恢复,然后才可被消费;如果没有持久化的话,就会产生消息丢失的现象。镜像模式:
在普通模式的基础上,把需要的队列做成镜像队列,存在于多个节点,消息实体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取,也就是说多少节点消息就会备份多少份。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉,所以在对业务可靠性要求较高的场合中适用。由于镜像队列之间消息自动同步,且内部有选举 Master 机制,即使 Master 节点宕机也不会影响整个集群的使用,达到去中心化的目的,从而有效的防止消息丢失及服务不可用等问题
集群节点的区别
RabbitMQ 的集群节点分为磁盘节点、内存节点。RabbitMQ 支持消息的持久化,也就是数据写在磁盘上。在 RabbitMQ 集群中,必须至少有一个磁盘节点,否则队列元数据无法写入到集群中。当磁盘节点宕掉时,集群将无法写入新的队列元数据信息。如果 RabbitMQ 集群全部宕机,必须先启动磁盘节点,然后再启动内存节点。最合适的方案就是既有磁盘节点,又有内存节点,推荐 1 个 磁盘节点 + 2 个内存节点的集群搭建方式。
准备工作
集群规划
名称 | IP | 端口 | 用途 | RabbitMQ 节点名称 |
---|---|---|---|---|
节点一 | 192.168.1.109 | 15672 | 磁盘节点 | rabbit@rabbitmq1 |
节点二 | 192.168.1.201 | 15672 | 内存节点 | rabbit@rabbitmq2 |
节点三 | 192.168.1.200 | 15672 | 内存节点 | rabbit@rabbitmq3 |
注意,在生产环境搭建 RabbitMQ 集群时,所有集群节点要求都可以连接上互联网,另外 RabbitMQ 集群节点建议都在同一网段里,如果是跨广域网(外网),效果会变差。
系统初始化
创建用户和用户组
1 | # 创建rabbitmq用户组 |
RabbitMQ 集群安装
Erlang 安装
在每个集群节点上分别编译安装 Erlang,这里使用的版本是 23.2
,其他版本的 Erlang 可以从 Erlang 官网 下载
1 | # 安装依赖 |
RabbitMQ 安装
在每个集群节点上分别使用二进制包的方式安装 RabbitMQ,使用的版本是 3.8.6
,其他版本的 RabbitMQ 可以从 RabbitMQ Github 下载
1 | # 安装依赖 |
RabbitMQ 配置
在每个集群节点上分别配置 RabbitMQ,包括创建默认的日志目录与数据目录、启用 Web 控制台管理插件
1 | # 创建默认的日志目录与数据目录 |
在每个集群节点上分别配置 RabbitMQ,包括创建虚拟主机、超级管理员用户、设置角色权限。由于出于系统安全考虑,RabbitMQ 默认限制了 guest
用户只能通过 localhost
登录使用,因此需要手动创建管理员帐号,并更改 guest
用户默认的密码
1 | # 进入安装目录 |
s 提示
上述步骤创建的超级管理员用户,可以用于登录 RabbitMQ 的 Web 管理控制台,也可以用于 RabbitMQ 客户端连接 RabbitMQ 服务器(通过编写代码方式)。
RabbitMQ 普通集群搭建
添加节点主机名
在每个集群节点上分别编辑 /etc/hosts
配置文件,指定各个节点的主机名
1 | # vim /etc/hosts |
配置节点的名称
RabbitMQ 节点由节点名称(RABBITMQ_NODENAME)标识,节点名称由两部分组成,前缀(默认是 rabbit
)和主机名,例如:rabbit@rabbit1
是一个包含前缀 rabbit
和主机名 rabbit1
的节点名称。可以在同一台主机上运行多个 RabbitMQ 节点,但集群中每个节点必须有一个唯一的 RABBITMQ_NODENAME。若在同一台主机上运行多个节点(开发和 QA 环境中通常是这种情况),每个节点还必须使用不同的前缀,例如:rabbit1@hostname1
和 rabbit2@hostname2
。在集群中,节点使用节点名称标识和联系彼此,这意味着必须解析每个节点名的主机名部分。当节点启动时,它会检查是否已为其分配了节点名,这是通过配置文件 rabbitmq-env.conf
里的 RABBITMQ_NODENAME
环境变量指定,如果环境变量没有配置,则节点将解析其主机名并在其前面添加 rabbit
来计算其节点名。
在每个集群节点上分别配置节点名称,只需将下面 rabbit@xxx
中的 xxx
替换为该节点的主机名即可,例如节点一的节点名称为: rabbit@rabbitmq1
1 | # 配置节点名称 |
拷贝 Erlang Cookie
RabbitMQ 的集群是依附于 Erlang 的集群来工作的,所以必须先构建起 Erlang 的集群。Erlang 的集群中各节点是经由过程一个 cookie 来实现的,当使用解压缩的方式来安装 RabbitMQ 时,那么这个 cookie 存放在 ${home}/.erlang.cookie
中,文件是 400 的权限。必须保证集群各节点的 cookie 一致,不然节点之间就无法通信。
1 | # 拷贝节点一的Cookie到其他节点 |
构建集群节点
在每个集群节点上分别启动 RabbitMQ 的服务,这里默认使用的用户为 root
。当使用解压缩的方式来安装 RabbitMQ 时,cookie 是存放在 ${home}/.erlang.cookie
,因此这里必须注意各个节点的 RabbitMQ 是使用哪个用户启动,否则后续很可能由于各节点的 .erlang.cookie
不一致而导致节点无法加入集群。
1 | # 进入安装目录 |
在节点二执行以下操作,将节点二(rabbit@rabbitmq2
)加入到 RabbitMQ 集群
1 | # 进入节点二的安装目录 |
在节点三执行以下操作,将节点三(rabbit@rabbitmq3
)加入到 RabbitMQ 集群
1 | # 进入节点三的安装目录 |
在任意节点上查看集群的状态
1 | # 进入安装目录 |
- 搭建集群时,停止 RabbitMQ 服务必须使用
stop_app
命令,而不是stop
命令,否则无法将节点加入到集群中- 默认情况下,RabbitMQ 启动后是磁盘节点,在上面的
join_cluster
命令下,rabbitmq2
和rabbitmq3
是内存节点,rabbitmq1
是磁盘节点- 若要使
rabbitmq2
和rabbitmq3
都成为磁盘节点,去掉--ram
参数即可,或者使用--disc
参数替代- 如果想要更改节点类型,可以使用命令
rabbitmqctl change_cluster_node_type disc(ram)
,前提是必须停掉 RabbitMQ 服务
测试集群节点
若集群节点构建成功,通过浏览器访问任意节点的 Web 控制台,例如 http://192.168.1.109:15672
,会看到如下的内容。最后可以在节点一创建队列 test
,如果在节点二、节点三的 Web 控制台,也可以看到对应的 test
队列,则说明各集群节点的元数据(队列的结构)同步正常。至此,RabbitMQ 的普通集群搭建完成。
RabbitMQ 镜像集群搭建
上面已经完成 RabbitMQ 普通集群的搭建,但并不能保证队列的高可用性,尽管交换机、队列、绑定这些可以复制到集群里的任何一个节点,但是队列内容(消息)不会复制。虽然普通集群解决可以一项目组的节点压力,但队列节点(磁盘节点)宕机会直接导致其他节点的队列(内存节点)无法使用,只能等待队列节点(磁盘节点)重启,所以要想在队列节点(磁盘节点)宕机或故障也能正常应用,就要复制队列内容(消息)到集群里的每个节点,因此必须要创建镜像队列。镜像队列是基于普通的集群模式的,然后再添加一些策略,所以还是得先配置普通集群,然后才能设置镜像队列。设置镜像队列可以在 RabbitMQ 的 Web 控制台进行,也可以通过命令,这里介绍是其中的 Web 控制台设置方式。
创建策略
在节点一的 Web 控制台上创建策略:
- 点击
Admin
菜单 –> 右侧的Policies
选项
- 点击
- 按照图中的内容根据自己的需求填写
- Name:策略名称
- Pattern:匹配的规则,
^a
表示匹配a
开头的队列,如果是匹配所有的队列,那就是^.
- Definition:使用
ha-mode
模式中的all
,也就是同步所有匹配的队列
- 点击左侧最下边的
Add/update a policy
按钮新增策略
- 点击左侧最下边的
- 此时分别登录节点二、节点三的 Web 控制台,同样可以看到刚添加的这个策略
创建队列
在节点一的 Web 控制台上创建队列:
- 点击
Queues
菜单
- 点击
- 输入
Name
和Arguments
参数的值,别的参数默认即可
- 输入
Name:队列名称
Durability:队列是否持久化
Node:消息队列的节点
Auto delete:是否自动删除
Arguments:使用的策略类型
- 点击左侧下边的
Add a new queue
按钮新增队列,将鼠标指向+2
可以显示出另外两台节点
- 点击左侧下边的
创建消息
- 点击
ab
队列按钮,拖动滚动条
- 点击
- 填写相关内容
2-Persistent:表示持久化
Headers:随便填写即可
Properties:点击问号,选择一个消息 ID 号
Payload:消息内容
- 点击
Publish message
按钮新增消息,可发现ab
队列的Ready
和Total
中多了一条消息记录
- 点击
验证高可用性
- 将节点一的 RabbitMQ 服务关闭,再通过节点一和节点二,查看消息记录是否还存在,结果可以看到在其他节点的消息记录是存在的
- 再将节点二的 RabbitMQ 服务关闭,通过节点三查看消息记录是否还存在,结果可以看到
ab
队列和消息记录还是存在的,只是变成了只有一个节点
- 再将节点二的 RabbitMQ 服务关闭,通过节点三查看消息记录是否还存在,结果可以看到
- 将节点一和节点二的 RabbitMQ 服务重启,从中可以看到
ab
队列后面+2
变成了红色,鼠标指上去显示镜像无法同步
- 将节点一和节点二的 RabbitMQ 服务重启,从中可以看到
- 采取的解决办法是选择在节点二上执行同步命令
1 | # 进入节点一的安装目录 |
同步完成后,+2
标识又变成了蓝色,这样就测试了 RabbitMQ 集群的高可用性,说明镜像集群配置成功。
FAQ
.erlang.cookie 解惑
.erlang.cookie
是 Erlang 实现分布式集群的必要文件,Erlang 分布式集群要求每个节点上都要有相同的 .erlang.cookie
文件,同时保证文件的权限是 400。在搭建 RabbitMQ 集群的时候往往会因为 .erlang.cookie
而报各种错误,官方在介绍集群的文档中提到过 .erlang.cookie
一般会存在这两个路径:第一个是 ${home}/.erlang.cookie
,第二个就是 /var/lib/rabbitmq/.erlang.cookie
,具体说明如下:
- 如果 RPM 等安装包方式进行安装的,那么这个文件会在
/var/lib/rabbitmq
目录下,完整路径为/var/lib/rabbitmq/.erlang.cookie
- 如果使用解压缩方式安装部署 RabbitMQ,那么这个文件会在
${home}
目录下,也就是用户的 Home 目录下,完整路径为${home}/.erlang.cookie
通过 RabbitMQ 的启动日志,可以查看其 Home 目录是哪里,就可以知道 .erlang.cookie
存放在哪里,以及 mnesia
数据库信息存在哪里
1 | # RPM包安装方式 |
1 | # 解压缩安装方式(这里使用Root用户启动) |
重新将节点加入集群
这里假设由于各种原因(例如断电重启、节点宕机重启),节点二无法成功加入到集群,那么可以执行以下操作来解决。特别注意,以下操作会删除节点二的元数据(虚拟机、用户、角色、权限、已持久化的消息等),因此当节点二成功加入集群后,必须重新配置节点二的虚拟机、用户、角色、权限等,否则 RabbitMQ 客户端将无法连接节点二。
首先在节点一里,将节点二移出集群
1 | # 进入节点一的安装目录 |
然后重置节点二的元数据、集群配置等信息,其中会删除虚拟机、用户、角色、权限、已持久化的消息等元数据
1 | # 进入节点二的安装目录 |
如果节点二仍然无法加入集群,可以直接删除节点二的所有数据库文件,然后重启节点二的 RabbitMQ 服务,最后重新将节点二加入到集群
1 | # 进入节点二的安装目录 |
强制重置节点,force_reset
命令和 reset
的区别是无条件重置节点,不管当前管理数据库状态以及集群的配置,如果数据库或者集群配置发生错误才使用这个最后的手段
1 | # ./rabbitmqctl force_reset |
RabbitMQ 更改默认端口
RabbitMQ 默认占用 4369、5672、15672、25672 默认端口号,更改默认端口的方法如下:
- 更改 15672 端口,配置文件路径:
/usr/local/rabbitmq/etc/rabbitmq/rabbitmq.config
1 | [ |
- 更改 5672、25672 端口,配置文件路径:
/usr/local/rabbitmq/etc/rabbitmq/rabbitmq-env.conf
1 | NODE_PORT=5673 |
- 更改 4369 端口,配置文件路径:
/etc/profile
,单机可以多个 RabbitMQ 节点共用同一个ERL_EPMD_PORT
端口
1 | export ERL_EPMD_PORT=4363 |
RabbitMQ 集群开机自启动 - 方案一
将集群各节点的 RabbitMQ 服务托管给 Systemd 管理,这里以节点一为例子,若其他节点的端口号不相同,默认情况下只需更改服务自启动脚本中对应的端口号即可,该脚本适用于在多台机器上搭建 RabbitMQ 集群(每个节点单独占用一台机器)。
1 | # 创建服务自启动脚本 |
RabbitMQ 集群各节点的服务管理
1 | # 关闭服务 |
★展开服务自启动脚本的完整内容★
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/bin/sh
#
# rabbitmq-cluster-15672 RabbitMQ broker
#
# chkconfig: - 80 05
# description: Enable AMQP service provided by RabbitMQ
#
### BEGIN INIT INFO
# Provides: rabbitmq-cluster-15672
# Required-Start: $remote_fs $network
# Required-Stop: $remote_fs $network
# Description: RabbitMQ broker
# Short-Description: Enable AMQP service provided by RabbitMQ broker
### END INIT INFO
# Source function library.
. /etc/init.d/functions
export HOME=/home/rabbitmq
PATH=/sbin:/usr/sbin:/bin:/usr/bin
USER=rabbitmq
NAME=rabbitmq-server
MQ_HOME=/usr/local/rabbitmq
DAEMON=${MQ_HOME}/sbin/${NAME}
CONTROL=${MQ_HOME}/sbin/rabbitmqctl
ROTATE_SUFFIX=
DESC=rabbitmq-cluster-15672
MAX_OPEN_FILES=1048576
ulimit -n $MAX_OPEN_FILES
START_PROG="daemon"
PID_FILE=/var/run/rabbitmq/pid-15672
LOCK_FILE=/var/lock/subsys/$NAME-15672
INIT_LOG_DIR=${MQ_HOME}/var/log/rabbitmq
test -x $DAEMON || exit 0
test -x $CONTROL || exit 0
RETVAL=0
set -e
[ -f /etc/default/${NAME} ] && . /etc/default/${NAME}
ensure_pid_dir () {
PID_DIR=`dirname ${PID_FILE}`
if [ ! -d ${PID_DIR} ] ; then
mkdir -p ${PID_DIR}
chown -R ${USER}:${USER} ${PID_DIR}
chmod 755 ${PID_DIR}
fi
}
remove_pid () {
rm -f ${PID_FILE}
rmdir `dirname ${PID_FILE}` || :
}
start_rabbitmq () {
status_rabbitmq quiet
if [ $RETVAL = 0 ] ; then
echo RabbitMQ is currently running
else
RETVAL=0
ensure_pid_dir
set +e
RABBITMQ_PID_FILE=$PID_FILE $START_PROG $DAEMON \
> "${INIT_LOG_DIR}/startup_log" \
2> "${INIT_LOG_DIR}/startup_err" \
0<&- &
$CONTROL wait $PID_FILE >/dev/null 2>&1
RETVAL=$?
set -e
case "$RETVAL" in
0)
echo SUCCESS
if [ -n "$LOCK_FILE" ] ; then
touch $LOCK_FILE
fi
;;
*)
remove_pid
echo FAILED - check ${INIT_LOG_DIR}/startup_\{log, _err\}
RETVAL=1
;;
esac
fi
}
stop_rabbitmq () {
status_rabbitmq quiet
if [ $RETVAL = 0 ] ; then
set +e
$CONTROL stop ${PID_FILE} > ${INIT_LOG_DIR}/shutdown_log 2> ${INIT_LOG_DIR}/shutdown_err
RETVAL=$?
set -e
if [ $RETVAL = 0 ] ; then
remove_pid
if [ -n "$LOCK_FILE" ] ; then
rm -f $LOCK_FILE
fi
else
echo FAILED - check ${INIT_LOG_DIR}/shutdown_log, _err
fi
else
echo RabbitMQ is not running
RETVAL=0
fi
}
status_rabbitmq() {
set +e
if [ "$1" != "quiet" ] ; then
$CONTROL status 2>&1
else
$CONTROL status > /dev/null 2>&1
fi
if [ $? != 0 ] ; then
RETVAL=3
fi
set -e
}
rotate_logs_rabbitmq() {
set +e
$CONTROL rotate_logs ${ROTATE_SUFFIX}
if [ $? != 0 ] ; then
RETVAL=1
fi
set -e
}
restart_running_rabbitmq () {
status_rabbitmq quiet
if [ $RETVAL = 0 ] ; then
restart_rabbitmq
else
echo RabbitMQ is not runnning
RETVAL=0
fi
}
restart_rabbitmq() {
stop_rabbitmq
start_rabbitmq
}
case "$1" in
start)
echo -n "Starting $DESC: "
start_rabbitmq
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
stop_rabbitmq
echo "$NAME."
;;
status)
status_rabbitmq
;;
rotate-logs)
echo -n "Rotating log files for $DESC: "
rotate_logs_rabbitmq
;;
force-reload|reload|restart)
echo -n "Restarting $DESC: "
restart_rabbitmq
echo "$NAME."
;;
try-restart)
echo -n "Restarting $DESC: "
restart_running_rabbitmq
echo "$NAME."
;;
*)
echo "Usage: $0 {start|stop|status|rotate-logs|restart|condrestart|try-restart|reload|force-reload}" >&2
RETVAL=1
;;
esac
exit $RETVAL
RabbitMQ 集群开机自启动-方案二
严格来说 RabbitMQ 并不适用使用 Supervior 来管理服务,因为当手动 Kill 掉 RabbitMQ 的进程时,Supervior 无法正常重启 RabbitMQ 的进程,具体原因可以看这里,但若只是简单实现 RabbitMQ 开机自启动,Supervior 无疑是可以胜任的。
使用 Supervior 托管管理 RabbitMQ 的服务,以节点一为例给出下述配置示例,其他节点只需更改对应的端口号即可。值得一提的是,这里必须指定 environment=HOME=/home/rabbitmq
,否则 RabbitMQ 会找不到 .erlang.cookie
而导致启动失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[program:rabbitmq]
environment=HOME=/home/rabbitmq
directory=/usr/local/rabbitmq
command=/usr/local/rabbitmq/sbin/rabbitmq-server
user=rabbitmq
numprocs=1
autostart=true
autorestart=true
startretries=10
process_name=%(program_name)s
stdout_logfile_backups=5
stdout_logfile_maxbytes=10MB
stdout_logfile=/var/log/supervisor/rabbitmq.log
stderr_logfile_backups=5
stderr_logfile_maxbytes=10MB
stderr_logfile=/var/log/supervisor/rabbitmq-error.log
以节点一为例通过 Supervisor 管理 RabbitMQ 服务,其他节点不再累述
1
2
3
4
5
6
7
8
9
10
11
# 关闭服务
# supervisorctl stop rabbitmq
# 启动服务
# supervisorctl start rabbitmq
# 查看服务状态
# supervisorctl status rabbitmq
# 重启服务
# supervisorctl restart rabbitmq
RabbitMQ 无法操作集群节点
若执行以下命令出现下述的错误,一般是当前执行操作的用户的家目录下的 .erlang.cookie
与 集群节点的 .erlang.cookie
不一致导致。解决办法是集群节点是以哪个用户启动的,就切换到对应的用户,例如 su rabbitmq
,然后再执行集群操作命令。
1
2
# 查看集群状态
# ./rabbitmqctl cluster_status
执行集群状态查看命令,出现以下错误信息
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
30
31
Error: unable to perform an operation on node 'rabbit2@rabbitmq2'. Please see diagnostics information and suggestions below.
Most common reasons for this are:
* Target node is unreachable (e.g. due to hostname resolution, TCP connection or firewall issues)
* CLI tool fails to authenticate with the server (e.g. due to CLI tool's Erlang cookie not matching that of the server)
* Target node is not running
In addition to the diagnostics info below:
* See the CLI, clustering and networking guides on https://rabbitmq.com/documentation.html to learn more
* Consult server logs on node rabbit2@rabbitmq2
* If target node is configured to use long node names, don't forget to use --longnames with CLI tools
DIAGNOSTICS
===========
attempted to contact: [rabbit2@rabbitmq2]
rabbit2@rabbitmq2:
* connected to epmd (port 4369) on rabbitmq2
* epmd reports node 'rabbit2' uses port 25674 for inter-node and CLI tool traffic
* TCP connection succeeded but Erlang distribution failed
* Authentication failed (rejected by the remote node), please check the Erlang cookie
Current node details:
* node name: 'rabbitmqcli-7936-rabbit2@rabbitmq2'
* effective user's home directory: /home/centos
* Erlang cookie hash: 5hmDFFQNoU5sdfrafENxAg==
RabbitMQ 集群配置文件概述
这里以节点一为例,各个配置文件的路径如下,其他节点不再累述
1
2
3
4
5
6
安装目录:/usr/local/rabbitmq
日志目录:/usr/local/rabbitmq/var/log/rabbitmq
数据目录:/usr/local/rabbitmq/var/lib/rabbitmq/mnesia
配置文件:/usr/local/rabbitmq/etc/rabbitmq/rabbitmq.config
环境变量配置文件:/usr/local/rabbitmq/etc/rabbitmq/rabbitmq-env.conf
服务自启动脚本:/etc/init.d/rabbitmq-cluster-15672
参考博客