RocketMQ

RocketMQ产生的原因

在早期阶段,阿里时构建了基于ActiveMQ 5.x (5.3之前)的分布式消息中间件。他们的跨国企业将其用于异步通信、搜索、社交网络活动六、数据管道,甚至在其贸易流程中。随着贸易业务吞吐量的增加,来自消息集群的压力也变得紧迫。

根据阿里的研究,随着使用中队列和虚拟主题的增加,AvtiveMQ IO模块达到了瓶颈。虽然尽力的通过节流、断路器或降级来解决这个问题,但效果依然不佳。

所以开始关注当时流行的消息传递解决方案Kafka,但不幸的时,Kafka无法满足要求,尤其是在低延迟和高可靠性方面。在这种情况下,决定发明一个新的消息传递引擎来处理更广泛的用例集,从传统的发布/订阅场景到大容量实时零丢失容忍交易系统。

  • Kafka为什么不能满足需求?

Kafka是一个分布式流媒体平台,诞生于日志聚合案例。它不需要太高的并发性。在阿里巴巴的一些大型案例中,我们发现原来的模型已经不能满足我们的实际需求。因此,我们开发了一个名为RocketMQ的消息中间件,它可以处理广泛的用例,从传统的发布/订阅场景到要求苛刻的大容量实时事务系统,不能容忍任何消息丢失。现在,阿里巴巴,RockerMQ集群每天处理超过5000亿个时间,为超过3000个核心应用提供服务。

  • Kafka对于分区的设计
    写入的生产者并行性受分区数量的限制。消费者消费的并行程度,也受消费的分区数量的限制。假设分区数为20,则最大鬓发消费消费者数为20.每个主题由固定数量的分区组成。分区号决定了单个代理可以拥有的最大主题数,而不会显性影响性能。

  • 为什么Kafka不能支持更多的分区
    每个分区存储整个消息数据,虽然每个分区都是有序写入磁盘的,但是随着同时写入的分区数量的增加,从操作系统的角度来看,写入变得随机
    由于数据文件比较分散,Linux IO Group Commit机制很难使用

RocketMQ如何支持多分区

所有消息数据都存储在提交日志文件中,所有写入都是完全顺序的,而读取时随机的。ConsumerQueue存储了实际的用户消费日志信息,这些信息也是按照顺序刷到磁盘的。

优点:每个消费队列都是轻量级的,并且包含有限数量的元数据。对磁盘的访问是完全顺序的,避免了磁盘锁争用,并且在创建大量队列时不会产生高磁盘IO等待。

缺点:消息消费会先读取消费队列,然后是提交日志。这个过程在最坏的情况下会带来一定的成本。提交日志和消费队列需要在逻辑上保持一致,这给编程模型带来了额外的复杂性。

定义与模型

  1. 消息模型
    RocketMQ主要由Producer、Broker、Consumer三部分组成,其中Producer负责生产消息,Consumer负责消费消息,Broker负责存储消息。Broker在实际部署过程中对应一台服务器,每个Broker可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的Broker。Message Queue用于存储消息的物理地址,每个Topic中的消息地址存储与多个Message Queue中。Consumer Group由多个Consumer实例构成。
  2. 消息生产者(Producer)
    负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统中产生的消息发送到Broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认消息,单向发送不需要。
  3. 消息消费者
    负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费、推动式消费

主题(Topic)
表示一类消息的集合,每个主题包含若干条消息,每条消息只能属于一个主题,是RocketMQ进行消息订阅的基本单位

代理服务器 (Broker Server)
消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉去请求做准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。

名字服务
名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题对应的Broker IP列表。多个Namesrc实例组成集群,但相互独立,没有信息交换。

拉取式消费
Consumer消费的一种类型,应用通常主动调用Consumer的拉消息方法,从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息,应用就会启动消费过程。

推动式消费
Consumer消费的一种类型,该模式下Broker收到数据后会主动推送给消费端,该消费模式一般实时性较高。

生产者组
同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃,则Broker服务器会联系通过一生产者组的其他生产者实例以提交或回溯消费。

