作为一个程序员,假设你有 A、B 两个服务,A 服务发出消息后,不想让 B 服务立马处理到。而是要过半小时才让 B 服务处理到,该怎么实现?
这类延迟处理消息的场景非常常见,举个例子,比如我每天早上到公司后都会点个外卖,我希望外卖能在中午送过来,而不是立马送过来,这就需要将外卖消息经过延时后,再投递到商家侧。
那么问题就来了,有没有优雅的解决方案?当然有,没有什么是加一层中间层不能解决的,如果有,那就再加一层。这次我们要加的中间层是消息队列 RocketMQ。
RocketMQ 是什么?
RocketMQ 是阿里自研的国产消息队列,目前已经是 Apache 的顶级项目。和其他消息队列一样,它接受来自生产者的消息,将消息分类,每一类是一个 topic,消费者根据需要订阅 topic,获取里面的消息。
是不是很像我们上篇文章里提到的消息队 Kafka,那么问题很自然就来了,既然都是消息队列,那它们之间有什么区别呢?
RocketMQ 和 Kafka 的区别
RocketMQ 的架构其实参考了 Kafka 的设计思想,同时又在 Kafka 的基础上做了一些调整。
这些调整,用一句话总结就是,"和 Kafka 相比,RocketMQ 在架构上做了减法,在功能上做了加法"。我们来看下这句话的含义。
在架构上做减法
我们来简单回顾下消息队列 Kafka 的架构。kakfa 也是通过多个 topic 对消息进行分类。
- 为了提升单个 topic 的并发性能,将单个 topic 拆为多个 partition。
- 为了提升系统扩展性,将多个 partition 分别部署在不同 broker 上。
- 为了提升系统的可用性,为 partition 加了多个副本。
- 为了协调和管理 Kafka 集群的数据信息,引入Zookeeper作为协调节点。
Kafka 已经是非常强的消息队列了,我们来看下 RocketMQ 在 Kafka 架构的基础上,还能玩出什么花样来。
简化协调节点
Zookeeper 在 Kafka 架构中会和 broker 通信,维护 Kafka 集群信息。一个新的 broker 连上 Zookeeper 后,其他 broker 就能立马感知到它的加入,像这种能在分布式环境下,让多个实例同时获取到同一份信息的服务,就是所谓的分布式协调服务。
但 Zookeeper 作为一个通用的分布式协调服务,它不仅可以用于服务注册与发现,还可以用于分布式锁、配置管理等场景。Kafka 其实只用到了它的部分功能,多少有点杀鸡用牛刀的味道。太重了。
所以 RocketMQ 直接将 Zookeeper 去掉,换成了 nameserver,用一种更轻量的方式,管理消息队列的集群信息。生产者通过 nameserver 获取到 topic 和 broker 的路由信息,然后再与 broker 通信,实现服务发现和负载均衡的效果。
当然,开发 Kafka 的大佬们后来也意识到了 Zookeeper 过重的问题,所以从 2.8.0 版本就支持将 Zookeeper 移除,通过 在 broker 之间加入一致性算法 raft 实现同样的效果,这就是所谓的 KRaft 或 Quorum 模式。
简化分区
我们知道,Kafka 会将 topic 拆分为多个 partition,用来提升并发性能。
在 RocketMQ 里也一样,将 topic 拆分成了多个分区,但换了个名字,叫 Queue,也就是"队列"。
Kafka 中的 partition 会存储完整的消息体,而 RocketMQ 的 Queue 上却只存一些简要信息,比如消息偏移 offset,而消息的完整数据则放到"一个"叫 commitlog 的文件上,通过 offset 我们可以定位到 commitlog 上的某条消息。Kafka 消费消息,broker 只需要直接从 partition 读取消息返回就好,也就是读第一次就够了。
而在 RocketMQ 中,broker 则需要先从 Queue 上读取到 offset 的值,再跑到 commitlog 上将完整数据读出来,也就是需要读两次。
那么问题就来了,看起来 Kafka 的设计更高效?为什么 RocketMQ 不采用 Kafka 的设计?这就不得说一下 Kafka 的底层存储了。
Kafka 的底层存储
Kafka 的 partition 分区,其实在底层由很多段(segment)组成,每个 segment 可以认为就是个小文件。将消息数据写入到 partition 分区,本质上就是将数据写入到某个 segment 文件下。
我们知道,操作系统的机械磁盘,顺序写的性能会比随机写快很多,差距高达几十倍。为了提升性能,Kafka 对每个小文件都是顺序写。如果只有一个 segment 文件,那写文件的性能会很好。但当 topic 变多之后,topic 底下的 partition 分区也会变多,对应的 partition 底下的 segment 文件也会变多。同时写多个 topic 底下的 partition,就是同时写多个文件,虽然每个文件内部都是顺序写,但多个文件存放在磁盘的不同地方,原本顺序写磁盘就可能劣化变成了随机写。于是写性能就降低了。
那问题又又来了,究竟多少 topic 才算多?这个看实际情况,但打太极从来不是我的风格。我给一个经验值仅供参考,8 个分区的情况下,超过 64 topic, Kafka 性能就会开始下降。
RocketMQ 的底层存储
为了缓解同时写多个文件带来的随机写问题,RocketMQ 索性将单个 broker 底下的多个 topic 数据,全都写到"一个"逻辑文件 CommitLog 上,这就消除了随机写多文件的问题,将所有写操作都变成了顺序写。大大提升了 RocketMQ 在多 topic 场景下的写性能。
注意上面提到的"一个"是带引号的,虽然逻辑上它是一个大文件,但实际上这个 CommitLog 由多个小文件组成。每个文件的大小是固定的,当一个文件被写满后,会创建一个新的文件来继续存储新的消息。这种方式可以方便地管理和清理旧的消息。
简化备份模型
我们知道,Kafka 会将 partiton 分散到多个 broker 中,并为 partiton 配置副本,将 partiton 分为 leader和 follower,也就是主和从。broker 中既可能有 A topic 的主 partiton,也可能有 B topic 的从 partiton。主从 partiton 之间会建立数据同步,本质上就是同步 partiton 底下的 segment 文件数据
RocketMQ 将 broker 上的所有 topic 数据到写到 CommitLog 上。如果还像 Kafka 那样给每个分区单独建立同步通信,就还得将 CommitLog 里的内容拆开,这就还是退化为随机读了。于是 RocketMQ 索性以 broker 为单位区分主从,主从之间同步 CommitLog 文件,保持高可用的同时,也大大简化了备份模型。
好了,到这里,我们熟悉的 Kafka 架构,就成了 RocketMQ 的架构。
是不是跟 Kafka 的很像但又简化了不少?
在功能上做加法
虽然 RocketMQ 的架构比 Kafka 的简单,但功能却比 Kafka 要更丰富,我们来看下。
消息过滤
我们知道,Kafka 支持通过 topic 将数据进行分类,比如订单数据和用户数据是两个不同的 topic,但如果我还想再进一步分类呢?比如同样是用户数据,还能根据 vip 等级进一步分类。假设我们只需要获取 vip6 的用户数据,在 Kafka 里,消费者需要消费 topic 为用户数据的所有消息,再将 vip6 的用户过滤出来。
而 RocketMQ 支持对消息打上标记,也就是打 tag,消费者能根据 tag 过滤所需要的数据。比如我们可以在部分消息上标记 tag=vip6,这样消费者就能只获取这部分数据,省下了消费者过滤数据时的资源消耗。
相当于 RocketMQ 除了支持通过 topic 进行一级分类,还支持通过 tag 进行二级分类。
支持事务
我们知道 Kafka 支持事务,比如生产者发三条消息 ABC,这三条消息要么同时发送成功,要么同时发送失败。
是,这确实也叫事务,但跟我们要的不太一样。
写业务代码的时候,我们更想要的事务是,"执行一些自定义逻辑"和"生产者发消息"这两件事,要么同时成功,要么同时失败。
而这正是 RocketMQ 支持的事务能力。
加入延时队列
如果我们希望消息投递出去之后,消费者不能立马消费到,而是过个一定时间后才消费,也就是所谓的延时消息,就像文章开头的定时外卖那样。如果我们使用 Kafka, 要实现类似的功能的话,就会很费劲。但 RocketMQ 天然支持延时队列,我们可以很方便实现这一功能。
加入死信队列
消费消息是有可能失败的,失败后一般可以设置重试。如果多次重试失败,RocketMQ 会将消息放到一个专门的队列,方便我们后面单独处理。这种专门存放失败消息的队列,就是死信队列。Kafka 原生不支持这个功能,需要我们自己实现。
消息回溯
Kafka 支持通过调整 offset 来让消费者从某个地方开始消费,而 RocketMQ,除了可以调整 offset, 还支持调整时间。
所以不那么严谨的说, RocketMQ 本质就是在架构上做了减法,在功能上做了加法的 Kafka。这个总结是不是特别精辟。现在大家通了吗?
最后遗留一个问题。现在看起来,RocketMQ 好像各方面都比 Kafka 更能打。但 Kafka 却一直没被淘汰,说明 RocketMQ 必然是有着不如 Kafka 的地方。是啥呢?性能,严格来说是吞吐量。这就很奇怪了,为什么 RocketMQ 参考了 Kafka 的架构,性能却还不如 Kafka?这个问题,我们下期聊聊。
总结
- RocketMQ 和 Kafka 相比,在架构上做了减法,在功能上做了加法
- 跟 Kafka 的架构相比,RocketMQ 简化了协调节点和分区以及备份模型。同时增强了消息过滤、消息回溯和事务能力,加入了延迟队列,死信队列等新特性。