参考链接:

背景

RocketMQ消息中间件由阿里巴巴团队开发,于2016年贡献给Apache,并作为了Apache的一个顶尖项目。经历过双十一考验,所以很靠谱。

基础名词概念

作为一个消息队列,显然会有:

  1. 生产者/发布者 producer/publisher
  2. 消费者/订阅者 consumer/subscriber

另外,还引入了主题的概念:可以简单的理解为消息的分类

主题模型

主题模型概念

虽然不了解newsletter的实现原理,但大致的表现和这里的主题模型类似。发布者将自己的内容发布到对应的主题中,而只有提前订阅的订阅者才会接收到对应主题下的内容。

RocketMQ的主题模型实现

主题模型是一个设计上的概念,而不同的消息中间件有不同的实现方法:

就比如 Kafka 中的 分区 ,RocketMQ 中的 队列 ,RabbitMQ 中的 Exchange 。

这里借用我参考的博客的图,先直观展示了RocketMQ的实现概念。 image.png

可以看到:

  • 一个topic包含多个队列(实现高并发),队列之间的内容不是重复的
  • 针对同一个topic和同一个消费组,一个队列只允许一个消费者消费(因为队列会记录不同消费者组的消费位置offset,如果一个组有多个消费,就无法正确记录)

技术架构

引入4个角色:

  • broker 存储队列和topic,相当于服务器的角色
  • namespace 类似注册中心的概念,存储broker的路由信息
  • producer 消息生产者,会将消息推到broker(推之前会和namespace交互获得broker通讯信息)
  • consumer 消息消费者,可支持push或pull两个方式获得消息(pull模式下会和namespace交互获得broker信息,发送pull请求)

image.png

消息发送模式:

  • 集群模式 1v1
  • 广播模式 1vn

image.png

上图为官网的架构图。从图中可以看到,在大项目中每个角色其实可能都会有多台:

  • 多台broker:以集群+主备方式组织,集群用于负载均衡(高可用),主备降低了主机宕机的影响,因为备机会定期从主机同步数据,当主机挂了,可以从备机消费(但不可写入)
  • 多台namespace:集群部署,没有主中心(去中心化),单个Broker和所有NameServer保持长连接

相关问题或技术

解决:顺序消费

我们知道,单个队列有有序的,但一个topic往往有多个队列,如何保证将消息分布到多个队列后,消费者还是顺序消费的呢?(因为有些消息的消费需要是有序的,比如想先将一条订单改为状态a,再改为状态b,如果无法保证顺序的话,最终状态就为错误的状态a了)

但是需要澄清的是,我们往往要解决的顺序消费问题,并不是严格的消息生产顺序,而是要满足相对的顺序、业务需要的顺序即可。这是因为严格顺序代价很大,只要一台broker挂掉,就没法正常消费了。满足相对的顺序,例如我们只需要保证同一笔订单的消息是被顺序消费就可以了,其他的顺序就算乱了,并不会有根本上的影响,这就够了。

要实现相对的顺序消费,一个推荐的方法是将同一个语义的消息放到同一个队列(如用hash取模实现)。

解决:重复消费

由于系统波动等问题,可能会出现重复消费多次同一个消息的情况。对于某些消息来说,重复消费是不可接受的。重复消费的解决,在于实际的使用方式,采取对应的解决手段。归根结底是实现幂等。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

如果多次消费的结果和一次消费作用一样,就可以解决重复消费问题。

例如,可以通过数据库中唯一键来保证。

解决:分布式事务

分布式事务,保证的是当事务分布在不同的系统下的事务性。而消息队列所说的分布式事务,是指本系统的执行和消息存储作为一个事务。只要保证各自系统都实现了这种事务性,也就能保证分布下的事务一致性。

RocketMQ是通过事务消息加上事务反查机制来解决的。实现细节:

如果消息是half消息,将备份原消息的主题与消息消费队列,然后 改变主题 为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。

如果没有 事务反查机制 ,如果出现网路波动第4步没有发送成功,这样就会产生 MQ 不知道是不是需要给消费者消费的问题,他就像一个无头苍蝇一样。在 RocketMQ 中就是使用的上述的事务反查来解决的,而在 Kafka 中通常是直接抛出一个异常让用户来自行解决。

解决:消息堆积

消息堆积,就涉及到削峰的概念。往往来源于:

  • 生产太快,考虑限流降级
  • 消费太慢,排查是否消费异常;按需增加消费队列(需要相应增加消息队列)

了解:回溯消费

回溯消费指的是按需消费已经消费过的消息,这才RocketMQ是支持的。broker不会立刻删除消费成功的消息,并支持按照时间维度(ms级别)回溯消费。

了解:消息刷盘及复制机制

消息刷盘,指的是将消息存储持久化到消息队列(disk)中。支持可选同步和异步刷盘:

  • 同步:需要等待返回ACK,更稳定但牺牲性能,适用于金融
  • 异步:无需等待,性能高但可能出现丢失(如系统波动导致),适用于验证码等

消息复制,指的是主broker同步数据到从broker的过程。也分为同步和异步:

  • 同步:只有消息同步到从broker才返回成功
  • 异步:只要主broker写入成功就返回成功

了解:RocketMQ底层存储机制设计

RocketMQ采用三大角色——CommitLog 、ConsumeQueue 和 IndexFile来支持消息存储和消费。

CommitLog 用于存储消息。不区分topic,按照生产推送顺序存储。

ConsumeQueue 作为每个队列的索引文件,保存各topic消息在CommitLog 中的位置(偏移量),用于消费者获取对应主题的消息时获取使用。

IndexFile 索引文件 提供了一种可以通过key或时间区间来查询消息的方法

RocketMQTemplate

RocketMQTemplate是SpringBoot为RocketMQ提供的模版类,我们可以通过重写方法快速实现。