消息队列(五)--- RocketMQ-消息存储4
问题
- index 文件有什么作用,结构又是如何
概述
index 文件主要是为了 message key 服务的,rocketmq 发送消息的时候可以带上 key , messge key 是为了标识某个消息的一个标志。
思考
我们思考一下,message key 是由用户生成的,我们需要尽可能地保证散列保存,这样当我们就可以快速地拿出来了。那么通常的作法就是利用哈希散列,当然最重要的是如何解决冲突。我们下面看一下rocketmq 是如何实现的。
总体思路
下面两张图片来自参考文章。侵删(作者的文章写得真的好)

我们从这里可以看到index文件分为三部分,头,散列值,索引文件。其中散列值会一一对应索引文件中的一个值,该值就是储存该message信息的。

可以看到假如有冲突(即找到散列值那个位置的时候已经有一个对应的索引位了),那么索引位就存放在新的索引位的“上一个索引位”的属性里,这里就形成一条单链表。
源码分析
index文件和其他consumerQueue文件的思路是一样的,同样是利用持久化在文件中,然后通过mappedFile 文件加载到内存中,开启一个服务,当发消息对消息进行持久化的时候,将消息的key持久化在index文件。
写入
DefaultMessageStore$$CommitLogDispatcherBuildIndex内部类
class CommitLogDispatcherBuildIndex implements CommitLogDispatcher {
@Override
public void dispatch(DispatchRequest request) {
if (DefaultMessageStore.this.messageStoreConfig.isMessageIndexEnable()) {
DefaultMessageStore.this.indexService.buildIndex(request);
}
}
}
该内部类就是当接受到消息对index进行记录的分发器,可以看到最主要的还是利用了indexService,我们来看一下indexService到底执行了什么操作。

