前文回顾

CommitLog篇 ——【RocketMQ源码分析】深入消息存储(1)

ConsumeQueue篇 ——【RocketMQ源码分析】深入消息存储(2)

前面两篇已经说过了消息如何存储到CommitLog,以及ConsumeQueue的构建流程,到了第三篇,我们有一个不得不跨过的坎儿,MappedFile —— 内存文件映射。

MappedFile的存在是RocketMQ选择将消息直接存储到磁盘的关键因素,在第一篇CommitLog存储流程开篇中,我就写过一个思路。

  1. 即用到内存又用到本地磁盘
  2. 填充和交换
  3. 文件映射到内存
  4. 随机读接口去访问

这里出现的几个关键句,都离不开本篇要说的MappedFile。

RocketMQ既然要去与磁盘交互存储文件,不同IO方法在性能差距上都是千差万别的,怎么高效的与磁盘/内存进行交互,是很多涉及存储的中间件强大与否的重要标志。

实现一个进程内基于队列的消息持久化存储引擎

这是几年前天池中间件大赛的题目,目标就是设计一个利用有限内存、较多磁盘空间来实现一个消息队列,这样看其实思路在第一篇就已经说过了,重点是他要求这个队列支持聚合操作。

这让我想到ElasticSearch的聚合场景,如果要实现那么复杂的聚合功能,也太南了吧。

不过好在题目只是要求做指定时间段的消息加和,这无非就是维护一个消息存储的偏移量与时间的存储就好了。

为了深入了解内存文件映射,我们可以来读读它的源码,这里相对于CommitLog、ConsumeQueue更加底层,更多涉及的是IO、Buffer、PageCache等知识。

从页表谈到零拷贝

在我过去学习汇编语言的时候,有两个寻址相关的寄存器。

段寄存器、变址寄存器。

在8086的年代,地址总线是20位,但寄存器16位,寻址能力有限,为了保证1M的寻址能力,是将两个16位寄存器一起使用,以段基址和偏移地址的形式,达到1M寻址能力。

这个思想在操作系统保护模式下也是一样的,假如我们有一台32位操作系统,内存4GB。

我们来思考一下它的内存布局,内核空间和用户空间这是我们熟知的概念了,假如内存空间不做任何操作,按顺序性让我们去访问,首先一个大问题就是内存隔离,两个进程之间如何做到内存互不污染,这也引出了Java虚拟机内存分配的一个问题,分配之后的内存空间被垃圾回收器清理,剩下的空间大大小小可能不连续,后续一个需要占据大内存的对象可能无法存储,JVM可以选择回收-清理的方式保证没有碎片,这是因为有栈上的引用指向堆,一个大对象就算被移动也不用担心,但操作系统不同,如果想用类似JVM回收-清理的方式减少碎片内存,首先一个要面对的问题就是地址变更,后续进程在寻址时可能找不到目标。

此处需要注意地址变更,因为后面我们也会提到,操作系统的PageCache操作不当也会引起这个问题。

还有一个问题是,这种循序的空间并不安全,所有进程之间都可以互相访问到对方的地址,这是一些修改器的常用手段。

基于以上问题,操作系统映入了保护模式,基于页表将内存空间调整为虚拟内存,与实际的物理内存区分开。

现在的页表通常是二级页表,所谓两级页表就是对页表再进行分页,一个页表内的所有页表项是连续存放的,页表本质上是一堆数据,也是以页为单位存放在内存。

第一级称为页目录表。每个页表的物理地址在页目录表中都以页目录项(PDE)的形式来存储,4MB的页表再次分页可以分为1K(4MB/4KB)个页,对每个页的描述需要4个字节,所以页目录表占用4K大小,正好是一个标准页的大小,其指向第二级表。线性地址的高10位产生第一级的索引,由索引得到的表项中,指定并选择了1K个二级表中的一个页表。

第二级称为页表,存放在一个4K大小的页面中,包含1K个表项,每个表项包含一个页的物理基地址。线性地址的中间10位产生第二级索引,可以获得包含页的物理地址的页表项。这个物理地址的高20位与线性地址的低12位形成了最终的物理地址。

有了页表就能很好的划分进程空间,以及减少碎片空间了,对于一个进程而言,理论上最大可使用空间为4GB。基于此,操作系统的内存操作大多都是基于页(4KB).

虚拟内存的映入使得操作系统管理划分内存更加方便,实际进行虚拟地址映射到物理地址的单元是MMU,mmap内存文件映射也是一样,通过MMU映射到文件。