消费者组(Cosumer Group)
同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ支持两种消息模式:集群消费和广播消费

集群消费
集群消费模式下,相同Consumer Group的每个Consumer实例都接收全量的消息

顺序消息
顺序消费模式下,消费者通过同一个消费队列(Topic分区,称作Message Queue)收到的消息是有顺序的,不同消息队列收到的消息可能是无序的

消息
消息系统所传输信息的物理载体,生产和消费数据的最小单位,每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID,且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能

标签
为消息设置的标志,用于同一主题下区分不同类型的消息。来自统一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效的保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑,实现更好的扩展性

安装RocketMQ

https://blog.csdn.net/w598882992/article/details/127509962

消息发送方式

  • 方式一:同步消息
    这种可靠性同步的发送方式使用的比较广泛,比如:重要的消息通知,短信通知
  • 方式二:异步消息
    异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间的等待Broker的响应
  • 方式三:批量消息
    批量发送消息能显著提高传递小消息的性能。限制是这些批量消息应该有相同的topic,而且不能是延时消息。此外,这一批消息的总大小不应超过4MB
  • 方式四:延迟消息
    现在RocketMQ并不支持任意时间的延时,需要设置几个固定的延时等级,从1s到2h分别对应着等级1到18,在org/apache/rocketmq/store/config/MessageStoreConfig.java类中:messageDelayLevel=“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”;
  • 方式五:通过Tag进行消息过滤
    在大多数情况下,TAG是一个简单而有用的设计,其可以用来选择您想要的消息,例如: DefultMQPushConsumer consumer = new DefaultMQPushConsumer(“CID_EXAMPLE”);
    consumer.subscribe(“TOPIC”,“TAGA || TAGB || TAGC”); //消费者将接收包含TAGA或TAGB或TAGC的消息
  • 方式六:通过SQL进行消息过滤
    通过Tag进行限制是一个消息只能有一个标签,这对于复杂的场景可能不起作用。在这种情况下,可以使用SQL表达式筛选消息。SQL特性可以通过发送消息时的属性来进行计算。在RocketMQ定义的语法下,可以实现一些简单的逻辑。下面是一个简单的例子:

通过SQL进行消息过滤RocketMQ定义了一些基本语法来支持这个特性:
数值比较,比如: >,>=,<,<=,BETWEEN,=
字符比较,比如:=,<>,IN
空值校验,比如:IS NULL 或者 IS NOT NULL
逻辑符号,比如:AND,OR,NOT
常量支持类型为:
数值,比如:123,3.1415
字符,比如:‘abc’,必须用单引号包裹起来
NULL,特殊的常量
布尔值,比如:TRUE 或 FALSE

请注意:默认情况下,RocketMQ是不支持SQL过滤消息的。会报错:“The Broker does not support consumer to filter message by SQL92”, 为了解决这个问题,我们需要在broker.conf配置文件中,添加enbalePropertyFilter=true,因为默认是false的。并且启动broker的时候,通过-c指定配置文件。

  • 方式七:顺序消息

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。
在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

  • 方式八: 事务消息

Apache RocketMQ在4.3.0版中已经支持分布式事务消息,这里RocketMQ采用了2PC的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息,如下图所示。
在这里插入图片描述事务消息发送及提交:
(1)发送消息(half消息)
(2)服务端响应消息写入结果
(3)根据发送结果执行本地事务(如果写入失败,此时half消息对事务不可见,本地逻辑不执行)
(4)根据本地事务状态执行Commit或者Rollback (Commit操作生成消息索引,消息对消费者可见)

补偿流程:
(1)对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
(2)Producer收到回查消息,检查回查消息对应的本地事务的状态
(3)根据本地事务状态,重新Commit或者Rollback

事务消息共有三种状态,提交状态、回滚状态、中间状态
TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息
TransactionStatus.RollbackTransaction: 回滚事务,它代表该消息将被删除,不允许被消费
TransactionStatus.Unknown: 中间状态,它代表需要检查消息队列来确认状态

设计与架构

