大曝光!从RabbitMQ平滑迁移至Kafka架构设计方案!

文章讲述了由于RabbitMQ使用混乱、维护困难、分区容错问题和性能瓶颈,公司决定转向Kafka。提出了两种迁移方案:消费者双订阅和基于灰度单订阅,以实现无损或减小影响的迁移,并强调了验证、监控和回滚的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

历史原因,公司存在多个 MQ 同时使用的问题,我们中间件团队在去年下半年开始支持对 Kafka 和 Rabbit 能力的进行封装,初步能够完全支撑业务团队使用。

鉴于在之前已经基本完全实施 Kafka 管控平台、以及 Kafka 集群迁移管控,我们基本可以认为团队对于 Kafka 的把控能力初具规模。

因此,考虑到以下几点原因,我们决定对 RabbitMQ 不再做维护和支持。

原因

使用混乱和维护困难

基于我们的数据统计和分析发现,基本上没有服务使用我们自己封装的 RabbitMQ 能力,用到的基本上是spring-amqp或者原生的Rabbit 使用方式,存在使用混乱,方式不统一的问题,对于排查问题方面存在更多的问题。

另外考虑到对于 MQ 能力支持要做双份,Kafka 和 Rabbit 都要支持相同的功能,对于人力资源方面存在浪费,当然也由于本身目前没有对 RabbitMQ 非常精通的同学,所以对于维护能力这方面存在担忧。

分区容错问题

RabbitMQ 集群对于网络分区的容错性不高,根据调查发现,系统中 RabbitMQ 高可用方案使用镜像队列,而当 RabbitMQ 出现网络分区时,不同分区里的节点会认为不属于自身所在分区的节点都已经挂了,对于队列、交换器、绑定的操作仅对当前分区有效。

而且,如果原集群中配置了镜像队列,而这个镜像队列又牵涉两个或者更多个网络分区中的节点时,每一个网络分区中都会出现一个 master 节点,对于各个网络分区,此队列都是相互独立的。

在默认的情况下,架构本身存在脑裂的风险,在 3.1 版本下是无法自动恢复的,之后的版本才会自动探测网络分区,人工介入存在数据丢失的风险。

性能瓶颈

镜像队列解决了 Rabbit 高可用的问题,但是并不能增加负载和性能,线上曾经出现过 RabbitMQ 在高流量下的性能问题,就是因为队列由单个节点承载流量,在高并发情况在集群中单个节点存在性能瓶颈。

即便我们目前大部分场景下 MQ 流量不高,但是一旦出现问题,将成为整个系统的性能瓶颈。

另外我们对 Rabbit 做了一些性能方面的测试:

测试集群一共有 4 台磁盘节点,其中每台 16 核,如果我们不做 Sharding,单队列最高 TPS 在 5K 左右,如果是内存节点,官方可以给出的处理极限为 50K/s,如果做 Sharding,单队列处理能力可以达到 10K/s。

上述结论都是以消息能够被正常快速消费为前提,实际上在高流量或者大量消息积压的情况会导致集群性能急剧下降。

运维&管控

基于以上现有的问题和难点,我们决定对 Rabbit 进行全量迁移至 Kafka,以便能在业务高速发展过程中能够保障对于稳定性、高可用、高性能方面的追求。

在方法论和理论体系层面,我们对业务生产有三板斧:可灰度、可监控、可回滚。

同样,对于消息中间件平台运维我们希望有三板斧:可运维、可观测、可管控,那么目前基于 Kafka 的集群管控和 Kafka Manager 的能力我们已经基本做到了上述几点。

  1. 高可用:根据自身经验,Kafka 本身拥有极高的平台可用性
  2. 高性能:Kafka 可支撑极高的 TPS,并且支持水平扩展,可快速满足业务的流量增长需求
  3. 功能支持:在原有两个 MQ 能力基础上,基础支持顺序消息、延时消息、灰度消息、消息轨迹等
  4. 运维管控:基于 Kafka Manager 基础上进行二次开发,丰富管控能力和运维支撑能力,提供给开发、运维、测试更好的使用体验和运维能力。

模型对比

