Kafka 和 RabbitMQ 有哪些区别,各自适合什么场景?
这个问题很常见,而且很多人对二者的选择也把握不好。
所以我认为撰写一篇专业文章深入探讨Kafka和RabbitMQ的主要区别及其适用哪些具体场景?
同时,这个问题在面试中也经常问到。
下面我会通过 6 个场景,来对比分析一下 Kafka 和 RabbitMQ 的优劣。
一、消息的顺序
每当某个条件满足时(即订单状态发生变化),将有关订单状态变化的消息传递给所有关注该订单变化的系统。
订单会有创建成功、待付款、已支付、已发货的状态,状态之间是单向流动的。

目前我们需要将订单状态变化的消息发送给所有关注订单状态的系统,并采用消息队列作为实现手段。

在这种业务下,我们最想要的是什么?
- 信息的排列:对于同一批次的订单而言,在其生命周期内各环节的状态变化必须遵循明确的时间序列。
- 性能指标(吞吐量):在处理订单业务时我们追求的最大目标是让系统的吞吐能力达到最大值时最好。
- 当系统处理更多的单条事务请求时系统的响应速度会逐渐降低
- 在这种情况下系统能够维持较高的并发度但响应速度会受到限制
- 在这种情况下系统的响应速度会逐渐下降
- 系统在处理大量事务请求时可能会出现性能瓶颈
- 在这种情况下系统的吞吐能力可能会受到限制
- 因此我们需要采取相应的措施来优化系统的性能
- 这样一来系统不仅能够处理更多的事务请求而且其响应速度也会得到显著提升
- 因此我们需要采取相应的措施来优化系统的性能以提高其吞吐能力并减少延迟
在这种情况下,我们先看看 RabbitMQ 是怎么做的。
在处理发消息并广播给多个消费者的情况时,在RabbitMQ中会创建相应的通道以供每个消费者使用。当一条消息发送出去后,在所有相关通道中系统将其复制一份副本以便接收方接收。

RabbitMQ 将消息发送至相应的队列后,接下来需要解决的问题是如何决定如何利用队列处理消息的数量。
如果只是单线程去获取消息,并没有挑战性的话……但是多线程场景下就可能出现问题。
RabbitMQ 允许多个线程同时从同一队列中读取消息但不能保证顺序。其原因在于,在某些情况下(如某个线程尝试读取消息却发生错误时),RabbitMQ 会将该错误的消息重新入队到该队列中以供后续处理)。这种操作会导致整个系统出现消息顺序混乱的问题

在T0时刻时点上,在队列中存有四个消息单元格:分别为A1、B1、B2及A2。其中,在订单系统中记录着两个状态信息:一个是订单A处于待付款阶段(State: Unpaid),另一个是完成支付过程(State: Paid)。同样地,在订单系统中还存在另外两个状态信息:一个是订单B处于待付款阶段(State: Unpaid),另一个则是完成支付过程(State: Paid)。
到了 T1 时刻,消息 A1 被线程 1 收到,消息 B1 被线程 2 收到。此时,一切都还正常。
在T3阶段时,B1出现了错误;与此同时,在线程1处理速度极快的情况下,在消息队列中获得了B2;此时问题逐渐显现
在 T4 时刻,在因为 RabbitMQ 线程消费出现了错误的情况下,“具备将消息重新入队的能力”的特性下,“此时 B1 被会话头重新放置到队列头部”。因此,“如果恰好如此的话”,会导致系统出现乱序处理问题,“因为线程 1 错误地获取了 B1”。具体而言,“原本应在顺序上位于 B2 前面的 B2 状态‘实际上应当是在 B1 处理之后的状态’’却被错误地优先处理了。”
所以,可以看到了,这个场景用 RabbitMQ,出现了三个问题:
- 为了达成发布订阅功能的需求,并非主动进行消息复制操作,则会导致性能下降同时消耗更多资源。
- 多个消费者在实际运行中难以保证消息严格的顺序。
- 由于大量的订单被集中分配到同一个队列中,在这种情况下系统的吞吐量会出现瓶颈。
那么Kafka又怎么样了呢?Kafka在以下三个方面中,在各方面都超越了RabbitMQ。
首先,在Kafka的消息发布与订阅机制中,并不会实际复制实际存在的消息内容。其核心原理在于消费者无需复制实际存在的消息数据就可以直接访问存储于日志文件中的完整消息内容。这种设计使得无论有多少消费者在线等待接收数据时,“每个消费者只需主动定位目标消息在日志文件中的具体存储位置即可”。
其次,Kafka 不会出现消费者出错后,把消息重新入队的现象。
在处理过程中,在Kafka系统中,在处理订单时
所以,对于这个需求 Kafka 更合适。
二、消息的匹配
我曾开发过一套营销系统。这套系统具有一个突出的特点,即高度复杂的规则应用机制。
对于推广内容而言,在选择具体宣传方式时应当与之相协调;而对于不同类型的活动,则应当选择合适的渠道来进行分发传播
总之,数不清的匹配规则是这套系统中非常重要的一个特点。