RocketMQ架构上主要分为四部分,分别为Producer、Consumer、NameServer和BrokerServer。如下图所示:

RocketMQ架构Producer
消息发布的角色,支持分布式集群方式部署。Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息投递,投递的过程支持快速失败并且低延迟。

Consumer
消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,他提供实时消息订阅机制,可以满足大多数用户的需求。

NameServer
NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。
主要包括两个功能:
(1)Broker管理
NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
(2)路由信息管理
每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Consumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递与消费。
NameServer通常也是集群的方式部署,各实例间相互不进行消息通讯。
Broker是向每一台NameServer注册自己的路由信息,所以在每一个NameServer实例删改你都保存一份完整的路由信息。
当某个NameServer因某种原因下线了,Broker任然可以向其他NameServer同步其路由信息,Producer和Consumer任然可以动态感知Broker的路由的信息。

BrokerServer

Broker主要负责消息的存储、投递和查询以及服务高可用保证,为了实现这些功能。Broker包含了以下几个重要子模块
在这里插入图片描述Remoting Module
整个Broker的实体,负责处理来自Client端的请求
Client Manager
负责管理客户端(Producer/Consumer)和维护Consumer的Topic订阅消息
Store Service
提供方便简单的API接口处理消息存储到物理硬盘和查询功能
HA Service
高可用服务,提供Master Broker和Slave Broker之间的数据同步功能
Index Service
根据特定的Message key对投递到Broker的消息进行索引服务,以提供消息的快速查询

Message

系统所传输消息的物理载体,生产和消费数据的最小单元。每条消息必须属于一个Topic, Rocket MQ中每条数据拥有唯一的MessageID, 且可以携带具有业务表示的Key。

Topic: 主题
表示一类消息的集合,每个主题都包含若干条消息,每条消息都只能属于一个主题,Topic是MQ进行消息订阅的基本单位

Queue: 消息队列
组成Topic的最小单元,默认情况下一个Topic会对应多个Queue, Topic是逻辑概念,Queue是物理概念,在Cosnumer消费Topic消息时迪岑则拉取Queue的消息。发送消息时执行该消息的Topic,RocketMQ会轮询该Topic下的所有队列将数据发出去。

在这里插入图片描述Tag: 消息标志,用于同一主题下区分不同类型的消息
来自统一业务单元的消息,可以根据不同业务目的在统一主题下设置不同标签。标签能够有效的保持代码的清晰度和连贯性,并优化RocketMQ提供的查询系统。消费者可以根据Tag实现不同字主题的不同消费的处理逻辑,实现更好的可扩展性。

UserProperties, 用户属性
用户自定义的属性集合,属于Message的一部分

ProducerGroup,生产者组
同一类Producer的集合,这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃,则Broker服务器会联系同一个生产者组的其他生产者实例以提交或回溯消费。

ConsumerGroup, 消费者组
同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致,消费者组使得在消息消费方面,实现负载均衡和容错的目标变得非常容易。要注意的是,消费者组的消费者实例必须订阅完全相同的Topic

Group, Topic, Tag之间的关系

在这里插入图片描述

消息存储

RocketMQ消息的存储是由ConsumerQueue和CommitLog配合完成的,消息真正的物理存储文件是CommitLog,ConsumeQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个Topic下的每个Message Queue都有一个对应的ConsumeQueue文件。
消息存储

页缓存与内存映射

页缓存(PageCache)是OS对文件的缓存,用于加速对文件的读写。一般来说,程序对文件进行顺序读写的速度几乎接近于内存的读写速度,主要原因就是由于OS使用PageCache机制对读写访问操作进行了性能优化,将一部分的内存用作PageCache。
对于数据的写入,OS会先写入至Cache内,随后通过异步的方式由pdflush内核线程将Cache内的数据刷盘至物理磁盘上。
对于数据的读取,如果一次读取文件时出现未命中PageCache的情况,OS从物理磁盘上访问读取文件的同时,会顺序对其他相邻块的数据文件进行预读取。