RabbitMQ

Exchange:生产者将消息发送到Exchange,由交换器将消息通过匹配Exchange Type、Binding Key、Routing Key后路由到一个或者多个队列中。

Queue:用于存储消息,消费者直接绑定Queue进行消费消息

Routing Key:生产者发送消息给 Exchange 会指定一个Routing Key。

Binding Key:在绑定Exchange与Queue时会指定一个Binding Key。

Exchange Type:

  • Direct:把消息路由到那些 Binding Key 和 Routing Key 完全匹配的队列中
  • Fanout:把消息转发给所有与它绑定的队列上,相当于广播模式
  • Topic:通过对消息的 Routing Key 和 Exchange、Queue 进行匹配,将消息路由给一个或多个队列,发布/订阅模式
  • Headers:根据消息的 Header 将消息路由到不同的队列,和 Routing Key 无关

Kafka

Topic:发送消息的主题,对消息的组织形式

Broker:Kafka 服务端

Consumer Group:消费者组

Partition:分区,topic 会由多个分区组成,通常每个分区的消息都是按照顺序读取的,不同的分区无法保证顺序性,分区也就是我们常说的数据分片sharding机制,主要目的就是为了提高系统的伸缩能力,通过分区,消息的读写可以负载均衡到多个不同的节点上

迁移方案

综上,我们将要对系统中所有使用RabbitMQ的服务进行迁移操作,整个迁移我们应该保证以下 3 点:

  1. 操作便捷,不能过于复杂,复杂会带来更多的不可控风险
  2. 风险可控,尽最大可能降低迁移对业务的影响
  3. 不影响业务正常运行

消费者双订阅

  1. 对消费者进行改造,同时监听 Rabbit 和 Kafka 消息
  2. 对生产者进行改造,迁移至Kafka发送消息
  3. 等待 Rabbit 遗留消息消费完毕之后,直接下线即可

优点:可以做到无损迁移

缺点:

  1. 需要同时维护两套监听代码,可能有大量的工作量,迁移完成之后还需要再进行一次老代码下线
  2. 消息无法保证顺序性

基于灰度单订阅

这是基于双订阅模式的优化,通过使用我们的灰度/蓝绿发布的能力,做到可以不双订阅,不用同时监听两个消息队列的消息。

  1. 直接修改消费者代码,发布灰度/蓝节点,监听 Kafka 消息
  2. 生产者改造,往 Kafka 发送消息
  3. 等待老的 Rabbit 消息消费完毕,下线,这里存在一个问题就是在进行灰度之后全量的过程中可能造成消息丢失的情况,对于这个问题的解决方案要区分来看,如果业务允许少量的丢失,那么直接全量即可,否则需要对业务做一定的改造,比如增加开关,全量之前关闭发送消息,等待存量消息消费完毕之后再全量。

优点:

  1. 基于双订阅方案改造,可以做到不同时监听两个队列的消息,减少工作量
  2. 可以做到无损迁移

缺点:同样无法保证消息有序性

实际场景问题

上述只是针对现状的迁移方案考虑,那么还有一些跟实际和复杂的问题可能需要考虑。

比如消息的场景有可能不是这种简单的发布/订阅关系,可能存在网状、环状的发布/订阅关系,该如何处理?

其实是一样的道理,只要我们能够梳理清楚每个 Exchange 之间的发布/订阅的关系,针对每个 Exchange 进行操作,就能达到一样的平滑迁移效果。

我们要做的就是针对每个 Exchange 进行迁移,而不是针对服务,否则迁移是无法进行下去的,但是这样带来的另外一个问题就是每个服务需要发布多次,而且如果碰到多个复杂消费或者生产的情况要特别小心。

实施细节

基于现状,我们对所有 Rabbit Exchange 的情况进行了详细的统计,将针对不同的 Exchange 和类型以及功能使用以下方式处理。

  1. 无用的Exchange、无生产者或者无消费者,还有没有任何流量的,可以直接删除
  2. Fanout 类型,Exchange 对应 Topic,Queue 对应 Consumer Group,还有存在使用随机队列的,需要对应多个Consumer Group(单独做一个简单的能力封装处理)
  3. Direct 类型,RoutingKey 对应 Topic,Queue 对应 Consumer Group
  4. Topic 类型,RoutingKey 对应 Topic,Queue 对应 Consumer Group,实际并未发现使用到通配符情况
  5. 延迟队列、重试等功能,基于 spring-kafka 做二次封装

