RabbitMQ 和 Kafka 的显著差异

RabbitMQ 是一个消息代理,但是 Apache Kafka 是一个分布式流式系统。好像从语义上就可以看出差异,但是它们内部的一些特性会影响到我们是否能够很好的设计各种用例。

例如,Kafka 最适用于数据的流式处理,但是 RabbitMQ 对流式中的消息就很难保持它们的顺序。

另一方面,RabbitMQ 内置重试逻辑和死信(dead-letter)交换器,但是 Kafka 只是把这些实现逻辑交给用户来处理。

这部分主要强调在不同系统之间它们的主要差异。

消息顺序

对于发送到队列或者交换器上的消息,RabbitMQ 不保证它们的顺序。尽管消费者按照顺序处理生产者发来的消息看上去很符合逻辑,但是这有很大误导性。

RabbitMQ 文档中有关于消息顺序保证的说明:

“发布到一个通道(channel)上的消息,用一个交换器和一个队列以及一个出口通道来传递,那么最终会按照它们发送的顺序接收到。” 

——RabbitMQ 代理语义(Broker Semantics)

换话句话说,只要我们是单个消费者,那么接收到的消息就是有序的。然而,一旦有多个消费者从同一个队列中读取消息,那么消息的处理顺序就没法保证了。

由于消费者读取消息之后可能会把消息放回(或者重传)到队列中(例如,处理失败的情况),这样就会导致消息的顺序无法保证。

一旦一个消息被重新放回队列,另一个消费者可以继续处理它,即使这个消费者已经处理到了放回消息之后的消息。

因此,消费者组处理消息是无序的,如下表所示:

使用 RabbitMQ 丢失消息顺序的例子

当然,我们可以通过限制消费者的并发数等于 1 来保证 RabbitMQ 中的消息有序性。

更准确点说,限制单个消费者中的线程数为 1,因为任何的并行消息处理都会导致无序问题。

不过,随着系统规模增长,单线程消费者模式会严重影响消息处理能力。所以,我们不要轻易的选择这种方案。

另一方面,对于 Kafka 来说,它在消息处理方面提供了可靠的顺序保证。Kafka 能够保证发送到相同主题分区的所有消息都能够按照顺序处理。

回顾第一篇文章介绍,默认情况下,Kafka 会使用循环分区器(round-robin partitioner)把消息放到相应的分区上。

不过,生产者可以给每个消息设置分区键(key)来创建数据逻辑流(比如来自同一个设备的消息,或者属于同一租户的消息)。

所有来自相同流的消息都会被放到相同的分区中,这样消费者组就可以按照顺序处理它们。

但是,我们也应该注意到,在同一个消费者组中,每个分区都是由一个消费者的一个线程来处理。结果就是我们没法伸缩(scale)单个分区的处理能力。

不过,在 Kafka 中,我们可以伸缩一个主题中的分区数量,这样可以让每个分区分担更少的消息,然后增加更多的消费者来处理额外的分区。

获胜者:显而易见,Kafka 是获胜者,因为它可以保证按顺序处理消息。RabbitMQ 在这块就相对比较弱。

消息路由

RabbitMQ 可以基于定义的订阅者路由规则路由消息给一个消息交换器上的订阅者。一个主题交换器可以通过一个叫做 routing_key 的特定头来路由消息。

或者,一个头部(headers)交换器可以基于任意的消息头来路由消息。这两种交换器都能够有效地让消费者设置他们感兴趣的消息类型,因此可以给解决方案架构师提供很好的灵活性。

另一方面,Kafka 在处理消息之前是不允许消费者过滤一个主题中的消息。一个订阅的消费者在没有异常情况下会接受一个分区中的所有消息。

作为一个开发者,你可能使用 Kafka 流式作业(job),它会从主题中读取消息,然后过滤,最后再把过滤的消息推送到另一个消费者可以订阅的主题。但是,这需要更多的工作量和维护,并且还涉及到更多的移动操作。

获胜者:在消息路由和过滤方面,RabbitMQ 提供了更好的支持。

消息时序(timing)

