RabbitMQ 秒杀应用—RabbitMQ 高可用之普通集群

原创作者:传智博学谷狂野架构师(转载请注明)

1.1 集群原理

思考的问题:

1、搭建集群的好处?

提供整体消息队列服务的可靠性可以通过水平扩容提高整体服务的吞吐量

2、有了集群以后是否可以保证消息不丢失?

不可以

基于存储空间和性能的考虑,在 RabbitMQ 集群中创建队列,集群只会在单个节点而不是在所有节点上创建队列的进程并包含完整的队列消息(元数据,状态,内容)。这样只有队列的宿主节点,即所有者节点知道队列的所有信息,所有其他非所有者节点只知道队列的元数据和指向该队列存在那个节点的指针。

因此当集群节点崩溃时,该节点的队列进程和关联的绑定都会消失。附加在那些队列上的消费者也会丢失其所订阅的消息,并且任何匹配该队列绑定信息的新消息也都会消失。

3、集群中的交换机有哪些特点呢?

交换机其实只是一个名称和绑定列表,没有自己独立的进程,并且交换机的元数据信息会在多个 RabbitMQ 节点上进行共享。

当消息发布到交换机时,实际上是由所连接信道将消息上的路由键同交换机的绑定列表进行比较,然后再路由消息。当创建一个新的交换机时,RabbitMQ 所要做的就是将绑定列表添加到集群中的所有节点上,这样每个节点上的每条信道都可以访问到新的交互机了。

4、客户端与集群建立连接的时候,是否需要与集群中所有的节点建立连接?

只需要连接集群中任意一个节点就可以了

1.2 集群搭建

1.2.1 创建容器

接下来我们就来搭建一个 RabbitMQ 的集群,本次我们搭建一个具有3个节点的 RabbitMQ 集群。

1、拉取镜像

docker pull rabbitmq:3.6.10-management

2、创建容器

docker run -di --network=docker-network --ip=172.19.0.50 --hostname=rabbitmq-node01 --name=rabbitmq_01 -p 15673:15672 -p 5673:5672 --privileged=true -e RABBITMQ_ERLANG_COOKIE=rabbitcookie rabbitmq:3.6.10-management /bin/bash docker run -di --network=docker-network --ip=172.19.0.51 --hostname=rabbitmq-node02 --name=rabbitmq_02 -p 15674:15672 -p 5674:5672 --privileged=true -e RABBITMQ_ERLANG_COOKIE=rabbitcookie rabbitmq:3.6.10-management /bin/bash docker run -di --network=docker-network --ip=172.19.0.52 --hostname=rabbitmq-node03 --name=rabbitmq_03 -p 15675:15672 -p 5675:5672 --privileged=true -e RABBITMQ_ERLANG_COOKIE=rabbitcookie rabbitmq:3.6.10-management /bin/bash

参数说明:Erlang Cookie 值必须相同,也就是 RABBITMQ_ERLANG_COOKIE 参数的值必须相同。因为RabbitMQ是用Erlang实现的,Erlang Cookie 相当于不同节点之间相互通讯的秘钥,Erlang 节点通过交换 Erlang Cookie 获得认证。

3、进入到 RabbitMQ 的容器中

docker exec -it rabbitmq_01 /bin/bash

4、配置 hosts 文件,让各个节点都能互相识别对方的存在。在系统中编辑/etc/hosts 文件,添加 ip 地址和节点名称的映射信息(apt-get update , apt-get install vim):

172.19.0.50 rabbitmq-node01 172.19.0.51 rabbitmq-node02 172.19.0.52 rabbitmq-node03

5、启动 RabbitMQ,并且查看状态

