1. 介绍

KafkaRcoketMQ是市面上比较主流的分布式消息队列。本文对他们做一番对比。如果对MQ还不是很熟悉的,可以先看看他们的文档,再来看本文。

2. MQ系统的关键问题——如何处理消息有序、消息重复

2.1 消息有序

Kafka和RocketMQ都不支持严格的消息有序。他们都仅仅保证在一个topic分区(队列)中是有序的。当然如果只使用一个分区或者队列来提供消费,性能肯定是比较差的了。

十分钟入门RocketMQ里面说RocketMQ支持严格消息有序。我觉得可能是指的生产者把消息发往一个队列中。 如果是这样的话,不能算是MQ的严格有序,因为牺牲了太多性能。后续再看看源码确认下。

如果需要做严格消息有序,就需要保证一个生产者对应一个消费者。而分布式MQ不可避免有多个生产者和消费者,要做严格有序可以按照如下方式:

假设M1和M2两条消息需要被有序消费,那么就必须先保证M1消息被消费,收到ACK之后再发送M2。这种方式实际上就是把整个消费过程设计成了“一个生产者对应一个消费者”,显然对性能有很大的影响。

之所以现在MQ都没有做严格消息有序,很多时候也是没必要。因为:

  1. 不关注消息乱序的场景大量存在
  2. 可以通过MQ之外的处理方法来保证有序。例如把需要有序消费的消息都发往一个分区,或者消费端做一些按KEY排序的操作。

2.2 消息重复

由于网络原因、机器原因等其他异常,必然会导致很多时候消息发送失败。通过重发机制来做消息补偿是很重要的措施。但是有时候业务场景不允许重复消费。对于kafka和RocketMQ来说,在MQ内部都仅仅保证at least once的消息分发语义,即不保证消息不重复。

针对消息重复常见解决方案(从MQ本身之外来入手):

  1. 消费者消费消息做幂等
  2. 去重表记录重复消息(需要有唯一ID标示重复消息)

3. RocketMQ相比Kafka多的功能

3.1 事务消息

这个算是RocketMQ的一个亮点了吧,支持事务消息,kafka不支持。不过估计也正是因为引入了事务消息,在吞吐性能上应该就没kafka性能好了。不过为了有更好的性能,在RocketMQ里面将一个大事务拆分成了2个小事务单元。例如下图的Bob给Smith转账,就分了两个事务单元。Smith增加前的事务操作异步执行了。异步执行完毕后,如果确定不需要rollback则提交offset表明消费成功。

事务消息的实现主要是依靠:

  1. 引入一个prepare的消息状态。先发送prepare状态的消息,然后在执行本地事务,执行完本地事务再发送一条消息“确认消息发送”,这会修改已经发送的消息的prepare状态。注意这种prepare消息仍然会被消费者消费。这样消费者才能异步执行其本地事务单元。
  2. broker上会周期扫描这种prepare消息,如果扫描到了就像生产者确认生产者上的本地事务是否执行完毕,如果已经发送了确认消息或者发送了回滚消息,则修改prepare消息的状态;如果事务没有执行完毕,根据策略来决定是直接回滚发送回滚消息,还是broker继续向生产者确认事务执行状态。 正常设计来说一般都是设定一个超时时间不断确认,还确认失败就回滚了。

PS: 这里需要注意,如果消息发送者的事务执行失败,进行回滚,需要消息接受者的事务单元也进行回滚。这个是通过发送一个回滚消息来完成的。消息接受者要回滚还是比较简单的,因为没有在发送者没有确认消息发送之前不会提交offset。所以修改下offset,消费者的事务单元就可以回滚了。

PS: 注意右侧的消息接受者会

3.2 消息过滤

对topic的概念上又引入了更细的tag来区分消息。可以根据tag做消息过滤。也可以根据按照Message Header、body进行过滤。

3.3 消费者的DLQ(dead letter queue)

RocketMQ给消费者实现了个死信队列来记录所有消费失败的消息。这个方便了对消费失败的信息的管理。kafka本身没有实现这个DLQ。不过spring的第三方工具可以实现kafka的DLQ。实现方式就是把处理失败的消息再写到一个专门的topic。

具体参考:Spring Cloud Stream Reference Guide的6.2.2节

3.4 按照时间回溯消费

RocketMQ支持按照时间回溯消费。Kafka如果要完成这个功能需要自己在消息里面添加时间戳字段,然后二分查找offset来查找。

4. 高可用

高可用方面kafka比RocketMQ做的更加好一点。RocketMQ在高可用设计上粒度只控制在Broker。其保证高可用是通过master-slave主从复制来解决的。而kafka控制高可用的粒度是放在分区上。每个topic的leader分区和replica分区都可以在所有broker上负载均衡的存储。 Kafka的这种设计相比RocketMQ这种主从复制的设计有以下好处:

  1. kafka中不需要设置从broker,所有的broker都可以收发消息。负载均衡也做的更好。
  2. kafka的分区选举时自动做的,RocketMQ需要自己指定主从关系
  3. kafka分区的复制份数指定为N,则可以容忍N-1个节点的故障。发生故障只需要分区leader选举下即可,效率很高。

5. 服务发现

RocketMQ貌似自己实现了namesrv,Kafka则依靠ZK。RocketMQ为什么这么做呢?难道是ZK要写的信息太多了有性能问题?

参考资料:

  1. 说说 MQ 之 RocketMQ
  2. 分布式开放消息系统(RocketMQ)的原理与实践
  3. 十分钟入门RocketMQ