KeepAlived+HaProxy 热备切换Rabbitmq集群

发布时间 2023-03-22 23:03:50作者: libing-zhang

一、集群简介

1.1 集群架构

当单台 RabbitMQ 服务器的处理消息的能力达到瓶颈时,此时可以通过 RabbitMQ 集群来进行扩展,从而达到提升吞吐量的目的。RabbitMQ 集群是一个或多个节点的逻辑分组,集群中的每个节点都是对等的,每个节点共享所有的用户,虚拟主机,队列,交换器,绑定关系,运行时参数和其他分布式状态等信息。一个高可用,负载均衡的 RabbitMQ 集群架构应类似下图:

 

这里对上面的集群架构做一下解释说明:

首先一个基本的 RabbitMQ 集群不是高可用的,虽然集群共享队列,但在默认情况下,消息只会被路由到某一个节点的符合条件的队列上,并不会同步到其他节点的相同队列上。假设消息路由到 node1 的 my-queue 队列上,但是 node1 突然宕机了,那么消息就会丢失,想要解决这个问题,需要开启队列镜像,将集群中的队列彼此之间进行镜像,此时消息就会被拷贝到处于同一个镜像分组中的所有队列上。

其次 RabbitMQ 集群本身并没有提供负载均衡的功能,也就是说对于一个三节点的集群,每个节点的负载可能都是不相同的,想要解决这个问题可以通过硬件负载均衡或者软件负载均衡的方式,这里我们选择使用 HAProxy 来进行负载均衡,当然也可以使用其他负载均衡中间件,如 LVS 等。HAProxy 同时支持四层和七层负载均衡,并基于单一进程的事件驱动模型,因此它可以支持非常高的井发连接数。

接着假设我们只采用一台 HAProxy ,那么它就存在明显的单点故障的问题,所以至少需要两台 HAProxy ,同时这两台 HAProxy 之间需要能够自动进行故障转移,通常的解决方案就是 KeepAlived 。KeepAlived 采用 VRRP (Virtual Router Redundancy Protocol,虚拟路由冗余协议) 来解决单点失效的问题,它通常由一主一备两个节点组成,同一时间内只有主节点会提供对外服务,并同时提供一个虚拟的 IP 地址 (Virtual Internet Protocol Address ,简称 VIP) 。 如果主节点故障,那么备份节点会自动接管 VIP 并成为新的主节点 ,直到原有的主节点恢复。

1.1 基于Linux系统安装RabbitMQ

注意:需要先设置linux机器别名称 hostnamectl set-hostname 机器别名 --static 

每台服务器配置ip与主机名称的映射关系

vi /etc/hosts 192.168.32.139 rabbit2 192.168.32.140 rabbit3 192.168.32.141 rabbit1

 

 

IP地址DNS设置的时候:必须是114.114.114.114、 8.8.8.8

 

1.2 安装Erlang

#第一步
curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
#第二步 安装erlang
yum install erlang
#第三步 查看erlang版本号,在命令行直接输入erl
erl

1.3 安装RabbitMQ

#第一步 先导入两个key
rpm --import https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey
rpm --import https://packagecloud.io/gpg.key
#第二步
curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash
#第三步
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.5/rabbitmq-server-3.8.5-1.el8.noarch.rpm
#第四步
rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
#第五步
yum -y install epel-release
yum -y install socat
#第六步
rpm -ivh rabbitmq-server-3.8.5-1.el8.noarch.rpm
#第七步 启用管理平台插件,启用插件后,可以可视化管理RabbitMQ
rabbitmq-plugins enable rabbitmq_management
#第八步 启动应用
systemctl start rabbitmq-server
> 关键操作:
1、如果实在云服务器上操作需要 放行端口 4369 25672 15672 5672
2、如果仅仅为了学习, 直接关闭掉防火墙
   sytemctl stop firewald
   systemctl disable firewalld
3、关闭SELINUX (如果不关闭会阻止其他的应用执行shell脚本)
   getenforce
   setenforce 0
 
 

1.4 设置访问权限

#创建管理员账户
rabbitmqctl add_user root root@123
#设置注册的账户为管理员
rabbitmqctl set_user_tags root administrator
#授权远程访问
rabbitmqctl set_permissions -p / gerry ".*" ".*" ".*"
#重启服务
systemctl restart rabbitmq-server
 
 

