设计理念
-
nameserver 元数据管理,不追求强一致性,而是最终一致性
-
高效的IO存储,追求消息发送的高吞吐量 – 一般高吞吐量离不开异步(线程池 & 异步线程)
-
消息存储文件设计成组的概念 – 方便攒批 批量进行写入/读取;引入mmap技术 & 所有主题消息存储按顺序读写 - 顺序写 ;此外还有其他的 日志辅助文件(消息队列文件 & 索引文件)
-
消息的高可用性的考虑(由于:broker异常崩溃、操作系统崩溃、机器断电、机器、磁盘损坏)前3中情况 rocketmq在同步刷盘的模式下可以确保不丢失消息, 在异步刷盘模式下,会丢失少量; 后面几种则是属于单点故障,一旦发生,该节点上的消息会全部丢失。如果开启了异步复制机制, rocketMq只能保证丢失少量消息
-
想一想, 高可用 高吞吐 高并发 各自要解决的问题是什么,又有什么应对手段呢? 高可用:重试,规避,存储复制/备份,多节点保活; 吞吐:文件/网络打交道;高并发 --这就不太清楚了,
-
rocketMq 在消息没有发生堆积的情况下,会以 长轮询的模式 实现准实时的推拉模式
-
Runtime.getRuntime().addShutdownHook()- “注册JVM钩子函数”
-
RocketMQ路由发现是非实时的, broker 将自己地址信息给到nameserver,消息生产者自己去查、自己感知有无新的 broker加入
-
想一想, mq 里面几个角色 之间的关系, broker 、ns、consumer、producer – 单向的?双向的?主动通知的还是靠自己去拉的?
-
-
消息发送 (三种实现方式 : 可靠同步-sync发送、可靠异步-async发送、单向-oneway发送)
-
学习消息的数据结构、消息发送的流程、批量消息发送; 需要考虑3个问题:如何进行负载、如何实现高可用、批量如何实现一致性
-
topic路由机制 : 消息发送者向某一个topic发送消息时- producer,需要查询topic的路由信息 ; 1、初次发送时会根据topic的名称向NameServer集群查询topic的路由信息,2、然后将其存储在本地内存缓存中,3、并且每隔30s依次遍历缓存中的topic,向NameServer查询最新的路由信息。如果成功查询到路由信息,会将这些信息更新至本地缓存,实现topic路由信息的动态感知。
-
RocketMQ提供了自动创建主题(topic)的机制,消息发送者向一个不存在的主题发送消息时,向NameServer查询该主题的路由信息会先返回空,如果开启了自动创建主题机制,会使用一个默认的主题名再次从NameServer查询路由信息,然后消息发送者会使用默认主题的路由信息进行负载均衡,但不会直接使用默认路由信息为新主题创建对应的路由信息。使用默认主题创建路由信息的流程如图3-1所示 —(创建topic的工作是在 broker中)- RocketMQ中的路由信息是持久化在Broker中的,NameServer中的路由信息来自Broker的心跳包并存储在内存中
-
消息发送高可用设计 , 消息发送 RocketMQ默认使用轮询算法进行路由的负载均衡,如果使用自定义的路由负载算法后,RocketMQ的重试机制将失效 ; 引入了两个非常重要的特性; 一、消息发送的重试机制(发送失败的时候-默认重试两次)、二、故障规避机制(“因此为了保证重试的可靠性,在重试时会尽量避开刚刚接收失败的Broker,而是选择其他Broker上的队列进行发送,从而提高消息发送的成功率) 消息是发给 broker的哈
-
-
消息发送的流程
-
-
Message 是消息基类 --继承了 google的 protobuf 相关的类
-
我们可以看一下 消息首先是怎么发送的, 当我们初始化好 producer 和 consumer的时候
// 绑定相关topic和tag还有消息体
Message message = buildMessageWithKey(topic, tag, messageBody, null);
SendResult result = metaProducer.send(message);
// result.getSendStatus() == SendStatus
public enum SendStatus {
SEND_OK,
FLUSH_DISK_TIMEOUT,
FLUSH_SLAVE_TIMEOUT,
SLAVE_NOT_AVAILABLE;
}
public enum CommunicationMode {
SYNC,
ASYNC,
ONEWAY,
}
-
消息生产者启动流程
消息生产者是如何一步一步启动的呢?我们可以从DefaultMQProducerImpl的start方法来跟踪,具体细节如代码清单3-3所示。 -
消息发送流程 checkMessage
---- > tryToFindTopicPublishInfo(是查找主题的路由信息的方法。如果生产者中缓存了topic的路由信息,且该路由信息包含消息队列,则直接返回该路由信息。如果没有缓存或没有包含消息队列,则向NameServer查询该topic的路由信息。如果最终未找到路由信息)
–> selectOneMessageQueue()
—> sendKernelImpl() -
故障可容忍阶段-- LatencyFaultTolerance(),
-
注意 :开启与不开启sendLatencyFaultEnable机制在消息发送时都能规避故障的Broker,那么这两种机制有何区别呢?
开启所谓的故障延迟机制,即设置sendLatencyFaultEnable为ture,其实是一种较为悲观的做法。当消息发送者遇到一次消息发送失败后,就会悲观地认为Broker不可用,在接下来的一段时间内就不再向其发送消息,直接避开该Broker。而不开启延迟规避机制,就只会在本次消息发送的重试过程中规避该Broker,下一次消息发送还是会继续尝试 -
消息里的一些属性 Message
5) compressMsgBodyOverHowmuch:消息体超过该值则启用压缩,默认为4KB。
6) retryTimesWhenSendFailed:同步方式发送消息重试次数,默认为2,总共执行3次。
7)retryTimesWhenSendAsyncFailed:异步方式发送消息的重试次数,默认为2。
8)retryAnotherBrokerWhenNotStoreOK:消息重试时选择另外一个Broker,是否不等待存储结果就返回,默认为false。
9)maxMessageSize:允许发送的最大消息长度,默认为4MB,最大值为2^32-1。
消息存储
== 持久化
从存储方式和效率来看,文件系统高于KV存储,KV存储又高于关系型数据库,直接操作文件系统肯定是最快的,但可靠性是最低的,而关系型数据库的性能和可靠性与文件系统恰恰相反; 想一想 redis的持久化,mysql的持久化,mysql自己作为数据库–最终也还是文件存储,— 又让我想到 –liunx 一切皆文件; 说白了 存储的话最终会落到文件上,但是看看以什么样的介质去存储,数据库、kv、or直接写文件
RocketMQ存储概要设计
主要包括CommitLog文件、ConsumeQueue文件、Index文件。RocketMQ将所有主题的消息存储在同一个文件中,确保消息发送时按顺序写文件,尽最大的能力确保消息发送的高性能与高吞吐量。因为消息中间件一般是基于消息主题的订阅机制,所以给按照消息主题检索消息带来了极大的不便。为了提高消息消费的效率,RocketMQ引入了ConsumeQueue消息消费队列文件,每个消息主题包含多个消息消费队列,每一个消息队列有一个消息文件。Index索引文件的设计理念是为了加速消息的检索性能,根据消息的属性从CommitLog文件中快速检索消息 ; 消息一旦写入,不支持修改; 无论是CommitLog、ConsumeQueue还是IndexFile,**单个文件都是固定长度的,命名格式也是固定的 **; 如果一个文件写满后再创建一个新的文件,文件名就为该文件的第一条消息对应的全局物理偏移量。 MappedFile是RocketMQ内存映射文件的具体实现 --具体单位
整体的日志文件:
1)CommitLog:消息存储,所有消息主题的消息都存储在CommitLog文件中。
2)ConsumeQueue:消息消费队列,消息到达CommitLog文件后,将异步转发到ConsumeQuene文件中,供消息消费者消费。 ( RocketMQ为了适应消息消费的检索需求,设计了消息消费队列文件(ConsumeQueue) )
3)Index:消息索引,主要存储消息key与offset的对应关系
CommitLog 长什么样? 命名是固定的, 大小也是固定的-- [应该说 最大的容量是固定的 ] ,里面会有 **topic、消息长度(4个字节)那最大是多少? ** ; 这样做的好处是给出任意一个消息的物理偏移量,可以通过二分法进行查找,快速定位这个文件的位置,然后用消息物理偏移量减去所在文件的名称,得到的差值就是在该文件中的绝对地址。
ConsumeQueue : 以topic为一级检索目录, 下面是 队列ID ,再就是偏移量吧-- 不是很清楚 ,
Index:
很多索引存放,依赖的是,Hash索引机制-- 很多hash槽、slot;
最后:
pagecache 刷? 只读的话 – pagecache的作用?? 堆外内存!
嗯 我不知道 该怎么记录了…