为了解决磁盘IO效率低下的问题,操作系统在进程空间内增加了一片空间,用于与磁盘文件进行地址映射,这部分内存也是虚拟内存地址,通过指针操作这部分内存,系统会自动将处理过的页写回对应的磁盘文件位置,就不需要去调用系统read、write等函数,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

这部分内存映射需要维护一份页表,用于管理内存——文件地址的映射关系,如果当前虚拟内存地址找不到对应的物理地址,就会发生所谓的缺页,缺页时系统会根据地址偏移量在PageCache中查看目标地址是否已经缓存过了,如果有就直接指向该PageCache地址,如果没有就需要将目标文件加载入PageCache中。

通过mmap的映射功能,就能避免IO操作,直接去操作内存,这就是所谓的零拷贝技术。

下面将要从几幅图说起IO到零拷贝。

这是最普通的文件服务器传输文件过程,首先在内核态将文件从物理设备读取到内核空间,这是一次直接直接内存拷贝,然后用户进程需要从内核中将数据读取到用户进程空间,完成读的流程,这是一次CPU拷贝,至此,读的过程完成了,进程需要将数据发送给客户端,这时有需要将数据放到内核空间的socket处,之后通过协议层发送出去。

这整个流程需要两次CPU拷贝、两次直接内存拷贝,还需要不断在内核态用户态切换。(第一种:四次)

第二种模型是引入了mmap,在内核空间与用户空间建立映射关系,就可以让socket空间直接操作内核空间就能完成拷贝功能,还不需要在内核态用户态之间切换,write系统调用使内核将数据从原始内核缓冲区复制到与套接字关联的内核缓冲区中。

这个方式使用mmap代替了read,虽然看上去减少了拷贝,但是缺存在风险。当映射一个文件到内存,然后调用write,在另一个进程write同一个文件时,就会发生系统错误。(第二种:三次)

第三种模型,基于Linux新增引入的sendfile系统调用,不仅能减少文件拷贝,还能减少系统切换,sendfile可以直接完成内核空间的拷贝流程,从内核空间拷贝到套接字空间,由此跳过了用户空间。(第三种:三次)

第四种模型,在内核版本2.4中,对sendfile进行了优化,可以直接从内核空间将数据发送到协议器,还消除了到套接字区域的数据拷贝,对于用户级应用程序没有任何变化。(第四种:两次)

综上,数据发送的流程中数据不会结果多余的拷贝,内核与用户态空间内都不会有多余的备份,这就是所谓的零拷贝技术,基于sendfile与mmap。

说回RocketMQ

MQ是IO使用的大户,MMap、FileChannel、RandomAccessFile是MQ文件操作最常使用的方法。

RocketMQ支持MMap与FileChannel,默认使用MMap,在PageCache繁忙时,会使用FileChannel,同样也可以避免PageCache竞争锁。

在MappedFile类中,可以看到FileChannel与MappedByteBuffer两个变量,在Java代码中可以通过FileChannel的map方法将文件映射到虚拟内存。

在MappedFile的init方法中也可以看到mmap初始化的过程。

在实际的写入流程中,操作的buffer可能是mmap也可能是TransientStorePool申请来的直接内存,避免页面被换出到交换区。

TransientStorePool是否启用根据TransientStorePoolEnable确定,当开启时,表示优先使用堆外内存存储数据,通过Commit线程刷到内存映射Buffer中。

TransientStorePool是一个简易的池化类,其中包含了池的大小,每个单元存储的大小,存储单元的队列以及存储配置类。具体的初始化操作可以在init方法中看到有循环使用allocateDirect申请JVM外的内存空间,相比于allocate申请到的JVM内的内存,堆外内存操作更加迅速,免去了数据从堆外再次拷贝到堆内的流程。

申请到内存后,取到了申请的内存地址。

Pointer pointer = new Pointer(address);
LibC.INSTANCE.mlock(pointer, new NativeLong(fileSize));

拿到地址后,创建一个指向该处的指针,调用本地链接库的方法,将该地址的内存锁住,防止释放。

综上,相信你已经对页表、文件系统IO操作有了一定的认识了。