1.2 部署情况

下面我们开始进行搭建,这里我使用三台主机进行演示,主机名分别为 rabbit1、rabbit2和 rabbit3 ,其功能分配如下:

  • 215服务器**:部署 RabbitMQ + HAProxy + KeepAlived
  • 216 服务器**:部署 RabbitMQ + HAProxy + KeepAlived
  • 217 服务器**:部署 RabbitMQ

以上三台主机上我均已安装好了 RabbitMQ

二、RabbitMQ 集群搭建

首先先进行 RabbitMQ 集群的搭建,具体步骤如下:

2.1 停止服务rabbit2、rabbit3

rabbitmqctl stop_app
 
 

2.2 拷贝 cookie

将 k8s-01上的 .erlang.cookie 文件拷贝到其他两台主机上。该 cookie 文件相当于密钥令牌,集群中的 RabbitMQ 节点需要通过交换密钥令牌以获得相互认证,因此处于同一集群的所有节点需要具有相同的密钥令牌,否则在搭建过程中会出现 Authentication Fail 错误。

RabbitMQ 服务启动时,erlang VM 会自动创建该 cookie 文件,默认的存储路径为 /var/lib/rabbitmq/.erlang.cookie 或 $HOME/.erlang.cookie,该文件是一个隐藏文件,需要使用 ls -al 命令查看。这里我使用的是 root 账户,$HOME 目录就是 /root 目录,对应的拷贝命令如下:

scp /var/lib/rabbitmq/.erlang.cookie root@rabbit2:/var/lib/rabbitmq
scp /var/lib/rabbitmq/.erlang.cookie root@rabbit3:/var/lib/rabbitmq

由于你可能在三台主机上使用不同的账户进行操作,为避免后面出现权限不足的问题,这里建议将 cookie 文件原来的 400 权限改为 777,命令如下:

chmod  400 /root/.erlang.cookie

注:cookie 中的内容就是一行随机字符串,可以使用 cat 命令查看。

2.3 集群搭建

RabbitMQ 集群的搭建需要选择其中任意一个节点为基准,将其它节点逐步加入。这里我们以 k8s-01 为基准节点,将 k8s-02 和 k8s-03 加入集群。在 k8s-02 和 k8s-03 上执行以下命令:

# 1.停止服务
rabbitmqctl stop_app
# 2.重置状态(需要更改节点类型的时候执行,首次不需要执行,除非你节点是以disk加入集群的)
rabbitmqctl reset
# 3.节点加入
rabbitmqctl join_cluster --ram rabbit@rabbit1 
# 4.启动服务
rabbitmqctl start_app

join_cluster 命令有一个可选的参数 --ram ,该参数代表新加入的节点是内存节点,默认是磁盘节点。如果是内存节点,则所有的队列、交换器、绑定关系、用户、访问权限和 vhost 的元数据都将存储在内存中,如果是磁盘节点,则存储在磁盘中。内存节点可以有更高的性能,但其重启后所有配置信息都会丢失,因此RabbitMQ 要求在集群中至少有一个磁盘节点,其他节点可以是内存节点。当内存节点离开集群时,它可以将变更通知到至少一个磁盘节点;然后在其重启时,再连接到磁盘节点上获取元数据信息。除非是将 RabbitMQ 用于 RPC 这种需要超低延迟的场景,否则在大多数情况下,RabbitMQ 的性能都是够用的,可以采用默认的磁盘节点的形式。这里为了演示,k8s-02 我就设置为内存节点。

另外,如果节点以磁盘节点的形式加入,则需要先使用 reset 命令进行重置,然后才能加入现有群集,重置节点会删除该节点上存在的所有的历史资源和数据。采用内存节点的形式加入时可以略过 reset 这一步,因为内存上的数据本身就不是持久化的。

2.4 查看集群状态

1. 命令行查看

在 rabbit2和 rabbit3上执行以上命令后,集群就已经搭建成功,此时可以在任意节点上使用 rabbitmqctl cluster_status 命令查看集群状态,输出如下:

rabbitmqctl cluster_status

默认的 cluster_name 名字为 rabbit@rabbit1,如果你想进行修改,可以使用以下命令:

