从自托管RabbitMQ迁移关键消息系统到Amazon MQ
TLDR: 我们成功将核心RabbitMQ消息基础设施从EKS自托管集群迁移到托管Amazon MQ服务,消除了成为"意外RabbitMQ专家"的重大运维负担。迁移复杂性源于50多个互连服务以及零停机、零消息丢失的要求。我们的策略包括细致审计、镜像设置和使用Shovel插件作为关键故障保护措施的"下游优先"实时切换。结果是获得了更稳定、可预测的平台,释放了工程周期,使我们能够100%专注于为客户构建安全功能,而不是调试基础设施故障。
想象一下:凌晨3点,您的消息代理出现问题。队列深度不断增加,消费者不断掉线,值班工程师疯狂重启他们几乎不了解的Kubernetes集群中的Pod。听起来熟悉吗?
多年来,我们在EKS上运行的自托管RabbitMQ一直面临这种现实。当然,我们对消息基础设施拥有"完全控制",但这种控制伴随着隐藏成本:成为意外的RabbitMQ专家,这是从我们核心使命——加速发布直接惠及客户并帮助他们保护资产的功能——中分散注意力的昂贵运维干扰。
转折点出现在我们意识到消息代理已成为单点故障——不仅在技术上,而且在组织上。只有少数人能够进行故障排除,随着强制性Kubernetes升级的临近,我们知道是时候做出改变了。
于是我们选择了Amazon MQ:AWS的托管RabbitMQ服务,承诺抽象掉运维头痛。
但挑战在于:我们不能简单地切换开关。我们有50多个服务24/7通过队列传输业务关键消息。支付处理、用户通知、数据同步——应有尽有。丢失单个消息是不可接受的。一个错误的举动就可能影响我们安全平台的可靠性。
这就是我们如何在保持零停机和绝对数据完整性的情况下,仔细迁移整个消息基础设施的故事。这并不简单,但这个过程在运维成熟度方面产生了重要经验。
背景:旧系统 vs 新系统
旧设置(EKS上的RabbitMQ)
运行自己的RabbitMQ集群起初感觉很有力量。您拥有完全控制权,可以调整每个设置,它只是Kubernetes集群中的"另一个容器"。但这种控制是有代价的。当RabbitMQ开始出现问题时,团队中需要有人了解足够的集群、内存管理和磁盘使用模式知识来修复它。当我们真正只想在服务之间发送消息时,我们发现自己成为了意外的RabbitMQ专家。
总线因素确实存在。只有少数人觉得能够深入解决RabbitMQ问题。当这些人休假或忙于其他项目时,事件处理时间会比应有的时间长。每个安全补丁都意味着仔细规划停机窗口。每个Kubernetes升级都意味着担心它会影响我们的消息代理。这是伪装成基础设施的技术债务。
警告信号已经存在。暂存环境偶尔会出现奇怪的行为——消息卡住、消费者掉线、无法解释的内存峰值。我们会重启服务,一切恢复正常,但您只能将问题拖延这么长时间。当类似问题开始出现在生产环境中时,即使只是短暂出现,我们也知道我们的时间不多了。
Kubernetes不会停滞不前,您的集群也不应该停滞不前。但是,当关键基础设施运行在上面时,主要版本升级可能会令人紧张。想到我们的消息代理在Kubernetes升级期间可能崩溃——导致一半平台宕机——是我们寻找替代方案的最终推动力。
新设置(Amazon MQ)
使用Amazon MQ,其他人会担心保持RabbitMQ运行。AWS处理集群、备份、您希望永远不会发生但可能发生的故障转移场景。它底层仍然是RabbitMQ,但包装了来自全球运行数千个消息代理的操作专业知识。
AWS处理许多常规运维任务,尽管您仍然需要为主要升级规划维护窗口。不同之处在于,这些任务比我们之前处理的持续修补和故障排除更不频繁且更可预测。监控也变得更简单——您可能仍会使用Grafana面板,但现在它们从CloudWatch拉取数据,而不是需要Prometheus导出器和自定义指标收集。
不过,Amazon MQ不是无服务器的,因此您仍然需要选择正确的实例大小并仔细监控CPU、RAM和磁盘使用情况。由于磁盘空间与实例类型相关,空间不足仍然是一个需要监控和规划的实际问题。关键区别在于您监控的是明确定义的资源,而不是调试神秘的集群行为。
默认安全总是比选择安全更好。Amazon MQ不允许您运行不安全连接,这意味着您不能意外部署具有明文消息流量的内容。在安全审计期间少了一件需要担心的事情,敏感数据泄漏少了一种方式。
当您的消息代理正常工作时,开发人员可以专注于真正重要的业务逻辑。当出现问题时,您仍然会收到Slack警报,队列配置仍然是您需要考虑的事情,但您不再在凌晨2点调试集群问题或调试节点无法相互通信的原因。平台从意外中断转变为具有适当监控的可预测故障。
迁移挑战
复杂的服务依赖关系
50多个服务依赖于RabbitMQ:
- 有些只消费消息
- 有些只生产消息
- 有些两者都做
像许多有机成长的公司一样,我们的RabbitMQ使用已经演变成一个复杂的依赖网络。50多个服务在某些上下文中可能听起来不是一个大数字,但当每个服务可能与多个队列通信,并且许多服务通过消息传递相互交互时,依赖图变得惊人地复杂。开始时简单的服务已经扩展到触及多个队列的触角。新功能建立在现有消息流之上。纸上看起来简单的"更改连接字符串"问题变成了移动部件的精心编排。
零停机要求
消息是业务关键的——停机不是选项。
这些不仅仅是调试日志或可有可无的通知流经我们的队列。支付处理、用户通知、系统之间的数据同步——如果停止工作,会立即破坏用户体验的那种东西。压力是真实的:成功迁移一切或冒着重大业务影响的风险。
迁移风险
风险包括:
- 丢失或重复的消息
- 消费者/生产者失去同步
- 意外的延迟或队列积压
消息系统有一个讨厌的特性,即小问题可以快速级联。落后的消费者可能导致队列备份。开始发送到错误位置的生产者可能产生难以追踪的幽灵流量。在迁移期间,您基本上是在平台仍在运行时重新布线其神经系统——没有"哎呀,让我再试一次"的余地。
我们需要一个计划来解开依赖关系并安全迁移流量。
我们的迁移方法
1. 审计和准备
服务映射和分析
在接触任何东西之前,我们需要了解我们正在处理什么。这意味着检查每个服务、每个队列、每个交换,并绘制出连接。有些是显而易见的——电子邮件服务显然生产到电子邮件队列。其他是令人惊讶的——为什么用户服务从分析队列消费?文档有帮助,但有时唯一确定的方法是阅读代码和检查配置。
RabbitMQ管理API在这个阶段证明是无价的。我们使用它查询所有队列、交换、绑定和连接详细信息——基本上是我们获取消息拓扑完整图片所需的一切。这种自动化方法比尝试从分散的文档和服务配置中拼凑信息可靠得多。
交互式拓扑图显示服务依赖关系和消息流
有了所有这些数据,我们使用go-echarts创建可视化表示,生成显示消息流和依赖关系的交互式图表。我们甚至将信息提供给Figma AI,创建所有连接和队列关系的清晰可视化地图。拥有这些可视化表示使得发现意外依赖关系和规划迁移顺序更加容易。
RabbitMQ连接和依赖关系的可视化地图
可视化帮助我们识别许多服务汇聚的热点和"低挂果实"——不依赖于许多其他组件的服务。通过首先定位这些,我们可以从依赖图中移除节点,这逐渐解开了复杂性,并为上游服务解锁了更安全的迁移路径。每个成功的迁移简化了整体情况,并降低了后续移动的风险。
服务分类
- 仅消费者
- 仅生产者
- 消费者 + 生产者
这种分类成为我们的迁移策略。仅消费者服务是最容易的——我们可以将它们指向新代理而不影响上游系统。仅生产者服务是下一个——只要消费者已经移动,生产者可以安全跟随。消费者+生产者服务是最棘手的,需要特殊处理。
迁移路线图
有计划总是胜过临时发挥。我们可以看到哪些服务首先迁移,哪些留到最后,以及潜在问题区域在哪里。它也有助于沟通——而不是"我们很快会迁移RabbitMQ",我们可以说"我们本周从日志服务开始,然后下周是通知系统"。
安全网策略
Shovel从第一天起就成为我们策略的关键部分。这些插件可以将消息从一个队列复制到另一个队列,甚至跨不同的代理,这意味着我们可以在迁移期间确保消息连续性。与其尝试协调生产商停止发送到旧代理和消费者开始从新代理读取之间的完美时机,不如使用shovel桥接该间隙,并保证传输中没有消息丢失。
2. 构建镜像设置
导出配置
我们从旧RabbitMQ集群导出了完整配置,包括:
- 队列
- 交换
- 用户和权限
RabbitMQ的导出功能成为我们最好的朋友。与其手动重新创建数十个队列和交换,我们可以从旧集群转储整个配置。这不仅仅是为了节省时间——是为了避免可能导致以后奇怪错误的细微差异。队列持久性设置、交换类型、绑定模式——手动操作时容易出错的所有小细节。
镜像设置
然后我们将所有内容导入Amazon MQ以创建相同的设置:
- 相同的队列名称和交换
- 相同的凭据
目标是使新代理尽可能接近即插即用替代品。服务不应该知道它们正在与不同的代理通信——相同的队列名称、相同的交换路由、相同的用户帐户。这种一致性对于顺利迁移至关重要,并且使得如果出现问题更容易回滚。
队列类型优化
我们做了一个战略性改变:大多数队列升级为仲裁队列以提高持久性,除了一个高流量队列,其中仲裁导致性能问题。
仲裁队列是RabbitMQ对经典"如果代理在消息中间崩溃会发生什么"问题的回答。它们更持久但使用更多资源。对于我们的大多数队列,权衡是有意义的。但我们有一个队列每秒处理数百条消息,仲裁共识的开销太大了。有时最好的技术解决方案并不适合您的特定约束。
3. 实时切换
一旦新代理准备就绪,我们开始实时移动服务。但我们不是从生产环境开始——我们在暂存环境中镜像了相同的设置,这成为我们的测试场地。对于每个服务迁移,我们首先在暂存环境中执行切换以验证过程,然后一旦我们确信一切顺利运行,在生产环境中重复相同的步骤。
仅消费者服务
仅消费者服务是我们的练习轮。我们可以移动它们,如果出现问题,爆炸半径是有限的。Shovel插件成为我们的安全网——它将消息从一个队列复制到另一个队列,甚至跨不同的代理。这意味着在迁移期间发送到旧队列的消息仍然会到达新代理上的消费者。没有丢失消息,没有服务中断。
步骤1:将消费者切换到Amazon MQ。
步骤2:添加shovel将消息从EKS RabbitMQ旧队列转发到Amazon MQ新队列。
步骤3:消费者无缝从Amazon MQ读取。
仅生产者服务
一旦消费者愉快地从Amazon MQ读取,我们可以专注于生产者。由于队列和交换名称相同,生产者只需要新的连接字符串。消息将流入相同逻辑目的地,只是托管在不同的基础设施上。
注意:要使生产者有资格迁移,您希望所有下游消费者首先迁移。
步骤1:将生产者切换到Amazon MQ。
步骤2:消息通过新交换/代理流到相同队列。
步骤3:移除shovel(清理)。
关键原则:首先迁移下游消费者,然后迁移生产者以避免丢失消息。
这个原则使我们免于许多潜在头痛。如果您首先移动生产者,您可能将消息发送到尚未有消费者的新代理。首先移动消费者,您总是可以使用shovel或其他机制确保消息到达它们,无论生产者发送到哪里。
消费者 + 生产者服务
这些服务是我们迁移的最终boss。它们不能被视为简单的仅消费者或仅生产者情况,因为它们两者都做。一次性切换它们意味着它们将在所有上游生产者迁移之前开始从Amazon MQ消费,可能错过消息。它们也将在所有下游消费者准备就绪之前开始生产到Amazon MQ。
解决方案需要一些代码手术。这些服务需要单独的消费和生产连接,而不是一个RabbitMQ连接做所有事情。这让我们可以独立迁移消费者和生产方。
步骤1:将消费者端迁移到Amazon MQ(生产者停留在旧代理上)。服务现在从新队列消费(通过shovel),但仍然生产到旧交换。
步骤2:将生产者端切换到Amazon MQ(完整迁移完成)。现在消费和生产都在Amazon MQ上进行。
这使我们能够逐步迁移复杂服务而无需停机。
迁移后监控
旧习惯难改,其中一个习惯是在感觉不对劲时检查仪表板。我们使用从CloudWatch拉取数据的Grafana面板为Amazon MQ重建了监控设置。这简化了我们的指标收集——不再需要自定义导出器或抓取配置。指标直接来自AWS,我们仍然获得习惯的相同可视化仪表板,只是具有更清晰的数据管道。
基本指标
我们专注于四个关键指标,为我们提供消息代理的完整可见性:
- 队列深度 – 显示消费者是否跟上生产者。稳定增加的队列深度表明积压正在建立。
- 连接计数 – 帮助发现连接到代理有问题的服务。突然下降通常表示网络或身份验证问题。
- 代理健康 – 提供基础设施本身是否正常工作的大局视图。AWS提供全面的健康检查。
- CPU和内存使用率 – 关键,因为Amazon MQ在特定实例类型上运行,而不是无服务器基础设施。您需要正确调整实例大小并注意资源耗尽。
警报策略
我们建立了分层警报方法,专注于基础设施级别和服务特定监控:
- 资源使用警报 – CPU和内存警报至关重要,因为您仍然负责选择正确的实例大小。我们为警告设置了Slack通知,为关键阈值设置了PagerDuty警报。
- 服务级别监控 – 每个服务都有自己的队列长度和消费者计数警报。这使团队拥有其特定队列的所有权,并帮助他们在问题成为代理范围问题之前发现其特定消息流的问题。
经验教训
迁移是可行的,但要仔细规划
实时迁移是可能的,但它不是微不足道的,绝对需要仔细规划。
我们在迁移期间避免了停机,但这需要一些准备和许多小心的小步骤。诱惑总是快速移动并完成它,但对于消息系统,您真的负担不起破坏事物。我们有几次接近失误,我们的规划使我们免于潜在问题。
对我们有效的模式
一些使我们的迁移更顺利的方法:
- 下游优先方法:在生产者之前移动消费者。
- 镜像一切:相同的交换、队列、凭据。
- 双代理策略:使用shovel并行运行旧和新。
- 灵活的服务设计:消费者和生产者的单独配置。
这些不是革命性的想法,但在实践中效果很好。下游优先方法起初感觉可怕,但最终显著降低了风险。拥有相同的设置意味着更少的意外。并行运行两个代理给了我们信心和回退选项。
我们得到了什么
迁移比我们预期的更好:
- 过程中没有中断。
- 没有丢失任何消息(据我们所知)。
- 日常操作现在肯定更容易了。
新系统更稳定,尽管我们仍然收到警报并且必须仔细监控事情。主要区别是当出现问题时,通常更清楚问题是什么以及如何修复。花费更少的时间挖掘Kubernetes日志,试图弄清楚为什么rabbit不高兴。
结论
从EKS上的RabbitMQ迁移到Amazon MQ结果证明是值得的,尽管这不是一个简单的切换操作。
主要胜利是减少了对我们团队的运维负担。我们仍然需要监控和维护事情,但围绕集群问题和神秘故障的日常消防大部分已经消失。
如果您正在考虑类似的迁移:
- 花时间首先真正了解您当前的设置。
- 在暂存环境中多次测试一切。
- 计划比您想象的更长时间。
迁移本身是有压力的,但最终结果是更多时间专注于构建功能,而不是照看基础设施。
回顾过去,最困难的部分不是技术复杂性——而是建立我们不会破坏任何重要东西的信心。但通过良好的规划、可视化依赖映射和对墨菲定律的健康尊重,这绝对是可行的。
现在当有人提到"零停机迁移关键基础设施"时,我们不立即想到"不可能"。我们想到"具有挑战性,但我们以前做过"。