浅析MMAP零拷贝在RocketMQ中的运用

什么是零拷贝?

零拷贝(英语: Zero-copy)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。

零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。

零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销。

可以看出没有说不需要拷贝,只是说减少冗余不必要的拷贝。

下面这些组件、框架中均使用了零拷贝技术:Kafka、Netty、Rocketmq、Nginx、Apache。

传统数据传送机制

比如:读取文件,再用socket发送出去,实际经过四次copy。

伪码实现如下:

buffer = File.read()
Socket.send(buffer)

四次拷贝的过程:

  • 第一次:将磁盘文件,读取到操作系统内核缓冲区;
  • 第二次:将内核缓冲区的数据,copy到应用程序的buffer;
  • 第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区);
  • 第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。

分析上述的过程,虽然引入DMA来接管CPU的中断请求,但四次copy是存在“不必要的拷贝”的。实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。

显然,第二次和第三次数据copy其实在这种场景下没有什么帮助反而带来开销(DMA拷贝速度一般比CPU拷贝速度快一个数量级),这也正是零拷贝出现的背景和意义。

打个比喻:200M的数据,读取文件,再用socket发送出去,实际经过四次copy(2次cpu拷贝每次100ms ,2次DMA拷贝每次10ms),传统网络传输的话:合计耗时将有220ms。

同时,read和send都属于系统调用,每次调用都牵涉到两次上下文切换:

总结下,传统的数据传送所消耗的成本:4次拷贝,4次上下文切换。4次拷贝,其中两次是DMA copy,两次是CPU copy。

mmap内存映射

mmap可以将硬盘上文件的位置和应用程序缓冲区(application buffers)进行映射(建立一种一一对应关系),将文件直接映射到用户空间,所以实际文件读取时根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了一次数据拷贝,不再有文件内容从硬盘拷贝到内核空间的一个缓冲区。

mmap内存映射将会经历:3次拷贝: 1次cpu copy,2次DMA copy;

打个比喻:200M的数据,读取文件,再用socket发送出去,如果是使用MMAP实际经过三次copy(1次cpu拷贝每次100ms ,2次DMA拷贝每次10ms),合计只需要120ms。

从数据拷贝的角度上来看,就比传统的网络传输,性能提升了近一倍。

mmap()是在<sys/mman.h>中定义的一个函数,此函数的作用是创建一个新的虚拟内存区域,并将指定的对象映射到此区域。mmap其实就是通过内存映射的机制来进行文件操作。

mmap的使用:

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

public class MmapDemo {
    public static void main(String[] args) throws IOException {

        File f = new File("/root/map.txt");
        RandomAccessFile randomAccessFile = new RandomAccessFile(f, "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);
        mappedByteBuffer.put("hello".getBytes(StandardCharsets.UTF_8));
        mappedByteBuffer.flip();
        byte[] bytes = new byte[5];
        mappedByteBuffer.get(bytes, 0, 5);
        System.out.println("content:" + new String(bytes, StandardCharsets.UTF_8));
    }
}

使用命令:

strace -ff -o out java MmapDemo

追踪MmapDemo程序产生的系统调用:

openat(AT_FDCWD, "/root/map.txt", O_RDWR|O_CREAT, 0666) = 5
... ...
fstat(5, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
ftruncate(5, 4096)                      = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, 5, 0) = 0x7f9e1c000000

发现底层调用了mmap系统调用,后续并没有产生write等系统调用,说明数据的读写直接发生在了应用态。

FileChannal的使用

另外RocketMQ在源码中还使用了FileChannel来做文件的写入。

package com.morris.rocketmq.mmap;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

public class FileChannelDemo {
    public static void main(String[] args) throws IOException {
        File f = new File("d:\\map.txt");
        RandomAccessFile randomAccessFile = new RandomAccessFile(f, "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        byteBuffer.put("hello rocketmq".getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();
        fileChannel.write(byteBuffer);
        fileChannel.close();
    }
}

为什么RocketMQ会同时使用FileChannel和MappedByteBuffer在做文件的写入,读取却只用MappedByteBuffer?

RocketMQ中MMAP运用

如果按照传统的方式进行数据传送,那肯定性能上不去,作为MQ也是这样,尤其是RocketMQ,要满足一个高并发的消息中间件,一定要进行优化。所以RocketMQ使用的是MMAP。

RocketMQ源码中,使用MappedFile这个类进行MMAP的映射。

这里需要注意的是,采用MappedByteBuffer这种内存映射的方式一次只能映射2G的文件至用户态的虚拟内存,这也是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因了。

为什么是2G?

sun.nio.ch.FileChannelImpl#map

public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
    if (size > Integer.MAX_VALUE)
        throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");

虽然size是long类型,但是限制了size只能是int的最大值,也就是2G。

mmap在源码MappedFile中的使用:

public MappedFile(final String fileName, final int fileSize) throws IOException {
	init(fileName, fileSize);
}

private void init(final String fileName, final int fileSize) throws IOException {
	this.fileName = fileName;
	this.fileSize = fileSize;
	this.file = new File(fileName);
	this.fileFromOffset = Long.parseLong(this.file.getName());
	boolean ok = false;

	ensureDirOK(this.file.getParent());

	try {
		this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
		this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);
		TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);
		TOTAL_MAPPED_FILES.incrementAndGet();
		ok = true;
	} catch (FileNotFoundException e) {
		log.error("Failed to create file " + this.fileName, e);
		throw e;
	} catch (IOException e) {
		log.error("Failed to map file " + this.fileName, e);
		throw e;
	} finally {
		if (!ok && this.fileChannel != null) {
			this.fileChannel.close();
		}
	}
}

