一、背景介绍
公司以前大部分服务在私有云上,因使用有一段时间了,机器比较老化再加上运维成本高,计划将整个机房上云,因负责中间件一块,所以最近将 RabbitMQ 顺利地迁移到云上。
先说下大概部署结构,为 保证高可用,在私有云上部署了 3 台 Broker,应用在配置文件中直接配置 3 个 IP,每次请求时由客户端随机选择 1 台。
本次迁移的目标是:
1、零数据丢失,但不保证消息不重复消费;
2、不出现整个 MQ 集群长时间(2 分钟以上)不可用;
二、方案分析
关于数据丢失,我们先要知道 RabbitMQ 中有哪些数据:
Exchange、Queue、Message。
关于 Exchange 和 Queue,可以设置在不存在的时候创建,但这样难以管控,所以一般我们都是在后台由管理员创建 Exchange 和 Queue,并设置好相应属性,一般来说都要设置为持久化,即 durable 为 true,这样保证在 Broker 重启时 Exchange 和 Queue 还存在。
Message 的持久化则要在发送的时候设置相应的参数,如果用的 amqp-client 这个包,则代码如下:
channel.basicPublish(exchange, routingKey, basicProperties, payload);
其中为 basicProperties 为消息属性,类型为 AMQP.BasicProperties
public static class BasicProperties extends com.rabbitmq.client.impl.AMQBasicProperties {
private String contentType;
private String contentEncoding;
private Map<String,Object> headers;
private Integer deliveryMode;
private Integer priority;
private String correlationId;
private String replyTo;
private String expiration;
private String messageId;
private Date timestamp;
private String type;
private String userId;
private String appId;
private String clusterId;
关注 deliveryMode 属性,要设为 2 消息才会持久化。
再来分析下有哪些场景下可能导致数据丢失:
1、单机宕机无法启动
假设机群有 3 台 Broker:A、B、C,发送消息的机制如下:
如果当前请求落到 A 上,则消息中会保存在 A 这个节点上,持久化也只持久化到这台机上。
如果消息还没有被消费,这个时候 A 宕机,则这条消息就丢了。
针对这种情况,官方推荐用镜像队列的方案,这时消息发送过程如下:
消息先发送到队列所在 Master 机器 A,然后 A 将消息同步到所有其它机器上。
这时即使 A 宕机了,整个集群会做漂移,将这个列列的 Master 漂移到另外 1 台机器上,因为在发送的时候消息已经同步到所有其它机器上了,因此消息不会丢失,但有可能重复消费,这就需要业务做幂等处理。
镜像队列添加命令如下:
Virtual host : "/oneplus"
Name : "all_ha"
Pattern : "^"
Apply to : "Queues"
Priority : 0
ha-mode : all
ha-sync-mode : automatic
2、脑裂
这个我们在生产上碰到过,可以加上以下配置避免:
{cluster_partition_handling, pause_minority}
另外在迁移过程中注意几点:
1)、尽量保证集群总机器数为奇数;
2)、尽量减少跨机房集群存在的时间;
那么除以做为以上我们是不是可以高枕无忧地说整个迁移万无一失了呢,上面只是讲了技术的原因,我们还要来通过其它方面来保障过程的稳定性:
1、测试
搭建整套的环境测试,以验证整个方案的有效性。
2、做好故障预案
A、备份好元数据
可以在 RabbitMQ 管理界面后台操作。
B、核心流程必须有数据核对方案
因为我们是电商,所以要保证整个交流主流程上的 MQ 消息不能丢失,所以需要有一套数据核对及补偿的方案,这个就不在这里详述了。
三、迁移过程
最后说下我们整个迁移的过程
1、第一步就是当前状态
2、在新机器房添加 4 台 Broker
在每台机器上执行命令如下:
rabbitmqctl stop_app
3、下线新、老机房各一台 Broker,保证总数仍为奇数
先在 要下线的机器 上执行命令:
rabbitmqctl stop_app
然后在存活的节点上执行命令
rabbitmqctl forget_cluster_node rabbit@${server1}
4、最后下掉老机房剩下机器
命令同上
最后总结下,我们从以下几方面保证迁移过程的稳定性:
1、配置好各项参数,保证 MQ 内部各数据尽量不丢失
主要做了以下几方面的工作:配置好镜像队列、防止脑裂;
2、在测试环境做好充分的验证;
3、保证主流程消息有核对及补偿方案。
故障演练利器之 ChaosBlade 介绍
线上故障处理实践
Redis 稳定性实践
如何做好稳定性
扩展 Redis:增加 Redis 命令