问题

  • 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的更多相关文章

  1. 消息队列之-RocketMQ入门

    简介 RocketMQ是阿里开源的消息中间件,目前已经捐献个Apache基金会,它是由Java语言开发的,具备高吞吐量.高可用性.适合大规模分布式系统应用等特点,经历过双11的洗礼,实力不容小觑. 官 ...

  2. [分布式学习]消息队列之rocketmq笔记

    文档地址 RocketMQ架构 哔哩哔哩上的视频 mq有很多,近期买了<分布式消息中间件实践>这本书,学习关于mq的相关知识.mq大致有有4个功能: 异步处理.比如业务端需要给用户发送邮件 ...

  3. 消息队列之--RocketMQ

    序言 资料 https://github.com/alibaba/RocketMQ http://rocketmq.apache.org/

  4. 消息队列中间件 RocketMQ 源码分析 —— Message 存储

  5. 阿里消息队列中间件 RocketMQ源码解析:Message发送&接收

  6. 阿里消息队列中间件 RocketMQ 源码分析 —— Message 拉取与消费(上)

  7. IPC之消息队列详解与使用

    一.    概念 消息队列就是一个消息的链表.对消息队列有写权限的进程可以向其中按照一定的规则添加新消息:对消息队列有读权限的进程可以从消息队列中读出消息.消息队列是随内核持续的.下面介绍三个概念: ...

  8. C#消息队列(RabbitMQ)零基础从入门到实战演练

    一.课程介绍 如果您从工作中之听过但未有接触过消息对队列(MQ),如果你接触过一点关于MQ的知识,如果没有这么的多如果的话......,那么阿笨将通过本次<C#消息队列零基础从入门到实战演练&g ...

  9. 【windows 操作系统】进程间通信(IPC)简述|无名管道和命名管道 消息队列、信号量、共享存储、Socket、Streams等

    一.进程间通信简述 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进 ...

  10. IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

    1.引言 消息是互联网信息的一种表现形式,是人利用计算机进行信息传递的有效载体,比如即时通讯网坛友最熟悉的即时通讯消息就是其具体的表现形式之一. 消息从发送者到接收者的典型传递方式有两种: 1)一种我 ...

随机推荐

  1. IIS新类型文件500错误

    时间过得太久,就真的会忘掉.在这里记录一下吧. 不得已重新安装系统,然后以前的配置都忘掉了.对新类型的文件.geojson 文件报错. 500错误. 首先反复调试MIME类型是否有问题, 再看映射是否 ...

  2. 【转载】各种SQL在PIG中实现

    转自:http://guoyunsky.iteye.com/blog/1317084 我这里以Mysql 5.1.x为例,Pig的版本是0.8 同时我将数据放在了两个文件,存放在/tmp/data_f ...

  3. 第五十四篇 Linux相关——远程连接SSH

        No.1. SSH(Secure Shell)安全外壳协议 建立在应用层基础上的安全协议 可靠,专为远程登录会话和其他网络服务提供安全性的协议 有效防止远程管理过程中的信息泄漏问题 SSH客户 ...

  4. 通过FormData对象可以组装一组用 [XMLHttpRequest]发送请求的键/值对,它可以更灵活方便的发送表单数据。

    工作记录用 1 大概页面,点击选择按钮,选择文件,填写备注并可以上传前预览,然后点击上传按钮开始上传 2 html+js代码 <h2>Test</h2> <div id= ...

  5. 网络、芯片、专利、产业链……影响5G手机走势的因素有哪些?

    近段时间,备受关注的5G手机迎来一个爆发的小高潮.中国质量认证中心官网显示8款5G手机获得3C认证.其中华为有4款 ,一加.中兴.OPPO和vivo各有一款5G手机获得3C认证.随后在7月23日,中兴 ...

  6. dbGet (二)

    dbGet是由它基本的语法加上各种object的attribute的组合构成的.大家在熟悉基本语法之后,就应该去学习各个object的attribute了.说实话,这很难,因为attribute很多, ...

  7. thinkphp的同步上传图片和异步上传图片

    同步: 前台页面: <div class="form-div"> <form action="{:url('index/i/edit_head_img_ ...

  8. 2020最新版idea激活教程

    windows破解教程 首先下载jar包:(云盘链接发不上去,大家关注gzh"灰太狼学爪哇"回复idea获取)将其放到合适的文件夹(首选IDEA的同级目录)进行管理: 进入C盘 - ...

  9. bugku 隐写2

    首先打开链接发现是一张图片 然后分析一下图片 发现这个图片实际是一个压缩包 然后更改一下后缀名 改成zip 然后解压之后会发现另一个压缩包和一张图片压缩包需要密码然后这个图片是一个提示 然后分析 (我 ...

  10. Windows Server 2012 忘记登录密码怎么办?

    Windows Server 2012系统 忘记登录密码处理方法,此方法在其他 Server 系统应该是通用的(其他系统未做测试,请知悉) 电脑 Windows Server 2012系统 做好的U盘 ...