接着了解 RabbitMQ 的特性时,你可能会发现它支持在消息中添加 routing_key 以及自定义的消息头字段,进而借助特定的Exchange机制,从而轻松实现了基于路由键的消息匹配分发过程。
而 Kafka 呢?如果你要实现消息匹配,开发成本高多了。
首先,无需复杂的配置即可实现自动生成匹配并发送到相应的消费者。
其次,在消费者端方面应当首先将所有的消息不论是否需要全部提取出来。接着,在根据业务需求的需求来独立实现精准及模糊的消息匹配功能。鉴于这种复杂的特性,在必要时应引入规则引擎来辅助处理。
这个场景下 RabbitMQ 扳回一分。
三、消息的超时
在电商业务领域中的一项规定指出,在用户下单后未能在15分钟内完成支付流程时,则会触发系统自动取消订单的行为
你可能奇怪,这种怎么也会用到消息队列的?
我来先简单解释一下,在单一服务的系统,可以起个定时任务就搞定了。
然而,在SOA架构或微服务架构中实施这一策略就不可行。由于多个服务均关注是否进行支付操作,因此若每个服务都需要独立地实现定时任务相关功能,则会导致资源浪费且系统维护难度增加。
在这种情况下,在线系统通常会进行一次抽象:将要执行的任务封装成特定的消息格式。等到指定的时间点到来时,在线的订阅者会接收并处理该消息以执行相应的任务。
希望把消息延迟一定时间再处理的,被称为延迟队列。
针对订单取消这一业务流程,在创建订单时会立即发送包含执行任务信息的通知包到延迟队列中。该消息将在预设15分钟后送达各消费者,并由各系统启动相关订单扫描任务。

RabbitMQ 和 Kafka 消息队列如何选?
先看下 RabbitMQ 的。
RabbitMQ中的消息都配备了时间戳功能,并且每个消息都包含了一个超时时间字段TTL。允许配置消息在RabbitMQ系统中的存活时间,默认情况下未超时的消息会被路由队列处理系统进行重排。
因此,在RabbitMQ中实现延时机制的一种简单方法是配置TTL值,并让消费端进程在消息超出超时时间后接收这些未超时的消息。
不过,在这种情况下存在一个关键问题:设想我们首先将一条过期时间为10秒的A消息加入队列,并随后加入一条过期时间为5秒的B消息。那就会出现一个问题:B消息会不会提前于A消息被归入死信队列呢?
答案是否定性的。B消息将依照先进先出原则运作;当A消息失效时(或在其失效之后),它与之一同转移至死亡消息队列以便于消费者处理。
在 RabbitMQ 的 3.5.8 版本更新后, 官方提供的延迟消息队列交换插件能够有效处理相关问题
采用了该插件后,在优化操作流程的同时,在发送的消息中将信息定向传输至一个专用的Exchange节点。
在消息头字段中设置了延迟时间参数。
Exchange接收消息后不会立即入队,在等待延迟时间截止后才进行入队操作。

再看下 Kafka 的:

