RocketMQ的主要特点以及实现方式

单机支持1万以上持久队列

所有数据单独存储到一个CommitLog,完全顺序写,随机读

在一个broker上一个DefaultMessageStore管理一个commitLog

顺序写:在commitLog.putMessage里面获取mapedFile之后进入synchronized块,开始写内存,所以当有新的消息需要保存的时候会等待锁释放,所以写消息的时候就是顺序的

MapedFile mapedFile = this.mapedFileQueue.getLastMapedFileWithLock();
// 给commitLog上锁
synchronized (this) {
// 保存消息
result = mapedFile.appendMessage(msg, this.appendMessageCallback);
}

随机读:因为在pull Message的时候根据consumeQueue来读取消息的,consumeQueue里面记录了offset,所以读取mapedFile的时候是按照offset随机读取的

对最终用户展现的是实际只存储了消息在commitLog的位置信息,并串行刷盘

最终用户接触到的是逻辑队列ComsumeQueue,只存储了topic、offset等信息

串行刷盘:DefaultMessageStore使用StoreCheckPoint记录当前刷盘的文件,并只将StoreCheckPoint的mapedFile进行刷盘

PageCache:文件cache是文件数据在内存中的副本,因此文件cache管理与内存管理和文件系统管理相关。文件cache分为两个层面,Page Cache和Buffer Cache,每一个Page Cache包含多个Buffer Cache。linux中文件Cache的操作分为两类,一是在文件Cache与应用程序提供的用户空间buffer拷贝数据(普通的read/wrote操作),二是使用mmap将Cache映射到用户空间,并没有拷贝(所以速度更快),用户空间可以像使用指针一样(普通访问文件是使用流)访问文件

刷盘策略

在commitLog.putMessage中决定刷盘方式,在MessageStoreConfig中配置刷盘的方式

RocketMQ的消息都是持久化的:所有消息保存在commitlog文件夹下的文件中

先写入系统PageCache:所有commitlog下的文件都是使用的直接内存,采用mmap文件映射的方法,每次接收到消息的时候先把消息写入直接内存PageCache——即mapedFile

然后刷盘:启动commitLog的时候会启动刷盘的线程(FlushCommitLogService)定时刷盘

可以保证内存与磁盘都有一份数据,访问消息的时候直接从内存中读取:读取文件的时候直接从mapedFile取

// 同步刷盘,使用GroupCommitService
if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
// 是否配置为等待
if (msg.isWaitStoreMsgOK()) {
request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());
service.putRequest(request);
// 超时等待
boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
if (!flushOK) {
log.error("do groupcommit, wait for flush failed, topic: " + msg.getTopic() + " tags: " + msg.getTags()
+ " client address: " + msg.getBornHostString());
putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);
}
} else {
service.wakeup();
}
}
// 异步刷盘,FlushRealTimeService
else {
this.flushCommitLogService.wakeup();
}

消息过滤

  1. 在DefaultMessageStore.getMessage的时候,先根据topic和queueId获取ConsumeQueue,然后读取consumeQueue,一次对比consumeQueue的tag的hashCode,如果匹配才去读取commitLog
  2. 存储tag的hashCode,定长节省空间
  3. 先读取consumeQueue,在消息堆积的情况下也能高效过滤消息

长轮询pull

在PullMessageService的run方法中pull message,在获取返回结果的回调中再次发起请求(只是添加pullRequest到pullRequestQueue中)——也就是长轮询

发送消息负载均衡

在DefaultMQProducerImpl send message的时候会调用selectOneMessageQueue(MessageQueue包含了topic,broker,queueID等信息,表明消息发送到哪一个broker的哪一个queue),使用递增取模的方法决定使用哪一个messageQueue

消费消息的负载均衡

AllocateMessageQueueAveragely.allocate实现了consumer消费的默认负载均衡算法,、

public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
List<String> cidAll) {
if (currentCID == null || currentCID.length() < 1) {
throw new IllegalArgumentException("currentCID is empty");
}
if (mqAll == null || mqAll.isEmpty()) {
throw new IllegalArgumentException("mqAll is null or mqAll empty");
}
if (cidAll == null || cidAll.isEmpty()) {
throw new IllegalArgumentException("cidAll is null or cidAll empty");
} List<MessageQueue> result = new ArrayList<MessageQueue>();
if (!cidAll.contains(currentCID)) {
log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}", //
consumerGroup, //
currentCID,//
cidAll);
return result;
} // 基本原则,每个队列只能被一个consumer消费
// 当messageQueue个数小于等于consume的时候,排在前面(在list中的顺序)的consumer消费一个queue,index大于messageQueue之后的consumer消费不到queue,也就是为0
// 当messageQueue个数大于consumer的时候,分两种情况
// 当有余数(mod > 0)并且index < mod的时候,当前comsumer可以消费的队列个数是 mqAll.size() / cidAll.size() + 1
// 可以整除或者index 大于余数的时候,队列数为:mqAll.size() / cidAll.size()
int index = cidAll.indexOf(currentCID);
int mod = mqAll.size() % cidAll.size();
int averageSize =
mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
+ 1 : mqAll.size() / cidAll.size());
int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
int range = Math.min(averageSize, mqAll.size() - startIndex);
for (int i = 0; i < range; i++) {
result.add(mqAll.get((startIndex + i) % mqAll.size()));
}
return result;
}

该负载平衡算法:

就是把messageQueue放到一个队列中,consumer放到一个队列中,messageQueue依次分配给consumer,

如果不够分配,则排在后面的consumer就不能消费messageQueue

如果给consumer分配完一轮之后,messageQueue还有多余,那么messageQueue接着分配,consumer队列从头开始

示意图如下:

HA,同步双写,异步复制

HAService,RocketMQ的高可用服务

同步双写:在commitLog.putMessage中进行同步双写,将GroupCommitRequest放进GroupTransferService.requestWrite等待slave主动拉取,master超时等待同步双写完成。所以在写消息的时候是同步等待的,slave从master复制消息的时候是异步的

RocketMQ源码 — 五、 主要feature及其实现方式的更多相关文章

  1. ROCKETMQ源码分析笔记1:tools

    rocketmq源码解析笔记 大家好,先安利一下自己,本人男,35岁,已婚.目前就职于小资生活(北京),职位是开发总监. 姓名DaneBrown 好了.我保证本文绝不会太监!转载时请附上以上安利信息. ...

  2. RocketMQ源码 — 六、 RocketMQ高可用(1)

    高可用究竟指的是什么?请参考:关于高可用的系统 RocketMQ做了以下的事情来保证系统的高可用 多master部署,防止单点故障 消息冗余(主从结构),防止消息丢失 故障恢复(本篇暂不讨论) 那么问 ...

  3. RocketMQ 源码学习笔记————Producer 是怎么将消息发送至 Broker 的?

    目录 RocketMQ 源码学习笔记----Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest ...

  4. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest Roc ...

  5. RocketMQ 源码分析 —— Message 发送与接收

    1.概述 Producer 发送消息.主要是同步发送消息源码,涉及到 异步/Oneway发送消息,事务消息会跳过. Broker 接收消息.(存储消息在<RocketMQ 源码分析 —— Mes ...

  6. RocketMQ源码分析之从官方示例窥探:RocketMQ事务消息实现基本思想

    摘要: RocketMQ源码分析之从官方示例窥探RocketMQ事务消息实现基本思想. 在阅读本文前,若您对RocketMQ技术感兴趣,请加入RocketMQ技术交流群 RocketMQ4.3.0版本 ...

  7. 记一次RocketMQ源码导入IDEA过程

    首先,下载源码,可以官网下载source包,也可以从GitHub上直接拉下来导入IDEA.如果是官网下载的source zip包,直接作为当前project的module导入,这里不赘述太多,只强调一 ...

  8. 【RocketMQ源码分析】深入消息存储(1)

    最近在学习RocketMQ相关的东西,在学习之余沉淀几篇笔记. RocketMQ有很多值得关注的设计点,消息发送.消息消费.路由中心NameServer.消息过滤.消息存储.主从同步.事务消息等等. ...

  9. 【RocketMQ源码分析】深入消息存储(3)

    前文回顾 CommitLog篇 --[RocketMQ源码分析]深入消息存储(1) ConsumeQueue篇 --[RocketMQ源码分析]深入消息存储(2) 前面两篇已经说过了消息如何存储到Co ...

随机推荐

  1. MongoDB的数据备份与恢复

    一:数据备份操作 步骤: 1.以管理员身份打开cmd,然后打开到mongdb的bin文件夹 2.输入命令 mongodump -h dbhost -d dbname -o dbdirectory -h ...

  2. Selenium+PyCharm环境搭建

    一.首先安装python并配置好环境变量 二.安装selenium 安装文件夹在安装的python文件夹下,例:D:\Program\python\Lib\site-packages\selenium ...

  3. uni-app 顶部导航点击更换图标

    更换顶部导航的iconfont.ttf图标,先在配置文件配置好按钮: pages.json文件 "buttons": [ { "text": "\ue ...

  4. Apple Mach-O Linker Error Group 与 "_OBJC_CLASS_$_XXXXXX", referenced from: 和 clang: error: linker command failed with exit code 1 (use -v to see invocation) 问题.

    此问题为链接报错,在Build Settings中的Other Linker Flags添加:-l"XXXXXX"

  5. Codeforces 884 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 传送门 A题 传送门 题意简述: 一个人要完成一件事总共需要ttt秒,现在有nnn天,每天有aia_iai​不能做事,问他可以在第几天做完. 思路:按照题 ...

  6. vuex创建store并用computed获取数据

    vuex中的store是一个状态管理器,用于分发数据.相当于父组件数据传递给子组件. 1.安装vuex npm i vuex --save 2.在src目录中创建store文件夹,里面创建store. ...

  7. linux nfs远程挂载和卸载

    一.nfs远程挂载 1.首先确定服务端(实体挂载节点)的IP 2.通过cat  /etc/hosts 查看服务端的server name 3.mount -t nfs servername:/挂载文件 ...

  8. 爬虫学习笔记-urllib库

    urllib库是python中一个最基本的网络请求库.可以模拟浏览器的行为,向指定的服务器发送一个请求,并可以保存服务器返回的数据. urlopen函数:在python3的urllib库中,所有和网络 ...

  9. 修改Macros的值

    修改模板的macro  修改对应主机的macro

  10. MySql在Mac上的安装配置

    一.下载安装 官网下载社区版dmg安装文件: https://dev.mysql.com/downloads/mysql/ 1.执行安装文件,按步骤完成安装. 2.安装完成后终端输入: mysql - ...