rabbitmqctl set_cluster_name rabbitmq_cluster

2. UI 界面查看

除了可以使用命令行外,还可以使用打开任意节点的 UI 界面进行查看,情况如下:

 

 

 

RabbitMQ集群元数据的同步

RabbitMQ集群会始终同步四种类型的内部元数据:

  • 队列元数据:队列名称和它的属性
  • 交换器元数据:交换器名称、类型和属性
  • 绑定元数据:一张简单的表格展示了如何将消息路由到队列
  • vhost元数据:为vhost内的队列、交换器和绑定提供命名空间和安全属性

因此,当用户访问其中任何一个RabbitMQ节点时,通过rabbitmqctl查询到的queue/user/exchange/vhost等信息都是相同的。

为何RabbitMQ集群仅采用元数据同步的方式?

  1. 存储空间。如果每个集群节点都拥有所有Queue的完全数据拷贝,那么每个节点的存储空间会非常大,集群的消息积压能力会非常弱(无法通过集群节点的扩容提高消息积压能力);
  2. 性能。消息的发布者需要将消息复制到每一个集群节点,对于持久化消息,网络和磁盘同步复制的开销都会明显增加。

RabbitMQ集群发送/订阅消息的基本原理

RabbitMQ集群的工作原理图如下:

 

 

 

客户端直接连接队列所在节点

如果有一个消息生产者或者消息消费者通过amqp-client的客户端连接至节点1进行消息的发布或者订阅,那么此时的集群中的消息收发只与节点1相关。

客户端连接的是非队列数据所在节点

如果消息生产者所连接的是节点2或者节点3,此时队列1的完整数据不在该两个节点上,那么在发送消息过程中这两个节点主要起了一个路由转发作用,根据这两个节点上的元数据转发至节点1上,最终发送的消息还是会存储至节点1的队列1上。同样,如果消息消费者所连接的节点2或者节点3,那这两个节点也会作为路由节点起到转发作用,将会从节点1的队列1中拉取消息进行消费。

集群节点类型

1. 磁盘节点

将配置信息和元信息存储在磁盘上(单节点系统必须是磁盘节点,否则每次重启RabbitMQ之后所有的系统配置信息都会丢失)。

2. 内存节点

将配置信息和元信息存储在内存中。性能是优于磁盘节点的。

RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点(如果集群中唯一的磁盘节点崩溃了,则不能进行创建队列、创建交换器、创建绑定、添加用户、更改权限、添加和删除集群节点)。总之如果唯一磁盘的磁盘节点崩溃,集群是可以保持运行的,但不能更改任何东西。因此建议在集群中设置两个磁盘节点,只要一个可以,就能正常操作。

2.5 节点下线

以上介绍的集群搭建的过程就是服务扩容的过程,如果想要进行服务缩容,即想要把某个节点剔除集群,有两种可选方式:

第一种:可以先使用 rabbitmqctl stop 停止该节点上的服务,然后在其他任意一个节点上执行 forget_cluster_node 命令。这里以剔除rabbit3

上的服务为例,此时可以在 rabbit1 或 rabbit2 上执行下面的命令:

rabbitmqctl forget_cluster_node rabbit@rabbit3

第二种方式:先使用 rabbitmqctl stop 停止该节点上的服务,然后再执行 rabbitmqctl reset 这会清空该节点上所有历史数据,并主动通知集群中其它节点它将要离开集群。

2.6 集群的关闭与重启

没有一个直接的命令可以关闭整个集群,需要逐一进行关闭。但是需要保证在重启时,最后关闭的节点最先被启动。如果第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的那个节点启动,默认进行 10 次连接尝试,超时时间为 30 秒,如果依然没有等到,则该节点启动失败。

这带来的一个问题是,假设在一个三节点的集群当中,关闭的顺序为 rabbit1,rabbit2,rabbit3 如果 rabbit1 因为故障暂时没法恢复,此时 rabbit2 和 rabbit3 就无法启动。想要解决这个问题,可以先将 rabbit1 节点进行剔除,命令如下:

rabbitmqctl forget_cluster_node rabbit@rabbit1 -offline

此时需要加上 -offline 参数,它允许节点在自身没有启动的情况下将其他节点剔除。

 

