文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-04-02 19:55

关注

什么是零拷贝?

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

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

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

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

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

传统数据传送机制

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

伪码实现如下:

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

四次拷贝的过程:

1657181537156.png

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

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

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

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

1657181556654.png

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

mmap内存映射

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

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

1657181598680.png

打个比喻: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运用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