root@014faa4cba72:/# rabbitmq-server -detached # 启动rabbitmq服务,该命令可以启动erlang虚拟机和rabbitmq服务 root@014faa4cba72:/# rabbitmqctl status# 查看节点信息 Status of node rabbit@014faa4cba72 [{pid,270}, {running_applications, [{rabbitmq_management,"RabbitMQ Management Console","3.6.10"}, {rabbitmq_management_agent,"RabbitMQ Management Agent","3.6.10"}, {rabbitmq_web_dispatch,"RabbitMQ Web Dispatcher","3.6.10"}, ............. root@014faa4cba72:/# rabbitmqctl cluster_status# 查看集群节点状态 Cluster status of node rabbit@014faa4cba72 [{nodes,[{disc,[rabbit@014faa4cba72]}]}, {running_nodes,[rabbit@014faa4cba72]},# 正在运行的只有一个节点 {cluster_name,<<"rabbit@014faa4cba72">>}, {partitions,[]}, {alarms,[{rabbit@014faa4cba72,[]}]}]

注意:此时我们可以通过浏览器访问 RabbitMQ 的后端管理系统,但是 RabbitMQ 默认提供的 guest 用户不支持远程访问。因此我们需要创建用户,并且对其进行授权。

root@014faa4cba72:/# rabbitmqctl add_user admin admin # 添加用户,用户名为admin,密码为admin root@014faa4cba72:/# rabbitmqctl list_users # 查看rabbitmq的用户列表 Listing users admin []# admin用户已经添加成功,但是没有角色 guest [administrator] root@014faa4cba72:/# rabbitmqctl set_user_tags admin administrator# 给admin用户设置管理员权限 # rabbitmqctl delete_user admin # 删除admin用户 # rabbitmqctl stop_app# 停止rabbitmq服务 # rabbitmqctl stop# 会将rabbitmq的服务和erlang虚拟机一同关闭

再次使用 admin 用户就可以登录 web 管理系统了。在其他的 RabbitMQ 中也创建用户,以便后期可以访问后端管理系统。

为 admin 用户设置可以访问的虚拟机:

1.2.2 配置集群

1、同步cookie

集群中的 RabbitMQ 节点需要通过交换密钥令牌以获得相互认证,如果节点的密钥令牌不一致,那么在配置节点时就会报错。

获取某一个节点上的 /var/lib/rabbitmq/.erlang.cookie 文件,然后将其复制到其他的节点上。我们以 node01 节点为基准,进行此操作。

docker cp rabbitmq_01:/var/lib/rabbitmq/.erlang.cookie . docker cp .erlang.cookie rabbitmq_02:/var/lib/rabbitmq docker cp .erlang.cookie rabbitmq_03:/var/lib/rabbitmq

2、建立集群关系

目前3个节点都是独立的运行,之间并没有任何的关联关系。接下来我们就来建立3者之间的关联关系,我们以rabbitmq-node01 为基准,将其他的两个节点加入进来。

把 rabbitmq-node02 加入到节点1中:

# 进入到rabbitmq-node02中 rabbitmqctl stop_app # 关闭rabbitmq服务 rabbitmqctl reset# 进行重置 rabbitmqctl join_cluster rabbit@rabbitmq-node01 # rabbitmq-node01为节点1的主机名称 rabbitmqctl start_app # 启动rabbitmq节点

把 rabbitmq-node03 加入到节点1中:

# 进入到rabbitmq-node03中 rabbitmqctl stop_app # 关闭rabbitmq服务 rabbitmqctl reset# 清空节点的状态,并将其恢复都空白状态,当设置的节点时集群中的一部分,该命令也会和集群中的磁盘节点进行通讯,告诉他们该节点正在离开集群。不然集群会认为该节点处理故障,并期望其最终能够恢复过来 rabbitmqctl join_cluster rabbit@rabbitmq-node01 # rabbitmq-node01为节点1的主机名称 rabbitmqctl start_app # 启动rabbitmq节点

进入后台管理系统查看集群概述:

至此也就证明我们的 RabbitMQ 集群就已经搭建好了。

1.3 节点类型

1.3.1 节点类型介绍