在RocketMQ中,ConsumeQueue逻辑消费队列存储的数据较少,并且是顺序读取,在Page Cache机制的预读取作用下,Consume Queue文件的读性能几乎接近读内存,即使在有消息堆积情况下也不会影响性能。而对于CommitLog消息存储的日志数据文件来说,读取消息内容时候会产生较多的随机访问读取,严重影响性能。如果选择合适的系统IO调度算法,比如:设置调度算法为“Deadline"(此时块存储采用SSD的话),随机读的性能也会有所提升。

另外,RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中,利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中(这种MMap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转换为直接对内存地址进行操作,从而极大的提高了文件的读写效率(正因为需要使用内存映射机制,而其限制条件之一就是一次只能映射1.5G-2G的文件至用户态的虚拟内存,所以RocketMQ的文件存储都是用定长结构1G来存储,方便一次将整个文件映射至内存)

消息刷盘

  • 同步刷盘
    只有消息在真正持久化至磁盘后RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是不错的保障,但是性能上会有较大影响,一般适用于金融业务应用该模式较多。

  • 异步刷盘
    能够充分利用OS的PageCache优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。

消息刷盘

高可用性-主从集群

通过搭建RocketMQ集群,采用多Master搭配多Slave的方式,实现整个RocketMQ集群的高可用性的。

  1. Master节点
    在broker.conf配置文件中,如果brokerId等于0,则表明这个Broker是Master
    其中,通过brokerRole,也标识这个Broker是Master还是Slave
    Master Broker支持读和写

  2. Slave节点
    如果brokerId大于0,则表明这个Broker是Slave
    Slave Broker仅支持读

  • Producer端高可用
    在创建Topic的时候,把Topic的多个Message Queue创建在多个Broker组上(相同Broker名称,不同brokerId的机器组成一个Broker组),这样当一个Broker的Master不可用后,其他组的Master仍然可用,Producer任然可以发送消息。Rocket MQ目前还不支持把Slave自动转成Master,如果机器资源不足,需要把Slave转成Master,则要手动停止Slave角色的Broker,更改配置文件,用新的配置文件启动Broker。

  • Consumer端高可用
    Consumer端默认会从Master Broker节点读取消息,但是如果Master负载较高或者Master不可用了,那么Consumer会自动的切换为去Slave Broker节点中读取数据。

  • 主从复制
    在RocketMQ集群中,消息需要从Master复制到Slave上,可以通过在broker.conf里配置brokerRole来确定复制方式。
    其中,如果时主节点,则可以配置SYNC_MASTER(同步复制)或ASYNV_MASTER(异步复制);
    如果是从节点,可以配置SLAVE。

那么下面我们来详细介绍一下者这两种复制方式:

  1. 同步复制
    同步复制方式是等Master和Slave都写成功后才反馈给客户端写成功状态
    在同步复制方式下,如果Master出故障,Slave上有全部的备份数据,容易恢复但是同步复制会增大数据写入延迟,降低系统吞吐量。
  2. 异步复制
    异步复制方式是只要Master写成功就可以反馈给客户端写成功状态。
    在异步复制方式下,系统拥有较低的延迟和较低的吞吐量,但是如果Master除了故障,有些数据因为没有被写入Slave,有可能会损失;

负载均衡 - Producer端

Producer端在发送消息时,会先根据Topic找到指定的TopicPublishInfo,在获取了TopicPublishInfo路由信息后,RocketMQ的客户端在默认方式下selectOneMessageQueue()方法会从TopicPublishInfo中的messageQueueList中选择一个队列(MessageQueue)进行发送消息。具体的容错策略均在MQFaultStrategy这个类中定义。

这个有一个sendLatencyFaultEnable开关变量:

  1. 如果开启
    在随机递增取模的基础上,再过滤掉not available的Broker代理
    所谓的”latencyFaultTolerance",是指对之前失败的,按一定的时间做退避。例如,如果上次请求的latency超过550Lml,就退避3000Lms;超过1000L,就退避60000L;
  2. 如果关闭
    采用随机递增取模的方式选择一个队列(MessageQueue)来发送消息

latencyFaultTolerance机制是实现消息发送高可用的核心关键所在。

负载均衡 - Consumer端

在RocketMQ中,Consumer端的两种消费模式(Push/Pull)都是基于拉模式来获取消息的。

而push模式只是对pull模式的一种封装,其本质实现为消息拉取线程在从服务器拉取到一批消息后,然后提交到消息消费线程池后,又"马不停蹄“的继续向服务器再次尝试拉取消息。如果未拉取到消息,则延迟一下又继续拉取。

在两种基于拉模式的消费方式中,均需要Consumer端知道从Broker端的哪一个消息队列中去获取消息。因此,有必要在Consumer端来做负载均衡,即: Broker端中多个MessageQueue分配给同一个ConsumerGrou中的哪个Consumer消费

在Consumer启动后,他就会通过定时任务不断的向RocketMQ集群中的所有Broker实例发送心跳包(其中包含了,消费分组名称、订阅关系集合、消息通信模式和客户端id的值等信息)。Broker端在收到Consumer的心跳信息后,会将他维护在ConsumerManager的本地缓存变量-consumerTable,捅死并将封装后的客户端网络通道信息保存在本地缓存变量-channelInfoTable中,为之后做Consumer端的负载均衡提供可以依据的元数据信息。

在Consumer实例的启动流程中的启动MQClientInstance实例部分,会完成负载均衡服务线程–RebalanceService的启动(每隔20s执行一次)。会根据消费者通信类型为”集群模式“还是”广播模式“做不同的逻辑处理。

负载均衡 – Consumer端-集群模式

在集群消费模式下,每条消息只需要投递到订阅这个Topic的Conusmer Group下的一个实例即可。RocketMQ采用主动拉取的方式拉取并消费消息,在拉取的时候需要明确指定拉取哪一条message queue。

  1. 而每当实例的数量有变更,都会触发一次所有实例的负载均衡,这时候会按照queue的数量和实例的数量平均分配queue给每个实例,默认的分配算法是AllocateMessageQueueAveragely,如右上图
  2. 还有一种平均的算法是AllocateMessageQueueAveragelyByCircle,也是平均分摊每一条Queue,只是以环状轮流分queue的形式,如右中图。

由于【广播消费模式】下要求一条消息需要投递到一个消费组下面所有的消费者实例,所以也没有消息被分摊消费的说法。在实现上,其中一个不同就是在consumer分配queue的时候,所有consumer都分在所有的queue, 如右下图

负载均衡

消息重试

  • 顺序消息重试
    对于顺序消息,当消费者消费消息失败后,消息队列RocketMQ会自动不断进行消息重试(每次间隔时间为1秒),这时,应用会出现消息消费被阻塞的情况。因此,在使用顺序消费时,务必保证应用能够及时监控并处理消费失败的情况,避免阻塞现象的发生。

  • 无序消息重试
    对于无序消息(普通、定时、延时、事务处理),当消费者消费失败时,您可以通过设置返回状态达到消息重试的结果。无序消息的重试只针对集群消费方式生效;广播方式不提供失败重试特性,即消费失败后,失败消息不再重试,继续消费新的消息。
    RocketMQ默认允许每条消息最多重试16次,如果消息重试16次后任然失败,消息将不再投递。需要注意的是,一条消息无论重复多少次,这些重试消息的Message ID不会改变。每次重试的间隔时间如下:

消息重试

死信队列

当一条消息第一次消费失败,RocketMQ会自动进行消息重试。但是如果达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确的消费该消息,此时,RocketMQ不会立刻将消息丢弃,而是将其发送到该消费者对应的死信消息(Dead-Letter Message)中。

死信队列具有以下特性:
不会被消费者正常消费
有效期与正常消息相同,均为三天,3天后会被自动删除。因此,请在私信消息产生后的三天内及时处理

死信队列具有以下特性:
一个死信队列对应以恶Group ID, 而不是对应单个消费者实例。
如果一个Group ID未产生死信消息,RocketMQ不会为其创建相应的死信队列
一个死信队列包含了对应Grooup ID产生的所有死信消息,不论该消息属于哪个Topic.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值