2.7 修改节点数据的类型

rabbitmqctl stop_app
rabbitmqctl reset # 如果是磁盘节点需要重置内容
rabbitmqctl join_cluster --ram rabbit@k8s-01
rabbitmqctl start_app
 
 

三、HAProxy 环境搭建

3.1 下载

HAProxy 官方下载地址为:https://www.haproxy.org/#down ,如果这个网站无法访问,也可以从 https://src.fedoraproject.org/repo/pkgs/haproxy/ 上进行下载。这里我下载的是 2.x 的版本,下载后进行解压:

tar xf haproxy-2.3.10.tar.gz

3.2 编译

进入解压后进入目录,执行下面的编译命令:

   cd  haproxy-2.3.10

 

make TARGET=linux-glibc  PREFIX=/usr/app/haproxy-2.3.10
yum install gcc-c++ -y 如果报错请先安装
make install PREFIX=/usr/app/haproxy-2.3.10

3.3 配置环境变量

配置环境变量:

vim /etc/profile

最后行添加以下内容

export HAPROXY_HOME=/usr/app/haproxy-2.3.10
export PATH=$PATH:$HAPROXY_HOME/sbin

使得配置的环境变量立即生效:

source /etc/profile

3.4 负载均衡配置

新建配置文件 haproxy.cfg,这里我新建的位置为:/etc/haproxy/haproxy.cfg,文件内容如下:

 

global
# 日志输出配置、所有日志都记录在本机,通过 local0 进行输出
log 127.0.0.1 local0 info
# 最大连接数
maxconn 4096
# 改变当前的工作目录
chroot /usr/app/haproxy-2.3.10
# 以指定的 UID 运行 haproxy 进程
uid 99
# 以指定的 GID 运行 haproxy 进程
gid 99
# 以守护进行的方式运行
daemon
# 当前进程的 pid 文件存放位置
pidfile /usr/app/haproxy-2.3.10/haproxy.pid

# 默认配置
defaults
# 应用全局的日志配置
log global
# 使用4层代理模式,7层代理模式则为"http"
mode tcp
# 日志类别
option tcplog
# 不记录健康检查的日志信息
option dontlognull
# 3次失败则认为服务不可用
retries 3
# 每个进程可用的最大连接数
maxconn 2000
# 连接超时
timeout connect 5s
# 客户端超时
timeout client 120s
# 服务端超时
timeout server 120s

# 绑定配置
listen rabbitmq_cluster
bind :5671
# 配置TCP模式
mode tcp
# 采用加权轮询的机制进行负载均衡
balance roundrobin
# RabbitMQ 集群节点配置
server node1 rabbit1:5672 check inter 5000 rise 2 fall 3 weight 1
server node2 rabbit2:5672 check inter 5000 rise 2 fall 3 weight 1
server node3 rabbit3:5672 check inter 5000 rise 2 fall 3 weight 1

# 配置监控页面
listen monitor
bind :8100
mode http
option httplog
stats enable
stats uri /stats
stats refresh 5s

 

负载均衡的主要配置在 listen rabbitmq_cluster 下,这里指定负载均衡的方式为加权轮询,同时定义好健康检查机制:

server node1 rabbit1:5672 check inter 5000 rise 2 fall 3 weight 1

以上配置代表对地址为 hadoop001:5672 的 node1 节点每隔 5 秒进行一次健康检查,如果连续两次的检查结果都是正常,则认为该节点可用,此时可以将客户端的请求轮询到该节点上;如果连续 3 次的检查结果都不正常,则认为该节点不可用。weight 用于指定节点在轮询过程中的权重。

3.5 启动服务

以上搭建步骤在 rabbit1 和 rabbit2 上完全相同,搭建完成使用以下命令启动服务:

haproxy -f /etc/haproxy/haproxy.cfg

启动后可以在监控页面进行查看,端口为设置的 8100,完整地址为:http://192.168.32.142:8100/stats ,页面情况如下:

 

 

 

所有节点都为绿色,代表节点健康。此时证明 HAProxy 搭建成功,并已经对 RabbitMQ 集群进行监控。

四、KeepAlived 环境搭建

