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. CentOS7 + Django2.1 + uwsgi + nginx配置

    假设已经可以运行Django项目,可以runserver.也已经安装了uwsgi和nginx 现在需要进行配置. 刚开始进行uwsgi测试就不行,提示bash:'uwsgi' Command not ...

  2. lab-kvm

    3)qemu帮助信息 qemu-kvm -h [root@Centos72 libvirt]#qemu-kvm -h QEMU emulator version (qemu-kvm--.el7_5.) ...

  3. ES6学习:两个面试题目--关于模板字符串

    号称看完就能“让开发飞起来”,不过文中的两个面试题目的知识点并没包括在文中. https://www.jianshu.com/p/287e0bb867ae 文中并没有完整的知识点去完成上面的两道题,这 ...

  4. docker install

    1.安装必要工具集 sudo yum install -y yum-utils 2.安装Docker官方源 sudo yum-config-manager \ --add-repo \ https:/ ...

  5. P层

    package net.goeasyway.uploadimage.presenter; import net.goeasyway.uploadimage.model.Photo;import net ...

  6. 【repost】图解Javascript上下文与作用域

    本文尝试阐述Javascript中的上下文与作用域背后的机制,主要涉及到执行上下文(execution context).作用域链(scope chain).闭包(closure).this等概念. ...

  7. C# 自动程序 windows 无法启动 XXXX 服务 错误5 拒绝访问

    遇到过两次 这样的问题了,所以记录一下 原因可能是服务所在文件的目录权限不够 解决方法: 1是查看服务对应的程序所在的目录 2是设置目录的安全权限 右击–属性–安全–添加相应的帐号,给予除完全控制外的 ...

  8. JavaScript实现页面刷新滚动条位置不变(利用cookie)

    实验环境:vs2015 asp.net(C#) 主要原理: 1.在页面滚动时或点击按钮时将当前滚动条位置记录到cookie[pos], 2.页面刷新或重载时查询cookie[pos]中的值是否存在,若 ...

  9. [转]Rapidly detecting large flows, sFlow vs. NetFlow/IPFIX

    Figure 1: Low latency software defined networking control loop The articles SDN and delay and Delay ...

  10. 亲子编程玩Micro:bit-动力小车“麦昆”

    少儿编程之风已经吹进各大城市,编程猫.乐博机器人.童程童美等专业培训机构逐渐进入大家的视野,年龄段已经从K12逐渐降低到幼儿园中班.其实,少儿编程的门槛并不高,它不会让孩子一上手就去接触代码,而是会通 ...