在使用 rabbitmqctl cluster_status 命令来查看集群状态时会有 [{nodes,[{disc,[rabbit@rabbitmq-node01,rabbit@rabbitmq-node02,rabbit@rabbitmq-node03]} 这一项信息,

其中的 disc 标注了 RabbitMQ 节点类型。RabbitMQ 中的每一个节点,不管是单一节点系统或者是集群中的一部分要么是内存节点,要么是磁盘节点。内存节点将所有的队列,交换机,绑定关系、用户、权限和 vhost 的元数据定义都存储在内存中,而磁盘节点则将这些信息存储到磁盘中。单节点的集群中必然只有磁盘类型的节点,否则当重启 RabbitMQ 之后,所有关于系统配置信息都会丢失。不过在集群中,可以选择配置部分节点为内存节点,这样可以获得更高的性能。

1.3.2 节点类型变更

如果我们没有指定节点类型,那么默认就是磁盘节点。我们在添加节点的时候,可以使用如下的命令来指定节点的类型为内存节点:

rabbitmqctl join_cluster rabbit@rabbitmq-node01 --ram

我们也可以使用如下的命令将某一个磁盘节点设置为内存节点:

rabbitmqctl change_cluster_node_type {disc , ram}

如下所示:

root@rabbitmq-node02:/# rabbitmqctl stop_app # 关闭rabbitmq服务 Stopping rabbit application on node rabbit@rabbitmq-node02 root@rabbitmq-node02:/# rabbitmqctl change_cluster_node_type ram# 将root@rabbitmq-node02节点类型切换为内存节点 Turning rabbit@rabbitmq-node02 into a ram node root@rabbitmq-node02:/# rabbitmqctl start_app # 启动rabbitmq服务 Starting node rabbit@rabbitmq-node02 root@rabbitmq-node02:/# rabbitmqctl cluster_status# 查看集群状态 Cluster status of node rabbit@rabbitmq-node02 [{nodes,[{disc,[rabbit@rabbitmq-node03,rabbit@rabbitmq-node01]}, {ram,[rabbit@rabbitmq-node02]}]}, {running_nodes,[rabbit@rabbitmq-node01,rabbit@rabbitmq-node03, rabbit@rabbitmq-node02]}, {cluster_name,<<"rabbit@rabbitmq-node01">>}, {partitions,[]}, {alarms,[{rabbit@rabbitmq-node01,[]}, {rabbit@rabbitmq-node03,[]}, {rabbit@rabbitmq-node02,[]}]}] root@rabbitmq-node02:/#

1.3.4 节点选择

RabbitMQ 只要求在集群中至少有一个磁盘节点,其他所有的节点可以是内存节点。当节点加入或者离开集群时,它们必须将变更通知到至少一个磁盘节点。如果只有一个磁盘节点,而且不凑巧它刚好崩溃了,那么集群可以继续接收和发送消息。但是不能执行创建队列,交换机,绑定关系、用户已经更改权限、添加和删除集群节点操作了。

也就是说、如果集群中唯一的磁盘节点崩溃了,集群仍然可以保持运行,但是直到将该节点恢复到集群前,你无法更改任何东西,所以在创建集群的时候应该保证至少有两个或者多个磁盘节点。当内存节点重启后,它会连接到预先配置的磁盘节点,下载当前集群元数据的副本。当在集群中添加内存节点的时候,确保告知所有的磁盘节点(内存节点唯一存储到磁盘中的元数据信息是磁盘节点的地址)。只要内存节点可以找到集群中至少一个磁盘节点,那么它就能在重启后重新加入集群中。

1.4 集群测试

连接集群中某一个节点进行消息的收发操作。

1.5 集群优化

之前搭建的集群存在的问题:不具有负载均衡能力

1.5.1 优化架构

思考的问题:

1、为什么要进行负载均衡?

不具有负载均衡的连接情况:

具有负载均衡的连接情况:

本次我们所选择的负载均衡层的软件是 HAProxy。为了保证负载均衡层的高可用,我们需要使用使用到keepalived 软件,使用 VRRP 协议产生虚拟 IP 实现动态的 IP 飘逸。

keepalived 是以 VRRP 协议为实现基础的,VRRP 全称 Virtual Router Redundancy Protocol,即虚拟路由冗余协议。虚拟路由冗余协议,可以认为是实现路由器高可用的协议,即将N台提供相同功能的路由器组成一个路由器组,这个组里面有一个master和多个 backup,master上面有一个对外提供服务的 vip(该路由器所在局域网内其他机器的默认路由为该vip),master 会定义向 backup 发送 VRRP 协议数据包,当 backup 收不到 VRRP 包时就认为 master 宕掉了,这时就需要根据 VRRP 的优先级来选举一个 backup 当 master。这样的话就可以保证路由器的高可用了。

由于我们的虚拟 IP 是在 Docker 中创建的,而我们的应用程序是基于 Windows 平台开发的,因此是无法直接使用Docker 容器中的虚拟 IP。因此我们需要在宿主机上安装 keepalived,在宿主机上安装 keepalived 的主要目的是为了让 keepalived 产生虚拟服务,后期我们在访问 HAProxy 的时候,直接通过宿主机上的虚拟服务去进行访问即可。

1.5.2 优化实现

HAProxy环境搭建

1、拉取镜像

docker pull haproxy:1.7

2、创建一个 HAProxy 的配置文件 haproxy.cfg

global #工作目录 chroot /usr/local/etc/haproxy #日志文件,使用rsyslog服务中local5日志设备(/var/log/local5),等级info log 127.0.0.1 local5 info #守护进程运行 daemon defaults log 127.0.0.1 local0 err #[err warning info debug] mode http#默认的模式mode { tcp|http|health },tcp是4层,http是7层,health只会返回OK retries 2#两次连接失败就认为是服务器不可用 option redispatch#当serverId对应的服务器挂掉后,强制定向到其他健康的服务器 option abortonclose#当服务器负载很高的时候,自动结束掉当前队列处理比较久的链接 option dontlognull #日志中不记录负载均衡的心跳检测记录 maxconn 4096 #默认的最大连接数 timeout connect 50000ms#连接超时 timeout client ms#客户端超时 timeout server ms#服务器超时 #timeout check 2000#=心跳检测超时 ######## 监控界面配置 ################# listenadmin_stats #监控界面的访问的IP和端口 bind0.0.0.0:8888 #访问协议 modehttp #URI相对地址 stats uri /dbs #统计报告格式 stats realm Global\ statistics #登陆帐户信息 stats authadmin:admin ​ # rabbitmq管理界面配置 listenproxy_rabbitmq_web #访问的IP和端口 bind0.0.0.0:5000 #网络协议 modetcp #负载均衡算法(轮询算法) #轮询算法:roundrobin #权重算法:static-rr #最少连接算法:leastconn #请求源IP算法:source balanceroundrobin # 这里是容器中的IP地址,由于配置的是轮询roundrobin,weight 权重其实没有生效 server rabbitmq_01 172.19.0.50:15672 check weight 1 maxconn 2000 server rabbitmq_02 172.19.0.51:15672 check weight 1 maxconn 2000 server rabbitmq_03 172.19.0.52:15672 check weight 1 maxconn 2000 # 使用keepalive检测死链 option tcpka ​ # rabbitmq服务代理,负载均衡配置 listenproxy_rabbitmq #访问的IP和端口 bind0.0.0.0:5010 #网络协议 modetcp #负载均衡算法(轮询算法) #轮询算法:roundrobin #权重算法:static-rr #最少连接算法:leastconn #请求源IP算法:source balanceroundrobin # 这里是容器中的IP地址,由于配置的是轮询roundrobin,weight 权重其实没有生效 server rabbitmq_01 172.19.0.50:5672 check weight 1 maxconn 2000 server rabbitmq_02 172.19.0.51:5672 check weight 1 maxconn 2000 server rabbitmq_03 172.19.0.52:5672 check weight 1 maxconn 2000 # 使用keepalive检测死链 option tcpka

3、创建 HAProxy 容器

docker run -di --network=docker-network --ip=172.19.0.40 -p 4001:8888 -p 5001:5000 -p 5010:5010 -v /usr/local/haproxy/haproxy-01/config:/usr/local/etc/haproxy --name=haproxy_01 --privileged=truehaproxy:1.7 /bin/bash docker run -di --network=docker-network --ip=172.19.0.41 -p 4002:8888 -p 5002:5000 -p 5011:5010 -v /usr/local/haproxy/haproxy-02/config:/usr/local/etc/haproxy --name=haproxy_02 --privileged=truehaproxy:1.7 /bin/bash

4、进入到容器中,启动 HAProxy

docker exec -it haproxy_01 /bin/bash# 进入容器 haproxy -f /usr/local/etc/haproxy/haproxy.cfg # 启动容器

5、通过浏览器就可以访问 HAProxy 的后端管理界面了: http://192.168.23.131:4002/dbs,然后输入配置的用户名和密码就可以看到如下界面:

HAProxy容器中安装keepalived

1、进入haproxy_01 服务

docker exec -it haproxy_01 /bin/bash

2、安装 keepalived 软件

apt-get update# 更新软件列表,apt-get源不太稳定,建议多执行几次 apt-get install keepalived# 安装keepalived软件

3、安装其他软件,供后期使用

apt-get install net-tools # 安装ifconfig命令

4、创建一个 keepalived.conf 文件

vim /etc/keepalived/keepalived.conf,内容如下所示:

! Configuration File for keepalived ​ vrrp_instanceVI_1 { stateMASTER # 标示状态为MASTER 备份机为BACKUP interfaceeth0 # 定义虚拟网卡 virtual_router_id100# 定义组vriid, 同一组中virtual_router_id必须相同 priority100 # MASTER权重要高于BACKUP 比如BACKUP为99 advert_int1 # MASTER 与 BACKUP 负载均衡器之间同步检查的时间间隔,单位是秒 authentication {# 定义组用户密码 auth_typePASS auth_pass } virtual_ipaddress {#定义docker内ip地址,必须要在和haproxy同一个网段 172.19.0.119 } }

5、启动 keepalived 服务

service keepalived start

6、查看 eth0 上是否已经绑定了虚拟 IP

ip add show eth0

haproxy_02 容器中的 keepalived 软件的配置文件,如下所示:

! Configuration File for keepalived ​ vrrp_instanceVI_1 { stateBACKUP # 标示状态为MASTER 备份机为BACKUP interfaceeth0 # 定义虚拟网卡 virtual_router_id100# 定义组vriid, 同一组中virtual_router_id必须相同 priority99# MASTER权重要高于BACKUP 比如BACKUP为99 advert_int1 # MASTER 与 BACKUP 负载均衡器之间同步检查的时间间隔,单位是秒 authentication {# 定义组用户密码 auth_typePASS auth_pass } virtual_ipaddress {#定义docker内ip地址,必须要在和haproxy同一个网段 172.19.0.119 } }

宿主机中keepalived

1、安装 keepalived

yum install -y keepalived

2、更改 keepalived 的配置文件的内容

> /etc/keepalived/keepalived.conf # 清空原有配置文件的内容 vim /etc/keepalived/keepalived.conf # 编辑配置文件,配置文件中的内容如下所示 ! Configuration File for keepalived vrrp_instance VI_1 { state MASTER interface ens33# 这里是宿主机的网卡,可以通过ip a查看当前自己电脑上用的网卡名是哪个 virtual_router_id 50 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress {# 可以不用指定虚拟ip } } ​ # 虚拟服务器地址 IP 对外提供服务的端口 virtual_server 192.168.23.131 4000 { delay_loop 3# 健康检查时长 单位秒 lb_algo rr# 负载均衡算法 rr|wrr|lc|wlc|lblc|sh|dh:LVS调度算法 lb_kind NAT # 负载均衡转发规则 persistence_timeout 50# http服务会话时长 单位秒 protocol TCP# 健康检查用的是TCP还是UDP # 对应后端真实的docker服务的ip地址和端口号 real_server 172.19.0.119 8888 { # 对应HAProxy的虚拟ip地址和后端管理系统端口 weight 1 } } ​ # rabbitmq的web管理端的虚拟服务器 virtual_server 192.168.23.131 15672 { delay_loop 3# 健康检查时长 单位秒 lb_algo rr# 负载均衡算法 rr|wrr|lc|wlc|lblc|sh|dh:LVS调度算法 lb_kind NAT # 负载均衡转发规则 persistence_timeout 50# http服务会话时长 单位秒 protocol TCP# 健康检查用的是TCP还是UDP ​ # 对应后端真实的docker服务的ip地址和端口号 real_server 172.19.0.119 5000 {# 对应HAProxy的虚拟ip地址和后端管理系统端口 weight 1 } ​ } ​ # rabbitmq的虚拟服务器 virtual_server 192.168.23.131 5672 { delay_loop 3 lb_algo rr lb_kind NAT persistence_timeout 50 protocol TCP ​ # 对应后端真实的docker服务的ip地址和端口号 real_server 172.19.0.119 5010 {# 对应HAProxy的虚拟ip地址和后端rabbitmq的监听端口 weight 1 } ​ }

3、启动keepalived服务,关闭防火墙

systemctl start keepalived systemctl status firewalld.service

1.6 集群监控

1.6.1 RabbitMQ 自带的 Web 管理端的插件

RabbitMQ 作为一款非常成熟的消息中间件,必然少不了监控功能,RabbitMQ 提供了 Web 版的页面监控(只在本地的浏览器端访问地址:http://xxx.xxx.xxx.xxx:15672/,默认端口号是15672)

Web 监控页面如下图所示:

如果只是在测试或生产环境小规模地应用 RabbitMQ 消息队列(比如业务并发访问量较低),那么简单地用用RabbitMQ 自带的 Web 页面进行监控也就足够了。但是,如果对 RabbitMQ 的并发性能、高可用和其他参数都有一些定制化的监控需求的话,那么我们就有必要采用其他的方式来达到该目标。

1.6.2 RabbitMQ 的 tracing 消息轨迹追踪

在使用 RabbitMQ 的时候,如果我们想清楚的知道消息的投递过程,那么此时我们就可以使用这个 tracing 插件来完成。使用步骤如下所示:

1、开启 RabbitMQ 的 tracing 插件

rabbitmq-plugins enable rabbitmq_tracing

开启 tracing 插件 MQ 的 Web 页面可以看到 admin 标签页多了一个选项:

2、配置追踪

如下图指定 vhost 添加一个追踪

Format 的取值:Text,json

Pattern 规则说明:

#匹配所有的消息,无论是发布还是消费的信息

publish.# 匹配所有发布的消息。

deliver.# 匹配所有被消费的消息。

#.test 如果 test 是队列,则匹配已经被消费了的 test 队列的消息。如果 test 是 exchange,则匹配所有经过该 exchange 的消息。

注意:如果是 json 格式的日志,消息体会采用 Base64 进行编码。

1.6.3 采用 RabbitMQ 的 HTTP API 接口进行监控

要构建独立的监控系统,可以使用 RabbitMQ 本身提供的 Restful HTTP API 接口来获取各种业务监控需要的实时数据。当然,这个接口的作用远不止于获取一些监控数据,也可以通过这些 HTTP API 来操作 RabbitMQ 进行各种集群元数据的添加/删除/更新的操作。

下面列举了可以利用 RabbitMQ 的 HTTP API 接口实现的各种操作:

上面的 HTTP API 接口只是列举了 RabbitMQ 所支持的部分功能,读者可以参考 RabbitMQ 官方文档和访问 http://192.168.23.131:15672/api/ 的 Web 页面来获取更多的其他接口信息。业务研发的

同学可以使用 Apache 的 httpcomponents 组件 — HttpClient 或者 Spring 的 RestTemplate 组件生成并发送 HTTP 的 GET/POST/DELETE/PUT 请求至 RabbitMQ Server,根据自己的业务目标完成相应的业务监控需求。

实例代码:RabbitmqMonitorApplication 类

注意:默认的交换机使用 %2F

PS:如果本文对你有帮助的话,可以关注:黑马架构师 继续和我们一起学习哦 ^-^ ~

相关阅读:程序员必知的微服务架构体系

女朋友问我啥是多级缓存?为啥我会懵!!!