使用建议
- 不建议同一个消费者组订阅多个Topic,因为 RocketMQ 是按照 Topic 维度进行 rebalanace: https://cloud.tencent.com/developer/article/1554950
- 要注意使用自定义的负载均衡算法选择队列后,RocketMQ 的重试机制将失效、
- 生产环境中,autoCreateTopicEnable 不能设置为 true
消费方式
在 RocketMQ 里消费方式虽有 PUSH(主动推送给消费者) 与 PULL(消费者主动取 broker 拉取) 两种,但实现机制实为 PULL 模式,PUSH 模式是一种伪推送,是对 PULL 模式的封装,每拉取一批消息后,提交到消费端的线程池(异步),然后马上向 Broker 拉取消息,即实现类似"推"的效果。
但是为了能做到实时收消息,RocketMQ 使用长轮询方式,可以保证消息实时性与 Push 方式一致
消费者线程池默认拒绝策略是 AbortPolicy(抛出异常)
区别对比
- push:实时性高,但增加服务端负载,消费端能力不同,如果 push 速度过快,消费端会出现问题
- pull:可控性好,但时间间隔不好设置,间隔太长,消息不能及时处理,间隔太短,空请求会多
一、RocketMQ 支持 3 种消息发送方式 :
- 同步消息(sync message )
producer向 broker 发送消息,执行 API 时同步等待, 直到broker 服务器返回发送结果 。
- 异步消息(async message)
producer 向 broker 发送消息时指定消息发送成功及发送异常的回调方法,调用 API 后立即返回,producer发送消息线程不阻塞 ,消息发送成功或失败的回调任务在一个新的线程中执行 。
- 单向消息(oneway message)
producer向 broker 发送消息,执行 API 时直接返回,不等待broker 服务器的结果 。
消息存储
消息存储和下面三个文件关系非常紧密:
- 数据文件 commitlog
消息主体以及元数据的存储主体 ; - 消费文件 consumequeue
消息消费队列,引入的目的主要是提高消息消费的性能。消费队列文件的格式为"./consumequeue/Topic名字/queue id/具体消费队列文件"。每个消费队列其实是commitlog的一个索引,提供给消费者做拉取消息、更新位点使用。 - 索引文件 indexfile
索引文件,提供了一种可以通过 key 或时间区间来查询消息,全部的文件都是按照消息 key 创建的 hash 索引,文件名是用创建时的时间戳命名。 - config
保存了当前 Broker 中全部的 topic、订阅关系和消费进度,这些数据 Broker 会定时从内存持久化到磁盘,以便宕机后恢复。
- abort
Broker 是否异常关闭的标志,正常关闭时该文金啊会被删除,当 Broker 重新启动时,根据是否异常宕机决定是否要重新构建 index 索引等操作
- checkpoint
Broker 最近一次正常运行时的状态,比如最后一次正常刷盘的时间、最后一次正确索引的时间等
RocketMQ 采用的是混合型的存储结构,Broker 单个实例下所有的队列公用一个数据文件来存储(commitlog)。
生产者发送消息到 Broker 端,然后 Broker 端使用同步或者异步的方式对消息刷盘持久化,保存至 commitlog 文件中,只要消息保存到 commitlog 中,那么消息生产者发送的消息就不会丢失。
Brocker 端的后台服务线程会不停的分发请求并构建 consumequeue(消费文件)和indexfile(索引文件)。
数据文件 commitlog
所有消息都会顺序写入数据文件,当文件写满,会写入下一个文件。
单个文件大小默认 1G , 文件名长度为 20 位,左边补零,剩余为起始偏移量,比如 00000000000000000000 代表了第一个文件,起始偏移量为 0 ,文件大小为 1 G = 1073741824。当第一个文件写满了,第二个文件为 00000000001073741824,起始偏移量为 1073741824,以此类推。
消费文件 consumequeue
- 消费文件按 topic 存储,每个 topic 一个文件夹,文件夹下有不同的队列,队列轻量化,单个队列数据非常少
- consumequeue 只存储消息在 commitlog 的位置信息,并且串行方式刷盘, 对磁盘访问串行化,避免磁盘竞争,不会导致 IOWAIT 增高,文件的名称也是以偏移量来命名的,可以通过消息的逻辑偏移量定位消息位于哪一个文件里
- ConsumeQueue 中存储的每条数据大小是固定的,总共 20 个字节,数据结构如下图
- 每个队列目录下,存储 consumequeue 文件,每个 consumequeue 文件也是顺序写入,但是 读取却成了随机读
- 消费者从 brocker 获取订阅消息数据时,不用遍历整个 commitlog 文件,只需要根据逻辑偏移量从 consumequeue 文件查询偏移量了,最后定位到 commitlog 获取到真正的消息数据,增加了读取开销
如何克服以上缺点(红色标识)
-
随机读尽可能让读命中 PageCache,减少 IO 读操作,所以内存越大越好。如果系统中堆积的消息过多,读数据要访问磁盘并不会由于随机读取导致性能急剧下降
- 访问 PageCache 时,即使只访问 1K 的数据,系统也会提前预读出更多数据,在下次读取时,就可能命中内存
- 随机访问 CommitLog 磁盘数据,系统 IO 调度算法设置为 NOOP 算法,会在一定程度上将完全随机的读变为顺序跳跃方式,而顺序跳跃方式读较完全的随机读性能会提高 5 倍以上
-
由于 ConsumeQueue 存储数据量极少,而且是顺序读,在 PageCache 预读作用下,ConsumeQueue 的读性能几乎与内存一致,所以可认为 ConsumeQueue 完全不会阻碍读性能
-
RocketMQ持久化先写入系统 pageCache,然后刷盘,可以保证内存与磁盘都有一份数据,访问时,直接从内存读取
索引文件 indexfile
消息在业务层面的唯一标识码要设置到 keys 字段,方便定位消息丢失问题,服务器会为每个消息创建索引(hash 索引),应用可以通过 topic、key 来查询这条消息内容,以及消息被谁消费。
- 索引文件名 fileName 是以创建时的时间戳命名的,固定的单个 IndexFile 文件大小约为 400 M
消息构成
索引机制
- 基于 MessageId 提取消息
messageId(服务端的) 是用 broker + offset 生成的,所以很容易找到对应的 commitLog 读取消息
-
基于 tag 查询
- 消费者对想要提取的 tag = 20220101 进行 hash,获取 hash 值 99
- 在 ConsumeQueue 文件中查找对应 hash(tag) = 99 的 offset 数据
- 根据物理位置 offset 到对应的 commitlog 文件提取消息,因为存在 hash 碰撞,所以对命中数据再进行字符串匹配方式筛选 "20220101" 的消息
- 提取消息,封装成 Message 对象返回
- 每个 IndexFile 文件包含文件头、hash 槽位、索引数据,每个文件的 hash 槽位个数、索引数据个数都是固定的,hash 槽位可以通过 Broker 启动参数 maxHashSlotNum 进行配置,默认 500W ,索引数据可以通过 maxIndexNum 进行配置,默认值是 500 ✖ 4 = 2000W,一个 IndexFile 约为 400MB
消息处理原理
消息过滤
- 在 Broker 端进行 MessageTag 比对,如果存储的 MessageTag 与订阅的 MessageTag 不符合,则跳过,符合则传输给 Consumer。注意:MessageTag 是字符串形式,ConsumeQueue 中存储的是对应的 hashCode,对比时对比的也是 hashCode
- Consumer 收到过滤后的消息后,同样也要执行在 Broker 端的操作,但是对比的是真是的字符串,而不是 HashCode
为什么要这样做?
- 过滤过程中不会访问 CommitLog 数据并且 hashCode 是数字比较,可以保证堆积情况下也能高效过滤
- 及时存在 hash 冲突,也可以在 Consumer 端继续宁修正,保证万无一失
RocketMQ一个消费组内订阅同一个主题不同的TAG为什么会丢消息
消息消费
- 如果从commitlog文件查找消息时,发现消息堆积太多,默认超过物理内存的40%后,会建议从从服务器读取
- 如果当前服务器的角色为从服务器:并且slaveReadEnable=true,则下次拉取切换为从主拉取。
- 如果slaveReadEnable=true(从允许读),并且建议从从服务器读取,则从消息消费组建议当消息消费缓慢时建议的拉取brokerId,由订阅组配置属性whichBrokerWhenConsumeSlowly决定;如果消息消费速度正常,则使用订阅组建议的brokerId拉取消息进行消费,默认为主服务器。如果不允许从可读,则固定使用从主拉取。
消息同步
- 消息消费进度的同步是单向的,从服务器开启一个定时任务,定时从主服务器同步消费进度
- 无论消息消费者是从服务器拉取的消息还是从主服务器拉取的消息,在向 Broker 反馈消息时,优先向主服务器汇报
- 消息消费者向从服务器拉取消息时 ,如果消息消费者内存中存在消息消费进度时,主服务器会尝试更新消息消费进度,这样一来,消费端与主服务器只挂了器中一个,并不会导致消息重新被消费
刷盘策略
同步刷盘与异步刷盘的唯一区别是异步刷盘写完 PAGECACHE 直接返回,而同步刷盘需要等待刷盘完成才返回,
同步刷盘流程如下:
- 写入 PageCache 后,线程等待,通知刷盘线程刷盘。
- 刷盘线程刷盘后,唤醒前端等待线程,可能是一批线程。
- 前端等待线程向用户返回成功。
问题
和 kafka 的区别
- kafka 使用 Zookeeper 作为注册中心
- kafka 针对消费者和生产者使用了同一份存储结构,每个 partition 对应一个文件,收到消息后 kafka 会把数据插入到文件末尾
- RocketMQ 针对消费者和生产者使用了不同的存储结构,消费者对应 CommitLog,消费者对应 ConsumeQueue,RocketMQ 是把所有 topic 的所有 queue 消息存储在一个文件(commitlog)里,然后分发给 ConsumeQueue
- kafka 在大数据量传输、IO 并行写入能力较强,但大量 topic 下由于并行度、CPU 切换等问题,导致服务器性能下降
- RocketMQ 是按照 Topic 维度进行 rebalanace,而 kafka 是将所有 topic 放在一起
- Consumer 消费消息过程,使用了零拷贝,RocketMQ 选择了第一种方式,mmap+write 方式,因为有小块数据传输的需求,效果会比 sendfile 更好,而 RocketMQ 是针对的业务场景, kafka 针对的是大数据场景。(出自《RocketMQ 原理简介》v3.1.1,Alibaba 淘宝消息中间件项目组)零拷贝包含以下两种方式
- 使用 mmap + write 方式
- 优点:即使频繁调用,使用小块文件传输,效率也很高
- 缺点:不能很好的利用 DMA 方式,会比 sendfile 多消耗 CPU,内存安全性控制复杂,需要避免 JVM Crash 问题。
- 使用 sendfile 方式
- 优点:可以利用 DMA 方式,消耗 CPU 较少,大块文件传输效率高,无内存安全新问题。
- 缺点:小块文件效率低于 mmap 方式,只能是 BIO 方式传输,不能使用 NIO。
- 使用 mmap + write 方式
为什么 RocketMQ 不使用 Zookeeper
- topic路由信息无须在集群之间保持强一致,而是追求最终一致性,而Zookeeper 满足 CP 原则,NameServer 集群之间互不通信,性能相较于 Zookeeper 有极大提升
- kafka 使用 Zookeeper 是因为 kafka 的 Master/Slave 是选出来的,而 RocketMq 基于 raft 协议的 DLedger 自动主从切换,完全可以不依赖 Zookeeper
- 引入 Zookeeper 增加运维复杂性
RocketMQ 参数
www.cnblogs.com/zhyg/p/1025...
useEpollNativeSelector:是否启用Epoll I/O模型,Linux 环境下建议开启