在测定发送到一个队列的消息时间方面,RabbitMQ 提供了多种能力:

①消息存活时间(TTL)

发送到 RabbitMQ 的每条消息都可以关联一个 TTL 属性。发布者可以直接设置 TTL 或者根据队列的策略来设置。

系统可以根据设置的 TTL 来限制消息的有效期。如果消费者在预期时间内没有处理该消息,那么这条消息会自动的从队列上被移除(并且会被移到死信交换器上,同时在这之后的消息都会这样处理)。

TTL 对于那些有时效性的命令特别有用,因为一段时间内没有处理的话,这些命令就没有什么意义了。

②延迟/预定的消息

RabbitMQ 可以通过插件的方式来支持延迟或者预定的消息。当这个插件在消息交换器上启用的时候,生产者可以发送消息到 RabbitMQ 上,然后这个生产者可以延迟 RabbitMQ 路由这个消息到消费者队列的时间。

这个功能允许开发者调度将来(future)的命令,也就是在那之前不应该被处理的命令。

例如,当生产者遇到限流规则时,我们可能会把这些特定的命令延迟到之后的一个时间执行。

Kafka 没有提供这些功能。它在消息到达的时候就把它们写入分区中,这样消费者就可以立即获取到消息去处理。

Kafka 也没用为消息提供 TTL 的机制,不过我们可以在应用层实现。

不过,我们必须要记住的一点是 Kafka 分区是一种追加模式的事务日志。所以,它是不能处理消息时间(或者分区中的位置)。

获胜者:毫无疑问,RabbitMQ 是获胜者,因为这种实现天然的就限制 Kafka。

消息留存(retention)

当消费者成功消费消息之后,RabbitMQ 就会把对应的消息从存储中删除。这种行为没法修改。它几乎是所有消息代理设计的必备部分。

相反,Kafka 会给每个主题配置超时时间,只要没有达到超时时间的消息都会保留下来。

在消息留存方面,Kafka 仅仅把它当做消息日志来看待,并不关心消费者的消费状态。

消费者可以不限次数的消费每条消息,并且他们可以操作分区偏移来“及时”往返的处理这些消息。

Kafka 会周期的检查分区中消息的留存时间,一旦消息超过设定保留的时长,就会被删除。

Kafka 的性能不依赖于存储大小。所以,理论上,它存储消息几乎不会影响性能(只要你的节点有足够多的空间保存这些分区)。

获胜者:Kafka 设计之初就是保存消息的,但是 RabbitMQ 并不是。所以这块没有可比性,Kafka 是获胜者。

容错处理

当处理消息,队列和事件时,开发者常常认为消息处理总是成功的。毕竟,生产者把每条消息放入队列或者主题后,即使消费者处理消息失败了,它仅仅需要做的就是重新尝试,直到成功为止。

尽管表面上看这种方法是没错的,但是我们应该对这种处理方式多思考一下。首先我们应该承认,在某些场景下,消息处理会失败。

所以,即使在解决方案部分需要人为干预的情况下,我们也要妥善地处理这些情况。

消息处理存在两种可能的故障:

  • 瞬时故障:故障产生是由于临时问题导致,比如网络连接,CPU 负载,或者服务崩溃。我们可以通过一遍又一遍的尝试来减轻这种故障。

  • 持久故障:故障产生是由于永久的问题导致的,并且这种问题不能通过额外的重试来解决。比如常见的原因有软件 Bug 或者无效的消息格式(例如,损坏(poison)的消息)

作为架构师和开发者,我们应该问问自己:“对于消息处理故障,我们应该重试多少次?每一次重试之间我们应该等多久?我们怎样区分瞬时和持久故障?”

最重要的是:“所有重试都失败后或者遇到一个持久的故障,我们要做什么?”

当然,不同业务领域有不同的回答,消息系统一般会给我们提供工具让我们自己实现解决方案。
RabbitMQ 会给我们提供诸如交付重试和死信交换器(DLX)来处理消息处理故障。

DLX 的主要思路是根据合适的配置信息自动地把路由失败的消息发送到 DLX,并且在交换器上根据规则来进一步的处理,比如异常重试,重试计数以及发送到“人为干预”的队列。