验证&监控&灰度&回滚

验证

  1. 迁移后针对 Rabbit 验证,通过管理平台流量或者日志输出来确认,而且现状是大部分 Exchange 流量都比较小,所以可能需要自行发送消息验证迁移效果。
  2. 迁移后针对 Kafka 流量进行验证可以通过 Kafka Manager 平台或者日志

监控

监控通过 Kafka Manager 平台或者现有监控

灰度

方案本身 Consumer 和 Producer 都可以直接灰度发布,预发验证

回滚

服务回滚,按照发布顺序控制回退顺序

<think>好的,我需要帮用户解答如何将RabbitMQ的anout事件迁移Kafka的设计方法。首先,我需要确认用户提到的“anout”是不是拼写错误,可能是指“fanout”交换器,因为RabbitMQ中有fanout类型,用于广播消息。假设用户确实是指fanout,接下来要考虑迁移的整体架构。 用户的需求是将现有的基于RabbitMQ fanout的事件系统迁移Kafka,可能需要保持类似广播的功能,但KafkaRabbitMQ在设计上有差异。RabbitMQ的fanout是消息被所有绑定到交换器的队列接收,而Kafka的消费者组模型不同,每个分区只能被组内的一个消费者消费。因此,直接的一对一映射可能不太行,需要考虑如何模拟广播。 首先,可能需要分析现有系统,了解fanout的使用场景,比如有多少消费者,消息的分发方式,是否有顺序要求,吞吐量如何。然后设计迁移步骤,可能需要逐步迁移,保持两者并行,验证数据一致性。 接下来,设计架构方面,Kafka的Topic对应RabbitMQ的Exchange,但Kafka的Topic是持久化的,分区会影响并行处理。如果要实现类似fanout的广播,可以让每个消费者组订阅同一个Topic,这样每个组都能获取所有消息。但同一个组内的消费者只能处理不同分区的消息,所以可能需要为每个消费者实例分配不同的组,或者使用不同的组来实现广播效果。 另外,可能需要一个中间层来桥接两者,比如使用Connector工具,或者自己编写生产者消费者来转发消息。例如,使用RabbitMQ的消费者接收消息,然后发布到Kafka的对应Topic。同时要考虑消息的可靠传输,避免丢失,确保至少一次或精确一次的处理。 然后需要考虑消息格式的转换RabbitMQ的消息可能需要序列化调整,以适应Kafka的消息结构。还有处理顺序性、分区策略,比如是否需要根据消息的某些属性进行分区,或者使用轮询等方式平衡负载。 数据一致性方面,迁移过程中可能需要双写,同时向RabbitMQKafka发送消息,逐步将消费者切换到Kafka,并监控是否有遗漏或错误。此外,错误处理和重试机制也很重要,比如Kafka的消息重试和死信队列处理。 性能方面,Kafka的吞吐量通常更高,但需要合理配置分区数和消费者数量,避免成为瓶颈。监控和日志记录也是必要的,确保迁移后的系统稳定。 最后,测试验证阶段,需要确保所有消费者能正确接收处理消息,数据一致,系统稳定后才能下线RabbitMQ的部分。可能还需要考虑回滚方案,以防出现问题。 总结下来,步骤大致是:分析现有系统,设计架构(包括Topic和消费者组策略),实现消息桥接,处理数据格式和分区,确保可靠传输,逐步迁移,测试验证,监控优化,最终切换。需要综合考虑KafkaRabbitMQ的特性差异,调整设计以满足原有fanout的需求。</think>将RabbitMQ的**fanout事件模型**迁移Kafka时,需结合两者设计差异制定迁移策略。以下是分步骤的设计方案--- ### 一、核心差异分析 | **特性** | **RabbitMQ (fanout)** | **Kafka** | |------------------|-----------------------------------------------|----------------------------------------| | 消息分发模式 | 广播:所有绑定队列均收到消息 | 发布-订阅:Topic消息被所有消费者组读取 | | 消费者组逻辑 | 无消费者组,队列独立 | 消费者组共享分区,组内竞争消费 | | 消息持久化 | 队列需显式声明持久化 | Topic数据默认持久化 | | 顺序性保证 | 队列内有序 | 分区内有序 | --- ### 二、迁移设计步骤 #### 1. **映射关系设计** - **RabbitMQ Exchange → Kafka Topic** 将每个fanout交换器映射为一个Kafka Topic,例如原交换器`orders.fanout`对应Topic`orders_topic`。 - **RabbitMQ队列 → Kafka消费者组** 每个原队列对应一个独立的消费者组(如`orders_group1`, `orders_group2`),实现广播效果(每组独立消费全量消息)。 #### 2. **消息路由适配** - **生产者改造** - 原有RabbitMQ生产者改为向Kafka Topic发送消息。 - **消息分区策略**:若需保持顺序性(如订单状态变更),需按业务键(如`order_id`)分区;纯广播场景可使用轮询分区。 ```java // Kafka生产者示例(Java) ProducerRecord<String, String> record = new ProducerRecord<>( "orders_topic", order.getOrderId(), // Key用于分区 order.toJson() ); producer.send(record); ``` #### 3. **消费者改造** - **消费者组配置** 每个原RabbitMQ队列对应一个独立消费者组,确保所有消费者都能收到全量消息: ```properties # 消费者配置示例 group.id=orders_group1 auto.offset.reset=earliest # 从最早偏移量开始消费(首次迁移时) ``` - **并发消费优化** 增加分区数(如6-12)并启动多消费者实例,提升吞吐量: ```java // 消费者并发配置 props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500); // 单次拉取最大记录数 ``` #### 4. **迁移过渡方案** - **双写阶段** 过渡期内同时向RabbitMQKafka发送消息,逐步验证Kafka稳定性: ```python # 伪代码示例:双写逻辑 def publish_event(event): rabbitmq.publish("orders.fanout", event) kafka.producer.send("orders_topic", key=event.id, value=event) ``` - **灰度切换** 按业务优先级逐步将消费者从RabbitMQ切换到Kafka,监控延迟和错误率。 #### 5. **数据一致性保障** - **幂等处理** 消费者端需处理重复消息(如Kafka可能因重试机制导致重复): ```java // 使用唯一ID去重(伪代码) if (!idempotenceCache.contains(event.getId())) { processEvent(event); idempotenceCache.add(event.getId()); } ``` - **事务补偿** 对关键业务实现补偿逻辑,例如记录消费偏移量与业务状态到数据库,定期校对。 --- ### 三、监控与调优 1. **监控指标** - Kafka:Topic吞吐量、消费者延迟(`consumer_lag`)、分区均衡性。 - 业务端:端到端延迟、错误率、消息积压量。 2. **分区扩容** 若发现分区热点,可通过`kafka-topics --alter`动态增加分区数。 3. **消费者弹性伸缩** 结合Kubernetes或云平台实现消费者自动扩缩容。 --- ### 四、常见问题应对 - **消息顺序性冲突** 若业务依赖严格顺序,需确保相同键的消息始终路由到同一分区。 - **历史数据处理** 使用Kafka Connect或自定义脚本将RabbitMQ存量数据导入Kafka- **协议差异** Kafka默认使用二进制协议,需统一序列化格式(如JSON/Avro)。 --- ### 五、最终架构图 ``` 原系统: [Producer] → (RabbitMQ fanout exchange) → [Queue1], [Queue2], [Queue3] 迁移后: [Producer] → (Kafka Topic) ├─ Consumer Group1 → [Consumer1-1], [Consumer1-2] ├─ Consumer Group2 → [Consumer2-1] └─ Consumer Group3 → [Consumer3-1], [Consumer3-2] ``` 通过上述设计,既可保留原有广播语义,又能利用Kafka高吞吐特性,实现平滑迁移
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值