RocketMQ作为一款纯Java、分布式、队列模型的开源消息中间件,支持事务消息、顺序消息、批量消息、定时消息、消息回溯等。
在一个队列中可靠的先进先出(FIFO)和严格的顺序传递 (RocketMQ可以保证严格的消息顺序,而ActiveMQ无法保证)
pull其实就是消费者主动从MQ中去拉消息,而push则像rabbit MQ一样,是MQ给消费者推送消息。但是RocketMQ的push其实是基于pull来实现的。它会先由一个业务代码从MQ中pull消息,然后再由业务代码push给特定的应用/消费者,其实底层就是一个pull模式。
RocketMQ提供亿级消息的堆积能力,依然保持写入低延迟
满足至少一次消息传递语义(RocketMQ原生就是支持分布式的,而ActiveMQ原生存在单点性)
目前主流的 MQ 主要是 RocketMQ、kafka、RabbitMQ,其主要优势有:
RocketMQ主要有四大核心组成部分:NameServer、Broker、Producer以及Consumer四部分。
**Name Server是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。NameServer 是整个 RocketMQ 的“大脑” ,它是 RocketMQ 的服务注册中心。**所以 RocketMQ 需要先启动 NameServer 再启动 Rocket 中的 Broker。
**名称服务器(NameServer)用来保存 Broker 相关元信息并给 Producer 和 Consumer 查找Broker 信息。**NameServer 被设计成几乎无状态的,可以横向扩展,节点之间相互之间无通信,通过部署多台机器来标记自己是一个伪集群。
**每个 Broker 在启动的时候会到 NameServer 注册,Producer 在发送消息前会根据 Topic 到NameServer 获取到 Broker 的路由信息,进而和Broker取得连接。Consumer 也会定时获取 Topic 的路由信息。**所以从功能上看应该是和 ZooKeeper 差不多,据说 RocketMQ 的早期版本确实是使用的ZooKeeper ,后来改为了自己实现NameServer 。
Name Server和ZooKeeper的作用大致是相同的,从宏观上来看,Name Server做的东西很少,就是保存一些运行数据,Name Server之间不互连,这就需要broker端连接所有的Name Server,运行数据的改动要发送到每一个Name Server来保证运行数据的一致性(这个一致性确实有点弱),这样就变成了Name Server很轻量级,但是broker端就要做更多的东西了。
而ZooKeeper呢,broker只需要连接其中的一台机器,运行数据分发、一致性都交给了ZooKeeper来完成。
**消息服务器(Broker)是消息存储中心,主要作用是接收来自 Producer 的消息并存储,Consumer 从这里取得消息。它还存储与消息相关的元数据,包括用户组、消费进度偏移量、队列信息等。**另外Broker 有 Master 和 Slave 两种类型,Master 既可以写又可以读,Slave不可以写只可以读。
Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的Broker Name,不同的BrokerId来定义,BrokerId为0表Master,非0表示Slave。Master也可以部署多个。
从物理结构上看 Broker 的集群部署方式有四种:单 Master 、多 Master 、多 Master 多Slave(同步刷盘)、多 Master多 Slave(异步刷盘)。
这种方式一旦 Broker 重启或宕机会导致整个服务不可用,这种方式风险较大,所以显然不建议线上环境使用。
所有消息服务器都是 Master ,没有 Slave 。这种方式优点是配置简单,单个 Master 宕机或重启维护对应用无影响。缺点是单台机器宕机期间,该机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受影响。
**每个 Master 配置一个 Slave,所以有多对 Master-Slave,消息采用异步复制方式,主备之间有毫秒级消息延迟。**这种方式优点是消息丢失的非常少,且消息实时性不会受影响,Master 宕机后消费者可以继续从 Slave 消费,中间的过程对用户应用程序透明,不需要人工干预,性能同多 Master 方式几乎一样。缺点是 Master 宕机时在磁盘损坏情况下会丢失极少量消息。
**每个 Master 配置一个 Slave,所以有多对 Master-Slave ,消息采用同步双写方式,主备都写成功才返回成功。**这种方式优点是数据与服务都没有单点问题,Master 宕机时消息无延迟,服务与数据的可用性非常高。缺点是性能相对异步复制方式略低,发送消息的延迟会略高。
Producer也称为消息发布者,负责生产并发送消息至 Topic。RocketMQ提供了发送:同步、异步和单向(one-way)的多种范例。
**同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于可能链路耗时较长而对响应时间敏感的业务场景。**例如用户视频上传后通知启动转码服务。假如过一段时间检测到某个信息发送失败,可以选择重新发送。
**异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包。**一般用于重要通知消息,例如重要通知邮件、营销短信。
单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于某些耗时非常短但对可靠性要求并不高的场景,例如日志收集。
生产者组(Producer Group)是一类 Producer 的集合,这类 Producer 通常发送一类消息并且发送逻辑一致,所以将这些 Producer 分组在一起。从部署结构上看生产者通过 Producer Group 的名字来标记自己是一个集群。
Consumer也称为消息订阅者,负责从 Topic 接收并消费消息,消费者从brokers那里拉取信息并将其输入应用程序。
消费者组(Consumer Group)一类 Consumer 的集合名称,这类 Consumer 通常消费同一类消息并且消费逻辑一致,所以将这些 Consumer 分组在一起。消费者组与生产者组类似,都是将相同角色的分组在一起并命名。
RocketMQ中的消息有个特点,同一条消息,只能被某一消费组其中的一台机器消费,但是可以同时被不同的消费组消费。
消费者在拉取消息时,首先需要向 Broker 请求消息。Broker 收到请求后,会根据消费者的 offset 值返回指定数量的消息。消费者消费完消息后,需要将 offset 提交给 Broker,以便下次拉取时可以继续从该位置开始消费。
1、NameServer 名称服务器用于保存Broker消息服务器的相关元信息,Producer 和 Consumer 使用的话都需要查找Broker 消息信息。
2、在启动时,Broker消息服务器会向NameServer 注册自己的信息,并与NameServer 服务器保持长连接,NameServer会每隔30S会进行检查Broker是否存活。若是宕机,就会从路由注册表中将其移出。
3、生产者Producer、消费者Consumer每隔30s从Name server获取所有topic队列的最新情况。于此同时Broker每隔10s中扫描所有存活的连接,若某个连接在2分钟内没有收到心跳数据,则关闭连接。
4、若是Producer得到master宕机通知后,在此期间内(30s)发往Broker的所有消息都会失败。
5、若是消费者Consumer得到master宕机通知后,转向slave消费,slave不能保证master的消息100%都同步过来了,因此会有少量的消息丢失。但是一旦master恢复,未同步过去的消息会被最终消费掉。
生产者在发送某个主题的消息之前,先从 NamerServer 获取 Broker 服务器地址列表(有可能是集群),然后根据负载均衡算 法从列表中选择一台Broker 进行消息发送。
NameServer 与每台消息服务器 Broker 服务器保持长连接,并间隔 30S 检测 Broker 是否存活,如果检测到Broker 宕机(使用心跳机制, 如果检测超120S),则从路由注册表中将其移除。
消费者在订阅某个主题的消息之前从 NamerServer 获取对应的 Broker 消息服务器地址列表(有可能是集群),但是消费者选择从 Broker 中 订阅消息,订阅规则由 Broker 配置决定。
消费消息失败,可以进行重试。
消息(Message)就是要传输的信息。一条消息必须有一个主题(Topic),主题可以看做是你的信件要邮寄的地址。一条消息也可以拥有一个可选的标签(Tag)和额外的键值对,它们可以用于设置一个业务 key 并在 Broker 上查找此消息以便在开发期间查找问题。
主题(Topic)可以看做消息的规类,它是消息的第一级类型。比如一个电商系统可以分为:交易消息、物流消息等,一条消息必须有一个 Topic 。
Topic 与生产者和消费者的关系非常松散,一个 Topic可以有0个、1个、多个生产者向其发送消息,一个生产者也可以同时向不同的 Topic 发送消息。一个Topic 也可以被 0个、1个、多个消费者订阅。
标签(Tag)可以看作子主题,它是消息的第二级类型,用于为用户提供额外的灵活性。使用标签,同一业务模块不同目的的消息就可以用相同 Topic 而不同的 Tag 来标识。
比如交易消息又可以分为:交易创建消息、交易完成消息等,一条消息可以没有 Tag 。标签有助于保持您的代码干净和连贯,并且还可以为 RocketMQ 提供的查询系统提供帮助。
简单来说TOPIC可以看作衣服,而TAG可以看作衣服下的【短袖】、【外套】、【卫衣】等。
主题Topic被划分为一个或多个子主题,即消息队列。一个 Topic 下可以设置多个消息队列,RocketMQ 会轮询该 Topic 下的所有队列将消息发出去。 另外Topic只是一个逻辑上的概念,消息队列才是真正的实体。
消息消费模式有两种:集群消费(Clustering)和广播消费(Broadcasting)
默认情况下就是集群消费,该模式一条消息只能被某一消费者组中的某一台机器消费,如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。
而广播消费消息会发给消费者组中的每一个消费者进行消费。
消息顺序(Message Order)有两种:顺序消费(Orderly)和并行消费(Concurrently)。
顺序消费表示消息消费的顺序和生产者为每个消息队列发送信息时候的顺序一致,所以如果正在处理全局顺序是强制性的场景,需要确保使用的主题只有一个消息队列。
顺序消息(FIFO:First Input First Output)是一种严格按照顺序进行发布和消费的消息类型。要求消息的发布和消息消费都按照顺序进行,RocketMQ可以严格保证消息有序
RocketMQ可以严格的保证消息有序。但这个顺序,不是全局顺序,只是分区(queue)顺序。要全局顺序只能一个分区,但是同一条queue里面,RocketMQ的确是能保证FIFO的。
并行消费不再保证消息顺序,消费的最大并行数量受每个消费者客户端指定的线程池限制。
RocketMQ 的设计基于主题的发布与订阅模式,其核心功能包括消息发送、消息存储(Broker)、消息消费,整体设计追求简单与性能第一,主要体现在以下三个方面:
RocketMQ摒弃了业界常用的zookeeper作为注册中心,而是使用自研的NameServer来实现元数据的管理。因为Topic路由信息无须在集群间保持强一致性,追求最终一致性,并且能容忍分钟级的不一致,所以RocketMQ的NameServer集群间互不通信,极大降低了设计的复杂度,降低了对网络的要求,提升性能。
RocketMQ追求消息发送的高吞吐量,RocketMQ消息存储文件设计成文件组的概念,组内单个文件大小固定,方便引入内存映射机制。
所有主题的消息存储基于顺序写,提升写性能,同时为了兼顾消息消费与消息查找,引入了消息消费队列文件与索引文件。
如何保证消息一定能被消息消费者消费,并且保证只消费一次。 RocketMQ的设计者给出的解决办法是不解决这个难题,而是退而求其次,只保证消息被消费者消费,但设计上允许消息被重复消费,所以就需要
RocketMQ 架构图中展示了四个集群:
提供轻量级的服务发现及路由,每个 NameServer 记录完整的路由信息,提供相应的读写服务,支持快速存储扩展。
NameServer是一个功能齐全的服务器,主要包含两个功能:
Broker 管理,接收来自 Broker 集群的注册请求,提供心跳机制检测 Broker 是否存活。
路由管理,每个 NameServer 持有全部有关 Broker 集群和客户端请求队列的路由信息。
**通过提供轻量级的 Topic 和Queue 机制处理消息存储。同时支持推(Push)和拉(Pull)两种模型,包含容错机制。提供强大的峰值填充和以原始时间顺序累积数千亿条消息的能力。**此外还提供灾难恢复,丰富的指标统计数据和警报机制,这些都是传统的消息系统缺乏的。
远程处理模块,Broker 入口,处理来自客户端的请求
客户端管理,管理客户端(包括消息生产者和消费者),维护消费者的主题订阅
存储服务,提供在物理硬盘上存储和查询消息的简单 API
HA 服务,提供主从 Broker 间数据同步
索引服务,通过指定键为消息建立索引并提供快速消息查询
消息生产者支持分布式部署,分布式生产者通过多种负载均衡模式向 Broker 集群发送消息。
消息消费者也支持 Push 和 Pull 模型的分布式部署,还支持集群消费和消息广播。提供了实时的消息订阅机制,可以满足大多数消费者的需求。
情况1,4的RocketMQ在同步刷盘机制下可以确保不丢失消息,在异步刷盘模式下,会丢失少量消息。
情况5,6属于单点故障,一旦发生,该节点上的消息全部丢失,如果开启了异步复制机制,RoketMQ能保证只丢失少量消息,RocketMQ在后续版本中将引入双写机制,以满足消息可靠性要求极高的场合。
RocketMQ在消息不发生消息堆积时,以长轮询模式实现准实时的消息推送模式。
一种严格按照顺序进行发布和消费的消息类型。要求消息的发布和消息消费都按照顺序进行,RocketMQ可以严格保证消息有序。
但是要全局顺序只能一个分区,但是同一条queue里面,RocketMQ的确是能保证FIFO的。
在消息消费时,消息消费者可以对同一主题下的消息按照规则只消费自己感兴趣的消息。RocketMQ消息过滤支持在服务端与消费端的消息过滤机制。
消息过滤方式:消息消费端、消息消费者自定义
消息中间件的一个核心实现是消息的存储,对消息存储一般有如下两个维度的考量:消息堆积能力和消息存储性能。
RocketMQ追求消息发送的高吞吐量,RocketMQ消息存储文件设计成文件组的概念,组内单个文件大小固定,方便引入内存映射机制。RocketMQ消息存储文件并不是永久存储在消息服务器端,而是提供了过期机制,默认保留3天
所有主题的消息存储基于顺序写,提升写性能,同时为了兼顾消息消费与消息查找,引入了消息消费队列文件与索引文件。为了避免消息无限在消息存储服务器中累积,引入了消息文件过期机制与文件存储空间报警机制。
消息中间件的主要功能是异步解耦,必须具备应对前端的数据洪峰,提高后端系统的可用性,必然要求消息中间件具备一定的消息堆积能力。RocketMQ消息存储使用磁盘文件(内存映射机制),并且在物理布局上为多个大小相等的文件组成逻辑文件组,可以无限循环使用。
指消息消费端已经消费成功的消息,由于业务要求需要重新消费消息。RocketMQ支持按时间回溯消息,时间维度可精确到毫秒,可以向前或向后回溯。
定时消息是指消息发送到Broker后,不能被消息消费端立即消费,要到特定的时间点或者等待特定的时间后才能被消费。如果要支持任意精度的定时消息消费,必须在消息服务端对消息进行排序,势必带来很大的性能损耗,故RocketMQ不支持任意进度的定时消息,而只支持特定延迟级别。
转自:https://blog.csdn.net/lx9876lx/article/details/129246835
1.rocketmq-namesrv: 命名服务,更新和路由发现 broker服务。
NameServer 要作用是为消息生产者、消息消费者提供关于主题 Topic 的路由信息,NameServer除了要存储路由的基础信息,还要能够管理 Broker节点,包括路由注册、路由删除等功能
2.rocketmq-broker: mq的核心。它能接收producer和consumer的请求,并调用store层服务对消息进行处理。HA服务的基本单元,支持同步双写,异步双写等模式。
3.rocketmq-store: 存储层实现,同时包括了索引服务,高可用HA服务实现。
4.rocketmq-remoting: 基于netty的底层通信实现,所有服务间的交互都基于此模块。
5.rocketmq-common: 一些模块间通用的功能类,比如一些配置文件、常量。
6.rocketmq-client: java版本的mq客户端实现
7.rocketmq-filter: 消息过滤服务,相当于在broker和consumer中间加入了一个filter代理。
8.rocketmq-srvutil: 解析命令行的工具类ServerUtil。
9.rocketmq-tools: mq集群管理工具,提供了消息查询等功能
1)启动流程
RocketMQ服务端由两部分组成NameServer和Broker,NameServer是服务的注册中心,Broker会把自己的地址注册到NameServer,生产者和消费者启动的时候会先从NameServer获取Broker的地址,再去从Broker发送和接受消息。
2)消息生产流程
Producer将消息写入到RocketMQ集群中Broker中具体的Queue。
3)消息消费流程
Comsumer从RocketMQ集群中拉取对应的消息并进行消费确认。
从源码的启动可知,NameServer单独启动。入口类:NamesrvController。期间会分为三个环节:
**1)加载KV配置。**解析相关的配置参数
2)构建NRS通讯接收路由、心跳信息。
**3)定时任务剔除超时Broker。**NameServer会每隔10s扫描brokerLiveTable状态表,如果BrokerLive的lastUpdateTimestamp的时间戳距当前时间超过120s,则认为Broker失效,移除该Broker,关闭与Broker连接,同时更新topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable。而在这路由剔除机制的过程中产生的,若是Broker实际上已经宕机,那么可以通过故障规避策略及重试机制来解决。
这个路由剔除机制是采用读写锁机制。心跳检测机制,除了对外进行发送心跳包,也有其他服务来获取当前服务状态,要提高并发,采用了ReentrantReadWriteLock(针对读多写少的场景)。在写线程访问时,所有的读线程和其他写线程均被阻塞。
NameServer存储以下信息:
**topicQueueTable:**Topic消息队列路由信息,消息发送时根据路由表进行负载均衡
**brokerAddrTable:**Broker基础信息,包括brokerName、所属集群名称、主备Broker地址
**clusterAddrTable:**Broker集群信息,存储集群中所有Broker名称
**brokerLiveTable:**Broker状态信息,NameServer每次收到心跳包是会替换该信息
**filterServerTable:**Broker上的FilterServer列表,用于类模式消息过滤。
NameServer的实现基于内存,NameServer并不会持久化路由信息,持久化的重任是交给Broker来完成。这样设计可以提高NameServer的处理能力。
Broker能接收producer和consumer的请求,并调用store层服务对消息进行处理。HA服务的基本单元,支持同步双写,异步双写等模式。
Kafka 中文件的布局是以 Topic/partition ,每一个分区一个物理文件夹,在分区文件级别实现文件顺序写,如果一个Kafka集群中拥有成百上千个主题,每一个主题拥有上百个分区,消息在高并发写入时,其IO操作就会显得零散(消息分散的落盘策略会导致磁盘IO竞争激烈成为瓶颈),其操作相当于随机IO,即 Kafka 在消息写入时的IO性能会随着 topic 、分区数量的增长,其写入性能会先上升,然后下降。
**而 RocketMQ在消息写入时追求极致的顺序写,所有的消息不分主题一律顺序写入 commitlog 文件,并不会随着 topic 和 分区数量的增加而影响其顺序性。另外RocketMQ另外维护着topic与标签以及队列的逻辑信息,从而实现快速定位消息。**在消息发送端,消费端共存的场景下,随着Topic数的增加Kafka吞吐量会急剧下降,而RocketMQ则表现稳定。因此Kafka适合Topic和消费端都比较少的业务场景,而RocketMQ更适合多Topic,多消费端的业务场景。
**RocketMQ 主要存储的文件包括 Commitlog 文件、 ConsumeQueue 文件、 IndexFile。**RocketMQ 将所有主题的消息存储在同一文件,确保消息发送时顺序写文件,尽最大的能力确保消息发送的高性能与高吞吐量。但由于一般的消息中间件是基于消息主题的订阅机制,这样便给按照消息主题检索消息带来了极大的不便。所以引入了索引文件、以及消息队列文件。
每个消息主题包含多个消息消费队列,每个消息队列有一个消息文件。RocketMQ 还引入了IndexFile 索引文件,其主要设计理念就是为了加速消息的检索性能,可以根据消息的属性快速从 Commitlog 文件中检索消息。
在写入 CommitLog 文件时,RocketMQ 采用了一种叫做 MappedFile 的技术。MappedFile 是 Java NIO 中的一个类,可以将一个文件或文件片段映射到内存中,从而可以直接对内存中的数据进行读写操作。
除了 CommitLog 文件之外,RocketMQ 还维护了一个 IndexFile 文件用于快速查询消息。IndexFile 中记录了消息索引的偏移量以及消息的关键字,通过 IndexFile 可以快速定位到指定消息的位置。
**1 ) CommitLog :**消息存储文件,所有消息主题的消息都存储在 CommitLog 文件中
**2 ) ConsumeQueue :**消息消费队列,消息到达 CommitLog 文件后,将异步转发到消息消费队列,供消息消费者消费
**3 ) IndexFile :**消息索引文件,主要存储消息 Key与Offset 的对应关系
2-1)CommitLog
**CommitLog 以物理文件的方式存放,每台 Broker 上的 CommitLog 被本机器所有 ConsumeQueue 共享,在CommitLog 中,一个消息的存储长度是不固定的, RocketMQ采取一些机制,尽量向CommitLog 中顺序写 ,但是随机读。**commitlog 文件默认大小为lG ,可通过在 broker 置文件中设置 mapedFileSizeCommitLog 属性来改变默认大小。commitlog每条消息的前面4个字节存储该条消息的总长度。但是一个消息的存储长度是不固定的。
2-2)ConsumeQueue
**ConsumeQueue 是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。**每个Topic下的每个 Message Queue 都有一个对应的 ConsumeQueue 文件。**ConsumeQueue中存储的是消息条目,为了加速 ConsumeQueue 消息条目的检索速度与节省磁盘空间,每一个 Consumequeue条目不会存储消息的全量信息。**ConsumeQueue 即为Commitlog 文件的索引文件, 其构建机制是当消息到达 Commitlog 文件后,由专门的线程产生消息转发任务,从而构建消息消费队列文件(ConsumeQueue )与下文提到的索引文件。
存储机制这样设计有以下几个好处:
1 ) CommitLog 顺序写 ,可以大大提高写入效率。(实际上,磁盘有时候会比你想象的快很多,有时候也比你想象的慢很多,关键在如何使用,使用得当,磁盘的速度完全可以匹配上网络的数据传输速度。目前的高性能磁盘,顺序写速度可以达到600MB/s ,超过了一般网卡的传输速度,这是磁盘比想象的快的地方 但是磁盘随机写的速度只有大概100KB/s,和顺序写的性能相差 6000 倍!)
2 )虽然是随机读,但是利用操作系统的 pagecache 机制,可以批量地从磁盘读取,作为 cache 存到内存中,加速后续的读取速度。同时因为ConsumeQueue中每一条消息的索引是固定长度,所以也能够确保消息消费时的时间复杂度保持在O(1)。
3 )为了保证完全的顺序写,需要 ConsumeQueue 这个中间结构 ,因为ConsumeQueue 里只存偏移量信息,所以尺寸是有限的,在实际情况中,大部分的 ConsumeQueue 能够被全部读入内存,所以这个中间结构的操作速度很快,可以认为是内存读取的速度。此外为了保证 CommitLog和ConsumeQueue 的一致性, CommitLog 里存储了 Consume Queues 、Message Key、 Tag 等所有信息,即使 ConsumeQueue 丢失,也可以通过 commitLog 完全恢复出来。
2-3)IndexFile
**index 存的是索引文件,这个文件用来加快消息查询的速度。**消息消费队列 RocketMQ 专门为消息订阅构建的索引文件 ,提高根据主题与消息检索消息的速度 ,使用Hash索引机制,具体是Hash槽与Hash冲突的链表结构。
2-4)Config
config 文件夹中 存储着Topic和Consumer等相关信息。主题和消费者群组相关的信息就存在在此。
topics.json : topic 配置属性
subscriptionGroup.json :消息消费组配置信息。
delayOffset.json :延时消息队列拉取进度。
consumerOffset.json :集群消费模式消息消进度。
consumerFilter.json :主题消息过滤信息。
3-1)NRS与NRC的功能号设计
RocketMQ的通讯使用的是Netty,作为客户端核心类有两种:RemotingCommand与NettyRemotingClient。
RemotingCommand主要处理消息的组装:包括消息头、消息序列化与反序列化。
NettyRemotingClient主要处理消息的发送:包括同步、异步、单向、注册等操作。
3-2)同步双写数倍性能提升的CompletableFuture
在RocketMQ4.7.0之后,RocketMQ大量使用Java中的异步编程接口CompletableFuture。尤其是在Broker端进行消息接收处理时,合理使用CompletableFuture对同步双写进行性能优化,使得对消息的处理流式化,大大提高了Broker的接收消息的处理能力。
在Master-Slave主从架构下,Master 节点与 Slave 节点之间数据同步/复制的方式有同步双写和异步复制两种模式。同步双写是指Master将消息成功落盘后,需要等待Slave节点复制成功(如果有多个Slave,成功复制一个就可以)后,再告诉客户端消息发送成功。
3-3)Commitlog写入时使用可重入锁还是自旋锁?
RocketMQ在写入消息到CommitLog中时,使用了锁机制,即同一时刻只有一个线程可以写CommitLog文件。CommitLog 中使用了两种锁,一个是自旋锁,另一个是重入锁。
RocketMQ 官方文档优化建议:异步刷盘建议使用自旋锁,同步刷盘建议使用重入锁,调整Broker配置项useReentrantLockWhenPutMessage,默认为false;
同步刷盘时,锁竞争激烈,会有较多的线程处于等待阻塞等待锁的状态,如果采用自旋锁会浪费很多的CPU时间,所以“同步刷盘建议使用重入锁”。
异步刷盘是间隔一定的时间刷一次盘,锁竞争不激烈,不会存在大量阻塞等待锁的线程,偶尔锁等待就自旋等待一下很短的时间,不要进行上下文切换了,所以采用自旋锁更合适。
3-4)零拷贝技术之MMAP提升文件读写性能
RocketMQ底层对commitLog、consumeQueue之类的磁盘文件的读写操作都采用了mmap技术。具体到代码里面就是利用JDK里面NIO的MapperByteBuffer的map()函数,来先将磁盘文件(CommitLog文件、consumeQueue文件)映射到内存里来。
MMAP内存映射是在硬盘上文件的位置和应用程序缓冲区(application buffers)进行映射(建立一种一一对应关系),由于mmap()将文件直接映射到用户空间,所以实际文件读取时根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了一次数据拷贝,不再有文件内容从硬盘拷贝到内核空间的一个缓冲区。
**MMAP属于零拷贝技术的一种。**零拷贝(英语: Zero-copy) 技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
mmap技术在地址映射的过程中对文件的大小是有限制的,在1.5G~2G之间,所以,RocketMQ就会把单个的commitLog文件大小控制在1GB,consumeQueue文件大小控制在5.72MB,这样就在读写的时候,方便的进行内存映射了。
3-5)堆外内存机制
一般情况下RocketMQ是通过MMAP内存映射,生产时消息写入内存映射文件,然后消费的时候再读。但是RocketMQ还提供了一种机制。堆外内存机制:TransientStorePool,短暂的存储池(堆外内存)。
**如果开启了堆外内存缓冲区的话,集群模式必须是异步刷盘的模式同时该Broker必须为主节点。RocketMQ单独创建一个ByteBuffer内存缓存池,用来临时存储数据,数据先写入该内存映射中,然后由commit线程定时将数据从该内存复制到与目标物理文件对应的内存映射中。**RocketMQ引入该机制主要的原因是提供一种内存锁定,将当前堆外内存一直锁定在内存中,避免被进程将内存交换到磁盘。同时因为是堆外内存,这么设计可以避免频繁的GC。
默认方式,Mmap+PageCache的方式,读写消息都走的是pageCache(MappedByteBuffer类),这样子读写都在pagecache里面不可避免会有锁的问题,在并发的读写操作情况下,会出现缺页中断降低,内存加锁,污染页的回写(脏页面)。
而如果采用堆外缓冲区,DirectByteBuffer(堆外内存)+PageCache的两层架构方式,这样子可以实现读写消息分离,写入消息时候写到的是DirectByteBuffer——堆外内存中,读消息走的是PageCache(MappedByteBuffer类),带来的好处就是,避免了内存操作的很多容易堵的地方,降低了时延,比如说缺页中断降低,内存加锁,污染页的回写。
RocketMQ中消息发送者、消息消费者都属于”客户端“。每一个客户端就是一个MQClientInstance,每一个ClientConfig对应一个实例。故不同的生产者、消费端,如果引用同一个客户端配置(ClientConfig),则它们共享一个MQClientInstance实例。
消息发送默认选择队列策略为轮询算法。而故障延迟选择队列则是优先考虑消息的发送时长短的队列。
每次向Broker成功或者异常的发送,RocketMQ都会计算出该Borker的可用时间(发送结束时间-发送开始时间,失败的按照30S计算),并且保存,方便下次发送时做筛选。除了记录Broker的发送消息时长之外,还要计算一个Broker的不可用时长。
发送消息,消息是不定长,首先所有信息先放入 Commitlog中,每一条消息放入Commitlog的时候都需要上锁,确保顺序的写入。当Commitlog写成功了之后。数据通过ReputMessageService类定时同步到ConsunmeQueue中,写入Consume Queue的内容是定长的,固定是20个Bytes(offset 8个、size 4个、Hashcode of Tag 8个)。
查找消息的时候,可以直按根据队列的消息序号,计算出索引的全局位置(比如序号2,就知道偏移量是20),然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。这两次查找是差不多的:第一次在通过序号在consumer Queue中获取数据的时间复杂度是O(1),第二次查找commitlog文件的时间复杂度也是O(1),所以消费时查找数据的时间复杂度也是O(1)。
1)集群消费
2)广播消费
1)获取topic配置信息
2)获取Group的ConsumerList
3)获取Queue的消费Offset
4)拉取Queue的消息
5)更新Queue的消费Offset
6)注销Consumer
顺序消费的流程和并发消费流程整体差不多,唯一的多的就是使用锁机制来确保一个队列同时只能被一个消费者消费,从而确保消费的顺序性。所以有时候会感觉存在消费卡死情况,就是因为加锁了。
存储层实现,同时包括了索引服务,高可用HA服务实现。
基于netty的底层通信实现,所有服务间的交互都基于此模块。也区分服务端和客户端
RocketMQ 中有两个重要的缓存设计:PageCache 和 ConsumeQueue。
PageCache 是 RocketMQ 的物理内存缓存,主要用于加速消息的读写操作。RocketMQ 使用内存映射技术将磁盘上的 CommitLog 文件映射到内存中,这样就可以实现快速的消息读写操作。
PageCache 中的内存空间是由 JVM 进程直接申请的,因此需要考虑内存的使用效率和回收效率。默认情况下,RocketMQ 将 PageCache 的大小设为物理内存的 40%。
ConsumeQueue 可以看作是 RocketMQ 的逻辑内存缓存,主要用于消费者快速拉取消息和跟踪消息消费进度。每个 Topic 都有自己的 ConsumeQueue,用来存储该 Topic 的所有消息。
ConsumeQueue 中的每个消息都对应着一个索引项,记录了该消息在 CommitLog 文件中的偏移量和消息长度信息。当消费者向 Broker 请求消息时,Broker 会从对应的 ConsumeQueue 中读取消息索引信息,并根据索引信息去 CommitLog 中查询实际的消息内容。
RocketMQ 采用了日志追加的方式进行消息存储。当 Broker 崩溃或重启时,可能会出现数据丢失或消息重复等情况。为了解决这些问题,RocketMQ 实现了多种崩溃恢复机制。
RocketMQ 维护了每个消费者所消费的消息队列偏移量。当消费者重新启动时,可以通过之前保存的偏移量继续消费未消费的消息。
Checkpoint 文件用于记录 CommitLog 中最后一条消息的偏移量。当 Broker 发生异常情况导致崩溃时,Broker 再次启动时可以从 Checkpoint 文件中读取偏移量,从而定位到最近一次的消息读取位置。
RocketMQ 中的 CommitLog 文件是顺序写入的,因此具有很好的一致性和可靠性。Broker 在写入每个消息之前都会计算消息的 CRC 校验码,用于检测文件数据的完整性和正确性。
只有在消息真正持久化至磁盘后RocketMQ的Broker端才会真正返回给Producer端一个成功的ACK响应。同步刷盘对MQ消息可靠性来说是一种不错的保障,但是性能上会有较大影响,一般适用于金融业务应用该模式较多。
能够充分利用OS的PageCache的优势,只要消息写入PageCache即可将成功的ACK返回给Producer端。消息刷盘采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量。
后续补充…
转自
**RocketMQ在分布式事务中引入了半事务及事务回查机制。通过事务回查,如果本地事务执行成功,则提交commit_message,消费端即可消费消息。如果有一些比较耗时的操作导致,不能在这个步骤确认的话,可以提交UNKNOW,交给定时的任务回查来处理。**如果担心生产者发生故障导致分布式事务的问题话,定时事务回查是可以在生产者群组中做的。
半事务:
发一个消息到rocketmq,但该消息只储存在commitlog中,但consumeQueue中不可见,也就是消费端(订阅端)无法看到此消息。事务回查:
RocketMq会定时遍历commitlog中的半事务消息,这个事务回查机制就可以站在 RocketMQ的角度参与消息发送者的事务中。
RocketMQ采用JAVA语言编写,项目架构采用的是微服务架构,对各自服务领域的能力做了明确的划分。由于支持消息持久化,为了性能方面的考虑,采用的方式与MySQL的设计类同。但是也不完全一样,都有索引文件,同时设计也增加了堆外缓冲区,用于快速访问到想要的消息,减少磁盘IO开销。同时也增加了刷盘策略,有同步和异步两种选择,可自行选配。当然本身设计的目标是为了肖峰并且消费消息,所以可以设置对应的服务集群,也有添加双方心跳检测机制,避免服务宕机导致的过多消息没有及时处理(可能会存在30s消息没能及时处理,但是有消息重试机制)。