查看这篇文章[1],它在 RabbitMQ 处理重试上提供了额外的可能模式视角。

在 RabbitMQ 中我们需要记住最重要的事情是当一个消费者正在处理或者重试某个消息时(即使是在把它返回队列之前),其他消费者都可以并发的处理这个消息之后的其他消息。

当某个消费者在重试处理某条消息时,作为一个整体的消息处理逻辑不会被阻塞。

所以,一个消费者可以同步地去重试处理一条消息,不管花费多长时间都不会影响整个系统的运行。

消费者 1 持续的在重试处理消息 1,同时其他消费者可以继续处理其他消息

和 RabbitMQ 相反,Kafka 没有提供这种开箱即用的机制。在 Kafka 中,需要我们自己在应用层提供和实现消息重试机制。

另外,我们需要注意的是当一个消费者正在同步地处理一个特定的消息时,那么同在这个分区上的其他消息是没法被处理的。

由于消费者不能改变消息的顺序,所以我们不能够拒绝和重试一个特定的消息以及提交一个在这个消息之后的消息。你只要记住,分区仅仅是一个追加模式的日志。

一个应用层解决方案可以把失败的消息提交到一个“重试主题”,并且从那个主题中处理重试;但是这样的话我们就会丢失消息的顺序。

我们可以在 Uber.com 上找到 Uber 工程师实现的一个例子。如果消息处理的时延不是关注点,那么对错误有足够监控的 Kafka 方案可能就足够了。

如果消费者阻塞在重试一个消息上,那么底部分区的消息就不会被处理。

获胜者:RabbitMQ 是获胜者,因为它提供了一个解决这个问题的开箱即用的机制。

伸缩

有多个基准测试,用于检查 RabbitMQ 和 Kafka 的性能。

尽管通用的基准测试对一些特定的情况会有限制,但是 Kafka 通常被认为比 RabbitMQ 有更优越的性能。
Kafka 使用顺序磁盘 I/O 来提高性能。从 Kafka 使用分区的架构上看,它在横向扩展上会优于 RabbitMQ,当然 RabbitMQ 在纵向扩展上会有更多的优势。

Kafka 的大规模部署通常每秒可以处理数十万条消息,甚至每秒百万级别的消息。

过去,Pivotal 记录了一个 Kafka 集群每秒处理一百万条消息[2]的例子;但是,它是在一个有着 30 个节点集群上做的,并且这些消息负载被优化分散到多个队列和交换器上。

典型的 RabbitMQ 部署包含 3 到 7 个节点的集群,并且这些集群也不需要把负载分散到不同的队列上。这些典型的集群通常可以预期每秒处理几万条消息。

获胜者:尽管这两个消息平台都可以处理大规模负载,但是 Kafka 在伸缩方面更优并且能够获得比 RabbitMQ 更高的吞吐量,因此这局 Kafka 获胜。
但是,值得注意的是大部分系统都还没有达到这些极限!所以,除非你正在构建下一个非常受欢迎的百万级用户软件系统,否则你不需要太关心伸缩性问题,毕竟这两个消息平台都可以工作的很好。

消费者复杂度

RabbitMQ 使用的是智能代理和傻瓜式消费者模式。消费者注册到消费者队列,然后 RabbitMQ 把传进来的消息推送给消费者。RabbitMQ 也有拉取(pull)API;不过,一般很少被使用。

RabbitMQ 管理消息的分发以及队列上消息的移除(也可能转移到 DLX)。消费者不需要考虑这块。

根据 RabbitMQ 结构的设计,当负载增加的时候,一个队列上的消费者组可以有效的从仅仅一个消费者扩展到多个消费者,并且不需要对系统做任何的改变。

RabbitMQ 高效的伸缩

相反,Kafka 使用的是傻瓜式代理和智能消费者模式。消费者组中的消费者需要协调他们之间的主题分区租约(以便一个具体的分区只由消费者组中一个消费者监听)。
消费者也需要去管理和存储他们分区偏移索引。幸运的是 Kafka SDK 已经为我们封装了,所以我们不需要自己管理。

