深入浅出消息队列---6、RabbitMQ高可用-灵析社区

提笔写架构


RabbitMQ高可用

Rabbitmq常见的部署模式:单机,普通集群,镜像集群

RabbitMQ普通集群

集群原理

这里先思考两个问题:

  1. 搭建集群的好处?
  • 提供整体消息队列服务的可靠性
  • 可以通过水平扩容提高整体服务的吞吐量

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

  • 不可以

基于存储空间和性能的考虑,在Rabbitmq集群中创建队列,集群只会在单个节点而不是在所有节点上创建队列的进程并包含完整的队列消息(元数据,状态,内容)

 这样只有队列的宿主节点,即所有者节点知道队列的所有信息,所有其他非所有者节点只知道队列的元数据和指向该队列存在那个节点的指针

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

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

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

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

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

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


集群搭建

创建容器

接下来我们就来搭建一个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.同步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中
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集群就已经搭建好了。

节点类型

节点类型介绍

在使用rabbitmqctl cluster_status命令来查看集群状态时会有[{nodes,[{disc,[‘rabbit@rabbitmqnode01’,‘rabbit@rabbitmq-node02’,‘rabbit@rabbitmq-node03’]}这一项信息,其中的disc标注了Rabbitmq节点类型。

Rabbitmq中的每一个节点,不管是单一节点系统或者是集群中的一部分要么是内存节点,要么是磁盘节点。内存节点将所有的队列,交换机,绑定关系、用户、权限和vhost的元数据定义都存储在内存中,而磁盘节点则将这些信息存储到磁盘中

单节点的集群中必然只有磁盘类型的节点,否则当重启Rabbitmq之后,所有关于系统配置信息都会丢失。

不过在集群中,可以选择配置部分节点为内存节点,这样可以获得更高的性能。

节点类型变更

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

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:/#

节点选择

Rabbitmq只要求在集群中至少有一个磁盘节点,其他所有的节点可以是内存节点。当节点加入或者离开集群时,它们必须将变更通知到至少一个磁盘节点

如果只有一个磁盘节点,而且不凑巧它刚好崩溃了,那么集群可以继续接收和发送消息。但是不能执行创建队列,交换机,绑定关系、用户已经更改权限、添加和删除集群节点操作了。也就是说、如果集群中唯一的磁盘节点崩溃了,集群仍然可以保持运行,但是知道将该节点恢复到集群前,你无法更改任何东西,所以在创建集群的时候应该保证至少有两个或者多个磁盘节点。

当内存节点重启后,它会连接到预先配置的磁盘节点,下载当前集群元数据的副本。当在集群中添加内存节点的时候,确保告知所有的磁盘节点(内存节点唯一存储到磁盘中的元数据信息是磁盘节点的地址)。只要内存节点可以找到集群中至少一个磁盘节点,那么它就能在重启后重新加入集群中

集群优化

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

优化架构

思考的问题:

  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的时候, 直接通过宿主机上的虚拟服务去进行访问即可

优化实现

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 300000ms  #客户端超时
   timeout server 300000ms  #服务器超时
    #timeout check 2000     #=心跳检测超时
######## 监控界面配置 ################# 
listen admin_stats
 #监控界面的访问的IP和端口
 bind  0.0.0.0:8888
 #访问协议
   mode       http
 #URI相对地址
   stats uri   /dbs
 #统计报告格式
   stats realm     Global\ statistics
 #登陆帐户信息
   stats auth admin:admin
# rabbitmq管理界面配置
listen proxy_rabbitmq_web
    #访问的IP和端口
   bind  0.0.0.0:5000
    #网络协议
   mode tcp
    #负载均衡算法(轮询算法)
    #轮询算法:roundrobin
    #权重算法:static-rr
    #最少连接算法:leastconn
    #请求源IP算法:source
   balance roundrobin
    # 这里是容器中的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服务代理,负载均衡配置
listen proxy_rabbitmq
    #访问的IP和端口
   bind  0.0.0.0:5010
    #网络协议
   mode tcp
    #负载均衡算法(轮询算法)
    #轮询算法:roundrobin
    #权重算法:static-rr
    #最少连接算法:leastconn
    #请求源IP算法:source
   balance roundrobin
    # 这里是容器中的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=true  haproxy: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=true haproxy: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_instance VI_1 {
    
   state MASTER                       # 标示状态为MASTER 备份机为BACKUP
   interface eth0                     # 定义虚拟网卡
   virtual_router_id  100              # 定义组vriid, 同一组中
virtual_router_id必须相同
   priority  100                       # MASTER权重要高于BACKUP 比如
BACKUP为99
advert_int  1                       # MASTER 与 BACKUP 负载均衡器之间
同步检查的时间间隔,单位是秒
   authentication {                    # 定义组用户密码
       auth_type PASS
       auth_pass  123456
     }
    
     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_instance VI_1 {
    
   state BACKUP                       # 标示状态为MASTER 备份机为BACKUP
   interface eth0                     # 定义虚拟网卡
   virtual_router_id  100              # 定义组vriid, 同一组中
virtual_router_id必须相同
   priority  99                        # MASTER权重要高于BACKUP 比如
BACKUP为99
   advert_int  1                       # MASTER 与 BACKUP 负载均衡器之间
同步检查的时间间隔,单位是秒
   authentication {                    # 定义组用户密码
       auth_type PASS
       auth_pass  123456
     }
    
     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

RabbitMQ镜像集群

镜像队列原理

思考的问题:

  1. 为什么要存在镜像队列

      为了保证队列和消息的高可用
  2. 什么是镜像队列,镜像队列是如何进行选取主节点的?
    引入镜像队列的机制,可以将队列镜像到集群中的其他的Broker节点之上,如果集群中的一个节点失效了,队列能自动的切换到镜像中的另一个节点之上以保证服务的可用性。在通常的用法中,针对每一个配置镜像的队列(一下称之为镜像队列)都包含一个主节点(master)和若干个从节点(slave),如下图所示:


slave会准确地按照master执行命令顺序进行动作,故slave和master上维护的状态应该也是相同的。如果master由于某种原因宕机了,那么"资源最老"的slave会被提升为新的master。

根据slave加入的时间排序,时间最长的slave即为"资历最老"。发送到镜像队列的所有的消息会被同时发往master和所有的slave,如果此时master挂掉了,消息还会在slave上,这样slave提升为master的时候消息也不会丢失。

3.镜像队列的工作模式是什么?

  如下图所示:


  除发送消息(Basic.Publish)外的所有动作都只会向master发送,然后在由master将命令执行的结果广播给各个slave。如果消费者与slave建立连接并进行订阅消息,其实质上都是从master

  上获取消息,只不过看似是从slave上消费而已。比如:消费者与slave建立了TCP连接之后执行一个Basic.GET的操作,那么首先是有slave将Basic.GET请求发往master,再由master准备好数据返回给slave,最后又slave投递给消费者。

镜像策略介绍

针对某一个队列去配置其对应的镜像其实比较简单,我们只需要去添加一个镜像策略即可:

rabbitmqctl set_policy [-p <vhost>] [--priority <priority>] [--apply-to <apply-to>] <name> <pattern> <definition>

指令参数详情:


 definition参数详情:


 实例代码:

 例如:对队列名称为 hello 开头的所有队列镜像镜像,并且在集群的节点 rabbit@10.18.195.57上进行镜 像,队列消息自动同步,policy 的设置命令:

rabbitmqctl set_policy --apply-to queues hello-ha "^hello" '{"ha-mode":"nodes","ha-params":["rabbit@10.18.195.57"],"ha-sync-mode":"automatic"}'

我们也可以通过rabbitmq的后台管理系统来添加镜像策略,如下图所示:



 添加完毕以后,我们再次查看队列的定义信息,如下所示:


 此时也就证明我们的镜像队列配置成功了。

阅读量:393

点赞量:0

收藏量:0