接着就可以搭建 Keepalived 来解决 HAProxy 故障转移的问题。这里我在 192.168.32.142和 192.168.32.143上安装 KeepAlived ,两台主机上的搭建的步骤完全相同,只是部分配置略有不同,具体如下:

4.1 安装Keepalived

yum install -y keepalived

4.2 配置 HAProxy 检查

这里先对 rabbit1上 keepalived.conf 配置文件进行修改,完整内容如下:

 

global_defs {
# 路由id,主备节点不能相同
router_id node1
}

# 自定义监控脚本
vrrp_script chk_haproxy {
# 脚本位置
script "/etc/keepalived/haproxy_check.sh"
# 脚本执行的时间间隔
interval 5
weight 10
}

vrrp_instance VI_1 {
# Keepalived的角色,MASTER 表示主节点,BACKUP 表示备份节点
state MASTER
# 指定监测的网卡,可以使用 ifconfig 进行查看 (注意是安装keepalive宿主机的网卡)

interface ens33
# 虚拟路由的id,主备节点需要设置为相同
virtual_router_id 1
# 优先级,主节点的优先级需要设置比备份节点高
priority 100
# 设置主备之间的检查时间,单位为秒
advert_int 1
# 定义验证类型和密码
authentication {
auth_type PASS
auth_pass 123456
}

# 调用上面自定义的监控脚本
track_script {
chk_haproxy
}

virtual_ipaddress {
# 虚拟IP地址,可以设置多个
192.168.3.200
}
}

 
 

以上配置定义了 142上的 Keepalived 节点为 MASTER 节点,并设置对外提供服务的虚拟 IP 为 192.168.32.200。此外最主要的是定义了通过 haproxy_check.sh 来对 HAProxy 进行监控,这个脚本需要我们自行创建,内容如下:

 

#!/bin/bash
# 判断haproxy是否已经启动
if [ `ps -C haproxy --no-header | wc -l` -eq 0 ] ; then
#如果没有启动,则启动
haproxy -f /etc/haproxy/haproxy.cfg
fi

#睡眠3秒以便haproxy完全启动
sleep 3

#如果haproxy还是没有启动,此时需要将本机的keepalived服务停掉,以便让VIP自动漂移到另外一台haproxy
if [ `ps -C haproxy --no-header | wc -l` -eq 0 ]; then
systemctl stop keepalived
fi

 
 

创建后为其赋予执行权限:

chmod +x /etc/keepalived/haproxy_check.sh

 
 

这个脚本主要用于判断 HAProxy 服务是否正常,如果不正常且无法启动,此时就需要将本机 Keepalived 关闭,从而让虚拟 IP 漂移到备份节点。备份节点的配置与主节点基本相同,但是需要修改其 state 为 BACKUP;同时其优先级 priority 需要比主节点低。完整配置如下:

 

global_defs {
# 路由id,主备节点不能相同
router_id node2

}

vrrp_script chk_haproxy {
script "/etc/keepalived/haproxy_check.sh"
interval 5
weight 10
}

vrrp_instance VI_1 {
# BACKUP 表示备份节点
state BACKUP
interface ens33
virtual_router_id 1
# 优先级,备份节点要比主节点低
priority 80
advert_int 1
authentication {
auth_type PASS
auth_pass 123456
}

track_script {
chk_haproxy
}

virtual_ipaddress {
192.168.32.200
}
}

 

4.3 启动服务

分别在rabbit1 和 rabbit2上启动 KeepAlived 服务,命令如下:

systemctl start  keepalived

启动后此时 142 为主节点,可以在 142上使用 ip a 命令查看到虚拟 IP 的情况:

 

 

 

此时只有 142上是存在虚拟 IP 的,而 143上是没有的。

 

 

 

4.4 验证故障转移

这里我们验证一下故障转移,因为按照我们上面的检测脚本,如果 HAProxy 已经停止且无法重启时 KeepAlived 服务就会停止,这里我们直接使用以下命令停止 Keepalived 服务:

systemctl stop keepalived

此时再次使用 ip a 分别查看,可以发现 142上的 VIP 已经漂移到 143上,情况如下:

 

 

 

此时对外服务的 VIP 依然可用,代表已经成功地进行了故障转移。至此集群已经搭建成功,任何需要发送或者接受消息的客户端服务只需要连接到该 VIP 即可,示例如下: