本系列会分析OpenStack 的高可用性(HA)概念和解决方案:
(2)Neutron L3 Agent HA - VRRP (虚拟路由冗余协议)
(3)Neutron L3 Agent HA - DVR (分布式虚机路由器)
(4)Pacemaker 和 OpenStack Resource Agent (RA)
(5)RabbitMQ HA
(6)MySQL HA
1. RabbitMQ 集群
你可以使用若干个RabbitMQ 节点组成一个 RabbitMQ 集群。集群解决的是扩展性问题。所有的数据和状态都会在集群内所有的节点上被复制,只有queue是例外。默认的情况下,消息只会存在于它被创建的节点上,但是它们在所有节点上可见和可访问。
对于Queue 来说,消息实体只存在于其中一个节点,A、B两个节点仅有相同的元数据,即队列结构。当消息进入A节点的Queue中后,consumer从B节点拉取时,RabbitMQ会临时在A、B间进行消息传输,把A中的消息实体取出并经过B发送给consumer。所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连A或B,出口总在A,会产生瓶颈。该模式存在一个问题就是当A节点故障后,B节点无法取到A节点中还未消费的消息实体。如果做了消息持久化,那么得等A节点恢复,然后才可被消费;如果没有持久化的话,然后就没有然后了。
因此,在集群环境中,队列只有元数据会在集群的所有节点同步,但队列中的数据只会存在于一个节点,数据没有冗余且容易丢,甚至在durable的情况下,如果所在的服务器节点宕机,就要等待节点恢复才能继续提供消息服务。那既然有这种问题,为什么依然有这个选项呢?官方的说法是:
(1)存储空间:如果集群的每个节点都有每个queue的一个拷贝,那么增加节点将无法增加存储容量。比如,如果一个节点可以存放 1GB 的消息,增加另外两个节点只会增加另外两个拷贝而已。
(2)性能:发布消息,将会将它拷贝其它节点上。如果是持久性消息,那么每个节点上都会触发磁盘操作。你的网络和磁盘负载在每次增加节点时都会增加。
可见,RabbitMQ Clustering 技术不能完全解决HA 问题。单纯的集群只适合于在不需要HA的场景中使用。
2. Active/Passive HA 方案
RabbitMQ A/P HA 官方方案 是采用 Pacemaker + (DRBD 或者其它可靠的共享 NAS/SNA 存储) + (CoroSync 或者 Heartbeat 或者 OpenAIS)组合来实现的。需要注意的是 CoroSync 需要使用多播,而多播在某些云环境中是被禁用的,这时候可以改为使用 Heartbeat,它采用单播。其架构为:
实现 HA 的原理:
- RabbitMQ 将数据库文件和持久性消息保存在 DRBD 挂载点上。注意,在 failover 时,非持久性消息会丢失。
- DRBD 要求在某一时刻,只能有一个 node (primary)能够访问其共享存储,因此,不会有多个node 同时写一块数据的风险。这要求必须依赖 Pacemaker 来控制使用 DRBD 共享存储的 node。Pacemaker 使得在某一时刻,只有 active node 来访问 DRBD 共享存储。在 failover 时,Pacemaker 会卸载当前 active node 上的 DRBD 共享存储,并在新的 active node (原 secondary node)上挂载 DRBD 共享存储。
- 在 node 启动时,不会自动启动 RabbitMQ。Pacemaker 会在 active node 启动 RabbitMQ。
- RabbitMQ HA 只会提供一个访问 RabbitMQ 的 虚拟IP 地址。该方案依赖 Pacemaker 来保证 VIP 的切换。
2.1 基于 Pacemaker + DRBD + CoroSync 的 RabbitMQ HA 方案配置
RabbitMQ 官方的 这篇文章介绍了基于 Pacemaker 的 RabbitMQ HA 方案。它同时指出,这是传统的 RabbitMQ HA 方案,并且建议使用 RabbitMQ 集群 + 镜像 Message Queue 的方式来实现 A/A HA。使用 Pacemaker 实现 HA 的方案主要步骤包括:
-
为 RabbitMQ 配置一个 DRBD 设备
-
配置 RabbitMQ 使用建立在 DRBD 设备之上的数据目录
-
选择并绑定一个可以在各集群节点之间迁移的虚拟 IP 地址 (即 VIP )
-
配置 RabbitMQ 监听该 IP 地址
-
使用 Pacemaker 管理上述所有资源,包括 RabbitMQ 守护进程本身
2. 基于集群+镜像队列的A/A 方案
从 3.6.0 版本开始,RabbitMQ 支持镜像队列功能,官方文档在这里。。与普通集群相比,其实质和普通模式不同之处在于,消息实体会主动在镜像节点间同步,而不是在 consumer 取数据时临时拉取。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用。
Mirrorred queue 是 RabbitMQ 高可用的一种方案,相对于普通的集群方案来讲,queue中的消息每个节点都会存在一份 copy, 这个在单个节点失效的情况下,整个集群仍旧可以提供服务。但是由于数据需要在多个节点复制,在增加可用性的同时,系统的吞吐量会有所下降。
选举机制:mirror queue 内部实现了一套选举算法,有一个 master 和多个slave,queue 中的消息以 master 为主。镜像队列有主从之分,一个主节点(master),0个或多个从节点(slave)。当master宕掉后,会在 slave 中选举新的master,其选举算法为最早启动的节点。 若master节点失效,则 mirror queue 会自动选举出一个节点(slave中消息队列最长者)作为master,作为消息消费的基准参考; 在这种情况下可能存在ack消息未同步到所有节点的情况(默认异步),若 slave 节点失效,mirror queue 集群中其他节点的状态无需改变。所以,看起来可以使用两节点的镜像队列集群。
使用:对于publish,可以选择任意一个节点进行连接,rabbitmq内部若该节点不是master,则转发给master,master向其他slave节点发送该消息,后进行消息本地化处理,并组播复制消息到其他节点存储;对于consumer,可以选择任意一个节点进行连接,消费的请求会转发给master,为保证消息的可靠性,consumer需要进行ack确认,master收到ack后,才会删除消息,ack消息会同步(默认异步)到其他各个节点,进行slave节点删除消息。
2.1 配置
多个单独的 RabbitMQ 服务,可以加入到一个集群中,也可以从集群中退出。集群中的 RabbitMQ 服务,使用同样的 Erlang cookie(unix 系统上默认为 /var/lib/rabbitmq/.erlang.cookie)。所有
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
镜像队列的配置通过添加 policy 完成,policy 添加的命令为:
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
2.2 HAProxy 和 RabbitMQ A/A 集群
RabbitMQ 实现镜像队列的方式比较特别。这篇文章进行了深入的阐述。假设有如下的配置:
创建 queue 的过程:
- LB 将 client request 分发到 node 2,client 创建队列 “NewQueue”,然后开始向其中放入 message。
- 最终,后端服务会对 node 2 上的 “NewQueue” 创建一个快照,并在一段时间内将其拷贝到node 1 和 3 上。这时候,node2 上的队列是 master Queue,node 1 和 3 上的队列是 slave queue。
假如现在 node2 宕机了:
- node 2 不再响应心跳,它会被认为已经被从集群中移出了
- node 2 上的 master queue 不再可用
- RabbitMQ 将 node 1 或者 3 上的 salve instance 升级为 master instance
假设 master queue 还在 node 2 上,客户端通过 LB 访问该队列:
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
- 客户端连接到集群,要访问 “NewQueue” 队列
- LB 根据配置的轮询算法将请求分发到一个节点上
- 假设客户端请求被转到 node 3 上
- RabbitMQ 发现 “NewQueue” master node 是 node 2
- RabbitMQ 将消息转到 node 2 上
- 最终客户端成功连接到 node 2 上的 master 队列
可见,这种配置下,2/3 的客户端请求需要重定向,这会造成大概率的访问延迟,但是终究访问还是会成功的。要优化的话,总共有两种方式:
- 直接连到 master queue 所在的节点,这样就不需要重定向了。但是对这种方式,需要提前计算,然后告诉客户端哪个节点上有 master queue。
- 尽可能地在所有节点间平均分布队列,减少重定向概率
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
2.3 镜像队列的负载均衡
使用镜像队列的 RabbitMQ 不支持负载均衡,这是由其镜像队列的实现机制决定的。如前面所述,假设一个集群里有两个实例,记作 rabbitA 和 rabbitB。如果某个队列在rabbitA 上创建,随后在 rabbitB 上镜像备份,那么 rabbitA 上的队列称为该队列的主队列(master queue),其它备份均为从队列。接下来,无论client 访问rabbitA 或 rabbitB,最终消费的队列都是主队列。换句话说,即使在连接时主动连接rabbitB,RabbitMQ的 cluster 会自动把连接转向 rabbitA。当且仅当rabbitA服务down掉以后,在剩余的从队列中再选举一个作为继任的主队列。
如果这种机制是真的(需要看代码最最终确认),那么负载均衡就不能简单地随机化连接就能做到了。要实现轮询,需要满足下面的条件:
- 队列本身的建立需要随机化,即将队列分布于各个服务器
- client 访问需要知道每个队列的主队列保存在哪个服务器
- 如果某个服务器down了,需要知道哪个从队列被选择成为继任的主队列。
要实现这种方案,这篇文章 给出了一个方案:
首先,在建立一个新队列的时候,Randomiser 会随机选择一个服务器,这样能够保证队列均匀分散在各个服务器(这里暂且不考虑负载)。建立队列后需要在Meta data 里记录这个队列对应的服务器;另外,Monitor Service是关键,它用于处理某个服务器down掉的情况。一旦发生down机,它需要为之前主队列在该服务器的队列重新建立起与服务器的映射关系。
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
这里会遇到一个问题,即怎么判断某个队列的主队列呢?一个方法是通过rabbitmqctl,如下面的例子:
1
2
3
|
./rabbitmqctl -p production list_queues pid slave_pids
registration-email-queue <rabbit@mq01.2.1076.0> [<rabbit@mq00.1.285.0>]
registration-sms-queue <rabbit@mq01.2.1067.0> [<rabbit@mq00.1.281.0>]
|
可以看到pid和slave_pids分别对应主队列所在的服务器和从服务器(可能有多个)。利用这个命令就可以了解每个队列所在的主服务器了。
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
3. OpenStack 中RabbitMQ集群的用法和配置
3.1 配置
OpenStack 官方建议至少使用三节点 RabbitMQ 集群,而且推荐配置是使用镜像队列。对于测试和演示环境,使用两节点也是可以。以下OpenStack 服务都支持这种 A/A 形式的 RabbitMQ:
- 计算服务
- 块设备存储服务
- 网络服务
- Telemetry
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
OpenStack 支持如下的 RabbitMQ 配置:
- rabbit_hosts=rabbit1:5672,rabbit2:5672,rabbit3:5672:所有RabbitMQ 服务列表
- rabbit_retry_interval=1: 连接失败时候的重试间隔
- rabbit_retry_backoff=2: How long to back-off for between retries when connecting to RabbitMQ。不太明白其含义。
- rabbit_max_retries=0:最大重试次数。0 表示一直重试
- rabbit_durable_queues=true:true 的话表示使用持久性队列,Kilo 中默认为 false。
- rabbit_ha_queues=true: 设置为 true 的话则使用镜像队列,并设置 x-ha-policy 为 all;但是 Kilo 中其默认值为 false。
具体的配置可以参考 这篇文章 以及 OpenStack 官网,以及 RabbitMQ 官网。
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
3.3 性能配置
OpenStack 支持如下几种 RPC 性能配置:
- rpc_conn_pool_size = 30 (IntOpt) (RPC 连接池的大小)
- rpc_response_timeout = 60 (IntOpt) (等待返回的超时时间,单位是秒)
- rpc_thread_pool_size = 64 (IntOpt) (RPC 线程池的大小)
这些参数在做RPC 性能调试的时候往往需要考虑到。
参考链接:
- http://leejia.blog.51cto.com/4356849/841084
- http://drbd.linbit.com/
- http://kafecho.github.io/presentations/introduction-to-pacemaker/#/8
- http://chuansong.me/n/412792
- http://www.gpfeng.com/?p=603
- http://fengchj.com/?p=2273
- http://onlychoice.github.io/blog/2013/11/12/rabbitmq-ha/
- http://insidethecpu.com/2014/11/17/load-balancing-a-rabbitmq-cluster/
- http://my.oschina.net/hncscwc/blog/186350
- http://www.rabbitmq.com/ha.html
- rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
- -p Vhost: 可选参数,针对指定vhost下的queue进行设置
- Name: policy的名称
- Pattern: queue的匹配模式(正则表达式)
-
Definition: 镜像定义,包括三个部分 ha-mode,ha-params,ha-sync-mode
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- all表示在集群所有的节点上进行镜像
- exactly 表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
- nodes 表示在指定的节点上进行镜像,节点名称通过ha-params指定
- ha-params: ha-mode模式需要用到的参数
- ha-sync-mode: 镜像队列中消息的同步方式,有效值为automatic,manually
-
ha-mode: 指明镜像队列的模式,有效值为 all/exactly/nodes
- Priority: 可选参数, policy的优先级
例如,对队列名称以 ’hello‘ 开头的所有队列进行镜像,并在集群的两个节点上完成镜像,policy的设置命令为:
rabbitmqctl set_policy hello-ha "^hello" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'