另外,当我们有一个低负载时,单个消费者需要处理并且并行的管理多个分区,这在消费者端会消耗更多的资源。

当然,随着负载增加,我们只需要伸缩消费者组使其消费者的数量等于主题中分区的数量。这就需要我们配置 Kafka 增加额外的分区。

但是,随着负载再次降低,我们不能移除我们之前增加的分区,这需要给消费者增加更多的工作量。

尽管这样,但是正如我们上面提到过,Kafka SDK 已经帮我们做了这个额外的工作。

Kafka 分区没法移除,向下伸缩后消费者会做更多的工作

获胜者:根据设计,RabbitMQ 就是为了傻瓜式消费者而构建的。所以这轮 RabbitMQ 获胜。

如何选择?

现在我们就如面对百万美元问题一样:“什么时候使用 RabbitMQ 以及什么时候使用 Kafka?”概括上面的差异,我们不难得出以下结论:

优先选择 RabbitMQ 的条件:

  • 高级灵活的路由规则

  • 消息时序控制(控制消息过期或者消息延迟)

  • 高级的容错处理能力,在消费者更有可能处理消息不成功的情景中(瞬时或者持久)

  • 更简单的消费者实现

优先选择 Kafka 的条件:

  • 严格的消息顺序

  • 延长消息留存时间,包括过去消息重放的可能

  • 传统解决方案无法满足的高伸缩能力

大部分情况下这两个消息平台都可以满足我们的要求。但是,它取决于我们的架构师,他们会选择最合适的工具。

当做决策的时候,我们需要考虑上面着重强调的功能性差异和非功能性限制。

这些限制如下:

  • 当前开发者对这两个消息平台的了解

  • 托管云解决方案的可用性(如果适用)

  • 每种解决方案的运营成本

  • 适用于我们目标栈的 SDK 的可用性

当开发复杂的软件系统时,我们可能被诱导使用同一个消息平台去实现所有必须的消息用例。

但是,从我的经验看,通常同时使用这两个消息平台能够带来更多的好处。
例如,在一个事件驱动的架构系统中,我们可以使用 RabbitMQ 在服务之间发送命令,并且使用 Kafka 实现业务事件通知。

原因是事件通知常常用于事件溯源,批量操作(ETL 风格),或者审计目的,因此 Kafka 的消息留存能力就显得很有价值。

相反,命令一般需要在消费者端做额外处理,并且处理可以失败,所以需要高级的容错处理能力。

这里,RabbitMQ 在功能上有很多闪光点。以后我可能会写一篇详细的文章来介绍,但是你必须记住:你的里程(mileage)可能会变化,因为适合性取决于你的特定需求。

Kafka与RabbitMQ区别的更多相关文章

  1. Kafka与RabbitMQ、ActiveMQ协议区别

    对于Kafka与RabbitMQ.ActiveMQ协议,它们具体的区别如下: activemq:        activemq支持主从复制.集群.但是集群功能看起来很弱,只有failover功能,即 ...

  2. Kafka和RabbitMQ有哪些区别,各自适合什么场景?

    经常有人问我 有个 xx 需求,我应该用 Kafka 还是 RabbitMQ ? 这个问题很常见,而且很多人对二者的选择也把握不好. 所以我决定写篇文章来详细说一下:Kafka 和 RabbitMQ ...

  3. 消息中间件选型分析——从Kafka与RabbitMQ的对比来看全局

    一.前言 消息队列中间件(简称消息中间件)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成.通过提供消息传递和消息排队模型,它可以在分布式环境下提供应用解耦 ...

  4. IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?

    1.前言 在IM这种讲究高并发.高消息吞吐的互联网场景下,MQ消息中间件是个很重要的基础设施,它在IM系统的服务端架构中担当消息中转.消息削峰.消息交换异步化等等角色,当然MQ消息中间件的作用远不止于 ...

  5. 转 Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和优势

    Kafka.RabbitMQ.RocketMQ等消息中间件的对比 —— 消息发送性能和优势 引言 分布式系统中,我们广泛运用消息中间件进行系统间的数据交换,便于异步解耦.现在开源的消息中间件有很多,前 ...

  6. Kafka、RabbitMQ、RocketMQ等 消息中间件 介绍和对比

    文章目录 1.前言 2.概念 2.1.MQ简介 2.2.MQ特点 2.2.1.先进先出 2.2.2.发布订阅 2.2.3.持久化 2.2.4.分布式 3.消息中间件性能究竟哪家强? 3.1.Kafka ...

  7. Kafka、RabbitMQ、RocketMQ等消息中间件的介绍和对比(转)

    前言在分布式系统中,我们广泛运用消息中间件进行系统间的数据交换,便于异步解耦.现在开源的消息中间件有很多,前段时间产品 RocketMQ (MetaQ的内核) 也顺利开源,得到大家的关注. 概念MQ简 ...

  8. Kafka、RabbitMQ、RocketMQ等消息中间件的介绍和对比

    本博客强烈推荐: Java电子书高清PDF集合免费下载 https://www.cnblogs.com/yuxiang1/p/12099324.html 前言 在分布式系统中,我们广泛运用消息中间件进 ...

  9. 消息中间件选型分析:从 Kafka 与 RabbitMQ 的对比看全局

    本文转载自消息中间件选型分析:从 Kafka 与 RabbitMQ 的对比看全局 前言 消息队列中间件(简称消息中间件)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布 ...