从方法名我们对index文件的加载,会刷,获取,写入等。我们看一下 buildIndex 方法
IndexFile&putKey方法
public boolean putKey(final String key, final long phyOffset, final long storeTimestamp) {
if (this.indexHeader.getIndexCount() < this.indexNum) {
//获取hashCode 后取模
int keyHash = indexKeyHashMethod(key);
int slotPos = keyHash % this.hashSlotNum;
//计算绝对位置
int absSlotPos = IndexHeader.INDEX_HEADER_SIZE + slotPos * hashSlotSize;
FileLock fileLock = null;
try {
// fileLock = this.fileChannel.lock(absSlotPos, hashSlotSize,
// false);
int slotValue = this.mappedByteBuffer.getInt(absSlotPos);
if (slotValue <= invalidIndex || slotValue > this.indexHeader.getIndexCount()) {
slotValue = invalidIndex;
}
long timeDiff = storeTimestamp - this.indexHeader.getBeginTimestamp();
timeDiff = timeDiff / 1000;
if (this.indexHeader.getBeginTimestamp() <= 0) {
timeDiff = 0;
} else if (timeDiff > Integer.MAX_VALUE) {
timeDiff = Integer.MAX_VALUE;
} else if (timeDiff < 0) {
timeDiff = 0;
}
//为什么要把 hashSlot 那部分的总长也加进来呢?
//因为这个pos是相对于 mapfile的位移量进行获取的
int absIndexPos =
IndexHeader.INDEX_HEADER_SIZE + this.hashSlotNum * hashSlotSize
+ this.indexHeader.getIndexCount() * indexSize;
this.mappedByteBuffer.putInt(absIndexPos, keyHash);
this.mappedByteBuffer.putLong(absIndexPos + 4, phyOffset);
this.mappedByteBuffer.putInt(absIndexPos + 4 + 8, (int) timeDiff);
//先保存上一个槽位的值
this.mappedByteBuffer.putInt(absIndexPos + 4 + 8 + 4, slotValue);
//然后覆盖掉旧的
this.mappedByteBuffer.putInt(absSlotPos, this.indexHeader.getIndexCount());
if (this.indexHeader.getIndexCount() <= 1) {
this.indexHeader.setBeginPhyOffset(phyOffset);
this.indexHeader.setBeginTimestamp(storeTimestamp);
}
//更新index属性
this.indexHeader.incHashSlotCount();
this.indexHeader.incIndexCount();
this.indexHeader.setEndPhyOffset(phyOffset);
this.indexHeader.setEndTimestamp(storeTimestamp);
return true;
} catch (Exception e) {
log.error("putKey exception, Key: " + key + " KeyHashCode: " + key.hashCode(), e);
} finally {
if (fileLock != null) {
try {
fileLock.release();
} catch (IOException e) {
log.error("Failed to release the lock", e);
}
}
}
} else {
log.warn("Over index file capacity: index count = " + this.indexHeader.getIndexCount()
+ "; index max num = " + this.indexNum);
}
return false;
}
//直接取 hashCode,并返回正数
public int indexKeyHashMethod(final String key) {
int keyHash = key.hashCode();
int keyHashPositive = Math.abs(keyHash);
if (keyHashPositive < 0)
keyHashPositive = 0;
return keyHashPositive;
}
读取的方法
读取方法的调用链如下 :
DefaultMessageStore&queryMessage -> indexService&queryOffset -> IndexFile&selectPhyOffset
DefaultMessageStore&queryMessage 方法
@Override
public QueryMessageResult queryMessage(String topic, String key, int maxNum, long begin, long end) {
QueryMessageResult queryMessageResult = new QueryMessageResult(); long lastQueryMsgTime = end; //重试
for (int i = 0; i < 3; i++) {
//查找到返回结果
QueryOffsetResult queryOffsetResult = this.indexService.queryOffset(topic, key, maxNum, begin, lastQueryMsgTime);
if (queryOffsetResult.getPhyOffsets().isEmpty()) {
break;
}
//封装返回结果
Collections.sort(queryOffsetResult.getPhyOffsets()); queryMessageResult.setIndexLastUpdatePhyoffset(queryOffsetResult.getIndexLastUpdatePhyoffset());
queryMessageResult.setIndexLastUpdateTimestamp(queryOffsetResult.getIndexLastUpdateTimestamp()); for (int m = 0; m < queryOffsetResult.getPhyOffsets().size(); m++) {
long offset = queryOffsetResult.getPhyOffsets().get(m); try { boolean match = true;
MessageExt msg = this.lookMessageByOffset(offset);
if (0 == m) {
lastQueryMsgTime = msg.getStoreTimestamp();
} // String[] keyArray = msg.getKeys().split(MessageConst.KEY_SEPARATOR);
// if (topic.equals(msg.getTopic())) {
// for (String k : keyArray) {
// if (k.equals(key)) {
// match = true;
// break;
// }
// }
// } if (match) {
SelectMappedBufferResult result = this.commitLog.getData(offset, false);
if (result != null) {
int size = result.getByteBuffer().getInt(0);
result.getByteBuffer().limit(size);
result.setSize(size);
queryMessageResult.addMessage(result);
}
} else {
log.warn("queryMessage hash duplicate, {} {}", topic, key);
}
} catch (Exception e) {
log.error("queryMessage exception", e);
}
} if (queryMessageResult.getBufferTotalSize() > 0) {
break;
} if (lastQueryMsgTime < begin) {
break;
}
} return queryMessageResult;
}
indexService&queryOffset 逻辑很好懂,定位文件,解决冲突的链表进行查找
public QueryOffsetResult queryOffset(String topic, String key, int maxNum, long begin, long end) {
List<Long> phyOffsets = new ArrayList<Long>(maxNum);
long indexLastUpdateTimestamp = 0;
long indexLastUpdatePhyoffset = 0;
maxNum = Math.min(maxNum, this.defaultMessageStore.getMessageStoreConfig().getMaxMsgsNumBatch());
try {
this.readWriteLock.readLock().lock();
if (!this.indexFileList.isEmpty()) {
for (int i = this.indexFileList.size(); i > 0; i--) {
IndexFile f = this.indexFileList.get(i - 1);
boolean lastFile = i == this.indexFileList.size();
if (lastFile) {
indexLastUpdateTimestamp = f.getEndTimestamp();
indexLastUpdatePhyoffset = f.getEndPhyOffset();
}
if (f.isTimeMatched(begin, end)) {
f.selectPhyOffset(phyOffsets, buildKey(topic, key), maxNum, begin, end, lastFile);
}
if (f.getBeginTimestamp() < begin) {
break;
}
if (phyOffsets.size() >= maxNum) {
break;
}
}
}
} catch (Exception e) {
log.error("queryMsg exception", e);
} finally {
this.readWriteLock.readLock().unlock();
}
return new QueryOffsetResult(phyOffsets, indexLastUpdateTimestamp, indexLastUpdatePhyoffset);
}
总结
参考资料
- https://www.kunzhao.org/blog/2018/04/08/rocketmq-message-index-flow/
消息队列(五)--- RocketMQ-消息存储4的更多相关文章
- 消息队列之-RocketMQ入门
简介 RocketMQ是阿里开源的消息中间件,目前已经捐献个Apache基金会,它是由Java语言开发的,具备高吞吐量.高可用性.适合大规模分布式系统应用等特点,经历过双11的洗礼,实力不容小觑. 官 ...
- [分布式学习]消息队列之rocketmq笔记
文档地址 RocketMQ架构 哔哩哔哩上的视频 mq有很多,近期买了<分布式消息中间件实践>这本书,学习关于mq的相关知识.mq大致有有4个功能: 异步处理.比如业务端需要给用户发送邮件 ...
- 消息队列之--RocketMQ
序言 资料 https://github.com/alibaba/RocketMQ http://rocketmq.apache.org/
- 消息队列中间件 RocketMQ 源码分析 —— Message 存储
- 阿里消息队列中间件 RocketMQ源码解析:Message发送&接收
- 阿里消息队列中间件 RocketMQ 源码分析 —— Message 拉取与消费(上)
- IPC之消息队列详解与使用
一. 概念 消息队列就是一个消息的链表.对消息队列有写权限的进程可以向其中按照一定的规则添加新消息:对消息队列有读权限的进程可以从消息队列中读出消息.消息队列是随内核持续的.下面介绍三个概念: ...
- C#消息队列(RabbitMQ)零基础从入门到实战演练
一.课程介绍 如果您从工作中之听过但未有接触过消息对队列(MQ),如果你接触过一点关于MQ的知识,如果没有这么的多如果的话......,那么阿笨将通过本次<C#消息队列零基础从入门到实战演练&g ...
- 【windows 操作系统】进程间通信(IPC)简述|无名管道和命名管道 消息队列、信号量、共享存储、Socket、Streams等
一.进程间通信简述 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进 ...
- IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列
1.引言 消息是互联网信息的一种表现形式,是人利用计算机进行信息传递的有效载体,比如即时通讯网坛友最熟悉的即时通讯消息就是其具体的表现形式之一. 消息从发送者到接收者的典型传递方式有两种: 1)一种我 ...
随机推荐
- checkstyle配置规格说明
参考文献:https://blog.csdn.net/yang1982_0907/article/details/18086693?utm_source=blogxgwz1 https://blog. ...
- try/catch/finally 语句
定义和用法 try/catch/finally 语句用于处理代码中可能出现的错误信息. 错误可能是语法错误,通常是程序员造成的编码错误或错别字.也 可能是拼写错误或语言中缺少的功能(可能由于浏览器差异 ...
- e.printStackTrace()打印在哪里以及如何e.printStackTrace()的内容打印在日志中
1.e.printStackTrace()打印在哪里 在catch中的e.printStackTrace()将打印到控制台 2.e.printStackTrace()打印的内容是什么 如下代码: im ...
- Go_Redis
Redis介绍 Redis是一个开源的内存数据库,Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上.除此之外,通过复制.持久化和客户端分片等特性,我们可以 ...
- 【原】AMFObject数据格式详解
AMF AMF是Action Message Format(动作消息格式)的简写,它是一种二进制的数据格式.它的设计是为了把actionscript里面的数据(包括Object, Array, Boo ...
- 微信小程序 获取cookie 以及设置 cookie
小程序开发中我们需要获取到后端给的cookie进行请求验证,但是微信并没有帮我们保存cookie,那么我们要维持会话需要自己来保存cookie,并且请求的时候加上cookie 1.获取cookie 在 ...
- 使用git pull同步github代码到服务器
我直接用git pull的时候遇到这个错误: error: Your local changes to the following files would be overwritten by merg ...
- php商城数据库的设计 之无限分类
商品分类,使用无限分类 即: -------如何创建数据表 pid---父级分类id,如果是顶级分类则为0 path---1,用户分类的排序 . 排序示例: 实现逻辑:获取type表的所有分类,ord ...
- Flask 教程 第二十章:加点JavaScript魔法
本文翻译自The Flask Mega-Tutorial Part XX: Some JavaScript Magic 这是Flask Mega-Tutorial系列的第二十部分,我将添加一个功能,当 ...
- HTTP状态码详解(下)
接上文 HTTP状态码详解(上). 详细的描述状态码之(3**) 300:被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息.用户或浏览器能够自行选择一个首选的地址进 ...