Kafka 要实现延迟队列就很麻烦了。
首先, 你需要将这条消息归集到暂存区域中进行处理。
必须开发一个专门用于转发消息的消费者节点,该节点会负责将消息从暂存区域取出。
取出后,由于当前处于未到时间状态,无法立即处理这些信息,也不能将它们存储在本地缓存中,以防数据丢失风险的存在.因此,必须将尚未到达时间节点的消息存储于数据库中.
只有等到预设的时间节点到来后才能将这些数据重新推送给Kafka系统,以便真正的消费者节点能够执行相应的业务逻辑流程。
...
内容已经完成
改写内容
详细说明
这次,RabbitMQ 上那一条条戴手表的消息,才是最好的选择。
四、消息的保持
在微服务架构中,事件溯源模式的应用非常广泛。如果希望利用消息队列来实现这一功能,则通常的做法是将需要追踪的事件转化为特定的消息类型,并按顺序将这些事件发送至消息队列中进行处理。
在事件溯源过程中有一个最为经典的案例即所谓的重放操作。简而言之,在这段时间内将系统中各项事件按时间顺序逐一提取并进行处理。随着业务场景的不同其重复操作次数可能不止一次甚至可能达到N次以上
假设,我们现在需要一批在线事件重放,去排查一些问题。
RabbitMQ此时已经完全无法使用了, 因为消息一旦被取出就会被立即删除. 想要再次使用的话会遇到麻烦, 请原谅.
而 Kafka 的消息则会存储在一个专用的日志记录中。消息不会因被处理而丢失。
所以,对消息不离不弃的 Kafka 相对用过就抛的 RabbitMQ,请选择 Kafka。
五、消息的错误处理
很多情况下,在处理记录数据相关的业务时,Kafka 是一个常见的首选方案.不过有时候当处理的数据量较小时,我宁愿使用 RabbitMQ.
原因就是 Kafka 有一个我很不喜欢的设计原则:
当单个分区中的消息一旦发生消费失败时,则必须终止而不能跳过该条已失败的消息以继续处理后续的消息;这表明不允许出现消息断层的情况。
一旦Kafka的消息出现故障无法处理,则必须拒绝所有后续消费流程
因此,在数据统计对精确度要求不高的情况下选择了Kafka。当消息消费出现问题时,则会导致整个项目的停机状态。这种局面确实令人感到无奈。
RabbitMQ 因为在消息出问题或消费错误时能够自动重新入队或移动消息到死信队列以便处理后续的消息
负面消息如同群体中的负面人物,在Kafka面前必须经过如此严厉的审查程序才算是解决问题。相比之下,则显得更加宽容一些——群体就是普通的人群啊!而这些所谓的"坏蛋"也是各有特色啊——各自为政嘛!
六、消息的吞吐量
Kafka 每秒可处理数以十万计的消息吞吐量,而 RabbitMQ 的吞吐量为每秒少数万条消息。
实际上,在多数公司内部,并非需要依赖于Kafka达到极其高吞吐量的项目非常少见。大多数项目仅需每秒数万条消息的交换量即可满足需求。
对于那些吞吐量不足那么大的项目而言,在引入Kafka时我并不认为这会像引入RabbitMQ那样有效。
为什么呢?
由于Kafka为了提高吞吐量而大幅提升了自身的复杂性,在项目中这通常表现为两个关键问题。
1、配置复杂、维护复杂
Kafka 的参数配置相比 RabbitMQ 是相当繁琐的。例如:涉及磁盘管理的相关参数、集群管理的相关参数、ZooKeeper交互相关的参数以及 Topic级别的相关参数等,则都要求经过深入思考与优化工作。
另外,在使用Kafka时需要考虑的是:JVM环境的配置、消息的持久化存储、集群间的交互协调以及ZooKeeper自身的可靠性与高效性以及其与Kafka之间的可靠性和效率。
2、用好,用对存在门槛
Kafka 的 Producer 和 Consumer 本身要用好用对也存在很高的门槛。
例如,在Kafka中实现消息可靠性保障机制、处理幂等性以及管理事务相关的消息等方面的工作,均需要KafkaProducer具备深入的专业知识。
而Consumer根本不用去提,仅此一项日志偏移管理就让许多Consumer失去了不少头发。
相较于其他消息队列系统而言,RabbitMQ 相对而言非常简单。你可能不需要进行任何配置设置就可以轻松启动并投入使用;一旦运行起来就会运行得非常稳定可靠。即使需要进行一些配置调整, 也只需设置几个简单的参数即可完成操作
鉴于此,在项目中部署消息队列时,请切记要慎重考虑各项配置参数设置。我们应当认识到Kafka因其高效性而被广泛推崇,并非所有人都适合采用这一方案。
总结
可以看到,如果我们要做消息队列选型,有两件事是必须要做好的:
- 列出业务最重要的几个特点
- 深入到消息队列的细节中去比较