【RocketMQ源码分析】深入消息存储(3)的更多相关文章

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

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

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

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

  3. RocketMQ源码分析之RocketMQ事务消息实现原理上篇(二阶段提交)

    在阅读本文前,若您对RocketMQ技术感兴趣,请加入 RocketMQ技术交流群 根据上文的描述,发送事务消息的入口为: TransactionMQProducer#sendMessageInTra ...

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

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

  5. ROCKETMQ源码分析笔记2:client

    CLIENT 之前讲过tools里面有大量调用client的东西.为了从源码层面了解rocket,决定啃下client这块骨头. pom 先看pom,看看CLIENT依赖谁.看完后原来是依赖commo ...

  6. RocketMQ 源码分析之路由中心(NameServer)

    你可能没有看过 RocketMQ 的架构图,没关系,一起来学习一下,RocketMQ 架构图如下: 在 RocketMQ 中,有四个角色: Producer:消息的生产者,每个 MQ 中间件都有. C ...

  7. RocketMQ源码分析之RocketMQ事务消息实现原理中篇----事务消息状态回查

    上节已经梳理了RocketMQ发送事务消息的流程(基于二阶段提交),本节将继续深入学习事务状态消息回查,我们知道,第一次提交到消息服务器时消息的主题被替换为RMQ_SYS_TRANS_HALF_TOP ...

  8. RocketMQ源码分析之RocketMQ事务消息实现原下篇(事务提交或回滚)

    摘要: 事务消息提交或回滚的实现原理就是根据commitlogOffset找到消息,如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,然后将原 ...

  9. RocketMQ 源码分析 RouteInfoManager(四)

    在上一章分析了NamesrvController的构造函数时,会生成一个RouteInfoManager对象,该对象存放着整个消息集群的相关消息,所以这里单独拿出来分析.其实试想一下namesrv的功 ...

  10. rocketmq源码分析2-broker的消息接收

    broker消息接收,假设接收的是一个普通消息(即没有事务),此处分析也只分析master上动作逻辑,不涉及ha. 1. 如何找到消息接收处理入口 可以通过broker的监听端口10911顺藤摸瓜式的 ...

随机推荐

  1. Tomcat内部结构及工作原理学习

    Tomcat原本是Servlet/JSP的一个调试工具,后来才发展为一个Servlet/JSP的容器. Tomcat作为Servlet容器,负责处理客户请求,把请求传送给Servlet并把结果返回给客 ...

  2. 一:Shell基础

    1.shell概述  shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用shell来启动,挂起,停止甚至是编写一些程序:  shell还是 ...

  3. Redis常用命令汇总

    Redis HGET获取与字段中存储的键哈希相关联的值D:\web\JH2016\RedisV3.2\2MasterOpenAPI-15698\redis-cli.exe -h 127.0.0.1 - ...

  4. 【教程】模拟登陆百度之Java代码版

    [背景] 之前已经写了教程,分析模拟登陆百度的逻辑: [教程]手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程 然后又去用不同的语言: Python的: [教程]模 ...

  5. easyui layout 收缩的bug

    easyui layout提供collapse方法折叠指定的 panel,'region' 参数可能的值是:'north'.'south'.'east'.'west',但是在 IE6的环境下,调用这个 ...

  6. poj2001 Shortest Prefixes (trie)

    读入建立一棵字母树,并且每到一个节点就增加这个节点的覆盖数. 然后再重新扫一遍,一旦碰到某个覆盖数为1就是这个单词的最短前缀了. 不知为何下面的程序一直有bug……不知是读入的问题? type nod ...

  7. 深度学习算法实践15---堆叠去噪自动编码机(SdA)原理及实现

    我们讨论了去噪自动编码机(dA),并讨论了Theano框架实现的细节.在本节中,我们将讨论去噪自动编码机(dA)的主要应用,即组成堆叠自动编码机(SdA),我们将以MNIST手写字母识别为例,用堆叠自 ...

  8. python itchat 爬取微信好友信息

    原文链接:https://mp.weixin.qq.com/s/4EXgR4GkriTnAzVxluJxmg 「itchat」一个开源的微信个人接口,今天我们就用itchat爬取微信好友信息,无图言虚 ...

  9. FZU软工第十一次作业-软件产品案例分析

    目录 前言: 第一部分.调研,评测: 1.1.初次感觉: 1.2.企业号bug: 1.3.你觉得为什么这个产品组的人没有发现这些bug: 1.4.假设你们团队需要开发这套系统,需要注意哪些方面: 2. ...

  10. .Net异步编程 z

    1. 引言 最近在学习Abp框架,发现Abp框架的很多Api都提供了同步异步两种写法.异步编程说起来,大家可能都会说异步编程性能好.但好在哪里,引入了什么问题,以及如何使用,想必也未必能答的上来. 自 ...