随机推荐

  1. CF R631 div2 1330 E Drazil Likes Heap

    LINK:Drazil Likes Heap 那天打CF的时候 开场A读不懂题 B码了30min才过(当时我怀疑B我写的过于繁琐了. C比B简单多了 随便yy了一个构造发现是对的.D也超级简单 dp了 ...

  2. JAVA编程中你一定要掌握的“快捷键”

    JAVA编程常用快捷键 相信很多编程小白刚开始的时候,看向大神的时候都是双膝跪地满眼泪水的膜拜之情~不因为别的,就是因为他们可以随随便便敲出很多行代码,而且他们没有动鼠标!这时候就有人问了:“怎么才能 ...

  3. Nginx一个server主机上80、433,http、https共存

    如果一站点既要80 http访问,又要443https访问. 要让https和http并存,不能在配置文件中使用ssl on,配置listen 443 ssl; 实例 server { listen ...

  4. Java中增强一个类的几种方法

    今天有人问我怎么增强一个类的功能.博客刚好没东西,今天就讲讲增强类. 增强的手段有三种类型: 1.继承或者实现接口:特点是被增强对象不能变,增强的内容不能变. 2.装饰着模式:特点是被增强对象可变,但 ...

  5. troubleshoot之:用control+break解决线程死锁问题

    目录 简介 死锁的代码 control+break命令 Full thread dump 死锁检测 Heap信息 总结 简介 如果我们在程序中遇到线程死锁的时候,该怎么去解决呢? 本文将会从一个实际的 ...

  6. 2020-08-08:有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据?

    福哥答案2020-08-08: 参考答案:A.通过shell 或python 等调用api,结果先暂存本地,最后将本地文件上传到 Hive 中.B.通过 datax 的 httpReader 和 hd ...

  7. C#LeetCode刷题之#594-最长和谐子序列​​​​​​​​​​​​​​(Longest Harmonious Subsequence)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3800 访问. 和谐数组是指一个数组里元素的最大值和最小值之间的差 ...

  8. X86汇编——计算斐波那契数列程序(详细注释和流程图说明)

    X86汇编实现斐波那契数列 程序说明: 输入斐波那契数列的项数, 然后依次输出斐波那契数列, 输入的项数小于256且为数字, 计算的项数不能超过2^16次方, 输入失败是 不会回显数字 因为存结果是A ...

  9. 后端排序时去掉element表格排序的null状态

    经常会遇到远程排序,需要去掉null状态的排序,当设置sortable='custom'时,设置sort-orders为['ascending', 'descending']是不生效的.然后查到了一种 ...

  10. vue跳转页面问题记录

    跳转到别的页面带参数 const space = this.pageHelperspace['search'] = this.searchconst query_params = Object.ass ...