好用的消息队列有很多,一般来说其中的一种就可以满足大部分的需求了。消息队列作为重要的消息中间件,对注册中心也有要求,所以在技术选型的时候,除了考虑应用场景以外,也需要考虑到各个组件之间的搭配。
常见的消息队列:kafka、rocketMQ、rabbitMQ、redis 等
各种消息队列各有千秋,但是要满足项目的最优解,还是要考虑到各方各面:
- 首先是库的支持,如果项目本身没有库能够作为某个队列引擎,那么就只能换一个队列或者自己造轮子。
- 其次是组件兼容,早期 kafka 是必须部署 zookeeper 作为注册中心的,后来才放弃使用。类似的,如果你的项目中的某一组件不兼容某个队列,最好换一个更搭配的。
- 最后是项目需求,项目中对于消息的可靠性和并发性需求也是选型的重点难点之一。毕竟成本有限,所以常见的设计模式是 redis + 其他队列。因为 redis 本身就是万金油,但由于 redis 本身不可靠,所以只能用来传输一些并不太重要的消息。
消息队列 | 架构 | 消息存储 | 堆积 | 消息事务 | 负载均衡 | 集群方式 | 可用性 | 消息重复 |
---|---|---|---|---|---|---|---|---|
kafka | broker | 内存+磁盘+数据库 | 大量 | 支持 | 支持 | 无状态集群 | 非常高 | at least once、at most once |
rabbitMQ | exchange+queue | 内存+磁盘 | 少量 | 支持 | 较差 | 简单集群 | 高 | 同上 |
rocketMQ | name server+broker | 磁盘 | 大量 | 支持 | 支持 | 主从集群 | 非常高 | at least once |
消息队列 | 吞吐量 | 订阅形式和消息分发 | 顺序消费 | 消息确认 | 消息回溯 | 消息重试 | 并发度 |
---|---|---|---|---|---|---|---|
kafka | 极大 | topic、topic 正则匹配 | 支持 | 支持 | 支持 | 可实现 | 高 |
rabbitMQ | 较大 | direct、topic、Headers 和 fanout | 不支持 | 支持 | 不支持 | 可实现 | 极高 |
rocketMQ | 大 | topic/messageTag、消息类型/属性正则匹配 | 支持 | 支持 | 不支持 | 支持 | 高 |
一般来说 kafka 和 rocketMQ 就足以应对所有的场景,但是如果项目不算太大,并发量没有达到百万级别的,选择 rabbitMQ 可能更合适。
接下来将对 kafka 和 rocketMQ 的各个组件进行更深度的研究:
消息存储
- 共同点: 一个实例对应一个 broker,通常一个 broker 对应一台服务器。
- 不同点:
- kafka 最小的存储单元是分区,主从分区数据相同;rocketMQ 最小的存储单元是 broker,主从 broker 数据相同。
- kafka 消息存储在磁盘和内存中; rocketMQ 消息存储在磁盘的 commitLog 文件中,队列中实际只存放 commitLog 的索引信息,消费时会根据索引去 commitLog 取。
commitLog 文件限制大小为 1G,满了以后会创建一个新的 commitLog 文件保存数据。 队列存储格式的特性保证了写过程的顺序写盘,大量数据 IO 都顺序发生在同一个 commitLog。 加上 RocketMQ 是累计 4k 才强制从 PageCache 刷入磁盘的,所以高并发写性能突出。
消息事务
消息事务即分布式事务,在消费者消费消息成功之后,生产者才能进行事务提交。 若其中任意角色发生了异常,都会导致消息失效以及事务回滚。
但现实场景中会出现存在必须强一致性的分布式场景,这就需要依靠一致性事务来解决了。 kafka 和 rocketMQ 都同时支持 XA 事务和 TCC 补偿性事务;
不管是那种事务,本质上都是对事务状态的流转。
强一致性场景下,要避免使用事件驱动,消息的发送不可靠,所以需要是同步的。 事件驱动可以通过返回异步+消息通道来实现结果的回调,异常处理程序订阅异常消息,但是设计难度大。
对于一致性的问题,有两种解决办法:
- 不使用一致性事务,将需要强一致性操作的操作同步进行,不需要强一致性的操作交给消息队列。 消费成功将结果存到一个指定的地方提供给用户查询。 消费失败达到一定次数,消息就会放到死信队列中交给专门消费者处理。 但是之后如果强一致性操作增加,则会增加耦合。
- 使用一致性事务,可能会导致用户等待时间过长。
- 使用一致性事务,且将服务设置为异步。将服务处理设置为异步之后,就可以直接返回响应。 然后将处理结果(成功 or 失败)存到一个指定的地方提供给用户查询。 当异步线程池满时,则返回服务器繁忙。
负载均衡
- 相同点:
- 同一个 topic 的生产者,默认通过轮询的方式
- 同一个 topic 的消费者,会均匀分散到不同的分区上进行请求
- 不同点:
- kafka 通过主分区和副本实现;rocketMQ 通过主从 broker 实现。
- kafka 可以指定发送到某个分区;rocketMQ 不行。
- kafka 元数据存放在 broker 上,客户端会从 broker 中获取 topic 的路由信息。 rocketMQ 元数据存放在 nameserver 上,客户端需要从 nameserver 上获取 topic-broker 的路由信息。
集群模式
- 不同点: kafka 是无状态集群,每台服务器既是 Master 也是 Slave。 rocketMQ broker 是多主从集群,但 name server 几乎是无状态节点。
消息重复
- 相同点: 支持 at least once(至少一次)
- 不同点: kafka 支持 at most once(最多一次)
至少消费一次是通过有状态消息实现的。消息消费完成之后,需要返回 ack 到消息队列进行状态流转。
最多消费一次和至少消费一次的区别在于,他是在发送消息之后,立刻进行消息状态流转。
不管是哪个队列都无法准确实现 exactly once,即精确消费一次,因为消息处理所耗费的时间无法确定。 一般来说,需要确保 at least once。对于消息的幂等,可以通过客户端实现。
吞吐量 TPS
- 不同点:
- Kafka 按批次发送消息和消费消息,允许将多个小消息合并发生,也允许取出一个批次的小消息。需要通过幂等解决部分消息消费后失败的情况。
- RocketMQ 只能单次发送消息,但是允许 consumer 设置每次获取的消息数。
订阅形式和消息分发
- 相同点:
- 同个 topic 消息,producer 默认轮询队列发送消息。
- 消费同一个 topic 的 consumer 个数要少于对应的 topic 队列个数,多余的 consumer 无法进行消费。
- 不同点:
- kafka 可以指定发达的分区;rocketMQ 不允许。
- kafka 正常情况下 consumer 只会订阅一个分区; rocketMQ 则会每隔一段时间去 namespace 中读取 topic-broker 信息,可能存在切换。
- kafka 消息只会被同个 consumer group 下的一个 consumer 消费 rocketMQ 消息可以选择集群消费模式(单播)和广播消费模式
消息重试
- kafka 可以通过指定分区 offset 回溯实现消息重试。
- rocketMQ 内置消息定时重发机制。 发送端会定时轮转到下一个 broker 进行重试,总时长不超过设置的阈值。 接收端的重试使用的比较少,因为消息本身是允许重复消费的,而消费时出现异常是不会返回 ack 的。
并发度
相同点:一个线程一个消费者,可以通过消费者多开线程来提高并发。