到此这篇关于MMAP零拷贝在RocketMQ中的运用的文章就介绍到这了,更多相关MMAP RocketMQ运用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java 中的内存映射 mmap

    目录 1.mmap 基础概念 2.Java 中的 mmap 3.mmap 不是银弹 4.mmap vs FileChannel 4.1 pageCache 4.2 缺页中断 4.3 内存拷贝次数 4.4 用户态与内核态 5.mmap 细节补充 5.1 copy on write 模式 5.2 回收 mmap 内存 6.mmap 使用场景 6.1 mmap 缓存 6.2 小文件的读写 6.3 cpu 紧俏下的读写 6.4 特殊软硬件因素 1.mmap 基础概念 mmap 是一种内存映射文件的方法,

  • Java中EnumMap代替序数索引代码详解

    本文研究的主要是Java中EnumMap代替序数索引的相关内容,具体介绍如下. 学习笔记<Effective Java 中文版 第2版> 经常会碰到使用Enum的ordinal方法来索引枚举类型. public class Herb { public enum Type { ANNUAL, PERENNIAL, BIENNIAL }; private final String name; private final Type type; Herb(String name, Type type)

  • 浅析MMAP零拷贝在RocketMQ中的运用

    什么是零拷贝? 零拷贝(英语: Zero-copy)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域.这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽. 零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率. 零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销. 可以看出没有说不需要拷贝,只是说减少冗余不必要的拷贝. 下面这些组件.框架中均使用了零拷贝技术:Kafk

  • 浅析Linux中的零拷贝技术的使用

    本文探讨Linux中主要的几种零拷贝技术以及零拷贝技术适用的场景.为了迅速建立起零拷贝的概念,我们拿一个常用的场景进行引入: 引文## 在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本功能.这时候服务端的任务是:将服务端主机磁盘中的文件不做修改地从已连接的socket发出去,我们通常用下面的代码完成: while((n = read(diskfd, buf, BUF_SIZE)) > 0) write(sockfd, buf , n); 基本操作就是循环的从磁盘读入

  • 详细总结Java堆栈内存、堆外内存、零拷贝浅析与代码实现

    一.堆栈内存 堆栈内存,顾名思义,指的是堆内存以及栈内存,其中,堆内存是由Java GC进行管理的内存区域,而栈内存则是线程内存.关于栈内存,这里不去细说.以Hotspot为例,堆内存的简要结构如下图所示: 而堆栈的关系,我们可以通过一行简单的代码来理解: public static void main(String[] args) { Object o = new Object(); } 上述代码主要完成了两件事,new Object( ) 在堆上开辟了一块内存,也就是说,new Object

  • 看过就懂的java零拷贝及实现方式详解

    目录 前言 1.什么是零拷贝 2. 传统 IO 的执行流程 3. 零拷贝相关的知识点回顾 3.1 内核空间和用户空间 3.2 什么是用户态.内核态 3.3 什么是上下文切换 3.4 虚拟内存 3.5 DMA技术 4. 零拷贝实现的几种方式 4.1 mmap+write实现的零拷贝 4.2 sendfile实现的零拷贝 4.3 sendfile+DMA scatter/gather实现的零拷贝 5. java提供的零拷贝方式 5.1 Java NIO对mmap的支持 5.2 Java NIO对se

  • 一文彻底弄懂零拷贝原理以及java实现

    目录 零拷贝 传统I/O操作存在的性能问题 零拷贝技术原理 虚拟内存 mmap/write 方式 sendfile 方式 带有 scatter/gather 的 sendfile方式 splice 方式 总结 零拷贝 零拷贝(Zero-Copy)是一种 I/O 操作优化技术,可以快速高效地将数据从文件系统移动到网络接口,而不需要将其从内核空间复制到用户空间.其在 FTP 或者 HTTP 等协议中可以显著地提升性能.但是需要注意的是,并不是所有的操作系统都支持这一特性,目前只有在使用 NIO 和

  • Java图文并茂详解NIO与零拷贝

    目录 一.概念说明 1.传统IO 2.mmap 3.sendfile 4.mmap与sendfile 二.传统IO传输文件代码示例 1.服务端代码 2.客户端代码 3.控制台出输出 三.NIO传输文件代码示例 1.服务端代码 2.客户端代码 3.控制台出输出 四.总结 零拷贝指的是没有CPU拷贝,并不是不拷贝:减少上下文切换 一.概念说明 1.传统IO 需要4次拷贝,3次上下文切换 2.mmap mmap 通过内存映射,将文件映射到内存缓冲区,同时用户空间可以共享内存缓冲区的数据,减少内核空间到

  • 浅析jQuery 遍历函数,javascript中的each遍历

    jQuery 遍历函数 jQuery 遍历函数包括了用于筛选.查找和串联元素的方法. 函数 描述 .add() 将元素添加到匹配元素的集合中. .andSelf() 把堆栈中之前的元素集添加到当前集合中. .children() 获得匹配元素集合中每个元素的所有子元素. .closest() 从元素本身开始,逐级向上级元素匹配,并返回最先匹配的祖先元素. .contents() 获得匹配元素集合中每个元素的子元素,包括文本和注释节点. .each() 对 jQuery 对象进行迭代,为每个匹配元

  • 浅析SQL Server的嵌套存储过程中使用同名的临时表怪像

    SQL Server的嵌套存储过程,外层存储过程和内层存储过程(被嵌套调用的存储过程)中可以存在相同名称的本地临时表吗?如果可以的话,那么有没有什么问题或限制呢? 在嵌套存储过程中,调用的是外层存储过程的临时表还是自己定义的临时表呢? 是否类似高级语言的变量一样,本地临时表有没有"作用域"范围呢? 注意:也可以称呼为父存储过程和子存储过程,外层存储过程和内层存储过程.这些只是不同的称呼或叫法而已.我们这里统一使用外层存储过程和内层存储过程.后续文章部分不再述说. 我们先来看一个例子,如

  • 详解RocketMQ中的消费者启动与消费流程分析

    目录 一.简介 1.1 RocketMQ 简介 1.2 工作流程 二.消费者启动流程 2.1 实例化消费者 2.2 设置NameServer和订阅topic过程 2.2.1 添加tag 2.2.2 发送心跳至Broker 2.2.3上传过滤器类至FilterServer 2.3 注册回调实现类 2.4 消费者启动 三.pull/push 模式消费 3.1 pull模式-DefaultMQPullConsumer 3.2 push模式-DefaultMQPushConsumer 3.3 小结 四.

  • 浅析Python的web.py框架中url的设定方法

    网页中的数据在传递的时候有GET和POST两种方式,GET是以网址的形式传参数,在web.py中有着很好的匹配,如果我们配置以下的urls urls =( '/','index', '/weixin/(.*?)','WeixinInterface' ) 先不考虑/weixin/后面的东西,现在我们来写index的类 class index: def GET(self): i = web.input(name = 'kevinkelin',age = 100) return render.inde

随机推荐