磁盘I/O基础扫盲:开发人员不得不了解的IO那些事

磁盘I/O基础扫盲:开发人员不得不了解的IO那些事I O 即输入 input 和输出 output 也可以理解为读 Read 和写 Write I O 模式可以划分为本地 IO 模型 内存 磁盘 和网络 IO 模型 I O 关系到用户空间和内核空间

大家好,欢迎来到IT知识分享网。

文章目录

一、深入理解IO

1、什么是操作系统IO

read和write操作,都只能在内核空间里执行,磁盘IO和网络IO请求都是先放在内核空间,然后加载到用户态内存的数据。

2、IO读写性能差距实操

# dd用于指定大小的块,拷贝一个文件,并在拷贝的同时进行转换。 # if=文件名:输入文件名,缺省为标准输入,即指定源文件 # of=文件名:输出文件名,缺省为标准输出,即指定目的文件 # bs=bytes:同时设置读入/输出的块大小为bytes个字节 # count=blocks:仅拷贝blocks个块,块大小等于指定的字节数 # bs是每次读或写的大小,即一个块的大小,count是读写块的数量。 # 释放所有缓存 echo 3 > /proc/sys/vm/drop_caches # 操作一 dd if=/dev/zero of=xdclass_testio1 bs=1M count=1024 echo 3 > /proc/sys/vm/drop_caches # 操作二 dd if=/dev/zero of=xdclass_testio2 bs=1M count=1024 oflag=direct echo 3 > /proc/sys/vm/drop_caches # 操作三 dd if=/dev/zero of=xdclass_testio3 bs=1M count=1024 oflag=sync 

物理磁盘也会带有缓存disk cache,用于提高I/O速度,一般磁盘中带有电容,断电也能把缓存数据刷写到磁盘中。

3、什么是文件系统

(1)简介

在Linux系统中,一切皆是文件,文件系统管理磁盘上的全部文件,文件管理组织方式多种多样,所以文件系统存在多样化。

系统把文件持久化存储在磁盘上,文件系统就会实现文件数据的查询和存储
文件系统是管理数据,而存储数据的物理设备有硬盘、U盘、SD卡、网络存储设备等。
不同的存储设备其物理结构不同,不同的物理结构就需要不同的文件系统去管理。
比如说,Windows有FAT12、FAT16、FAT32、NTFS、exFAT等文件系统;Linux有Ext2、Ext3、Ext4、tmpfs、NFS等文件系统。


# 查询系统用了哪些文件系统  [root@localhost test]# df -h -T Filesystem Type Size Used Avail Use% Mounted on devtmpfs devtmpfs 1.9G 0 1.9G 0% /dev tmpfs tmpfs 1.9G 0 1.9G 0% /dev/shm tmpfs tmpfs 1.9G 8.7M 1.9G 1% /run tmpfs tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup /dev/sda1 xfs 40G 22G 19G 54% / tmpfs tmpfs 379M 0 379M 0% /run/user/0 overlay overlay 40G 22G 19G 54% /var/lib/docker/overlay2/6cce6b0ef229cb98e74acffb11333b4f4fd26c298a53e8cc714e2d55/merged overlay overlay 40G 22G 19G 54% /var/lib/docker/overlay2/ae42c3f3e656cc8e13fef0edf4ac89fe2b8d1afe34fd/merged 

(2)索引节点和目录项

(3)什么是虚拟文件系统VFS(Virtual File System)

(4)Linux的IO存储栈

https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram
在这里插入图片描述

总体来说,这就是操作系统的多级缓存和数据的可用性。操作系统也是程序,靠着多线程、异步、多级缓存实现高性能。
我们日常的业务开发,也可以借鉴这种思想。

二、深入理解磁盘

1、机械硬盘

(1)结构

在这里插入图片描述
机械硬盘(HDD)组成结构很多,重点关注磁盘、磁头臂、磁头,每个硬盘的正反两面都有对应的磁头。

(2)如何读取数据

磁臂摆动+盘片转动(耗时大所以导致慢,随机在硬盘上找一个数据,需要8-14ms),定位到目标扇区读取数据
磁臂在一定范围内摆动,来找到目标扇区,靠磁头把某个扇区的数据传输到总线上;
磁臂摆动范围有限,触达不到比较远的扇区,靠转轴来转动盘片,比如磁盘转速有7200转/分,1秒就是120圈;
常规1秒可以做100次随机IO,所以高并发业务单靠磁盘是扛不住的,基本都要结合缓存;


机械硬盘想要优化,就不能用随机IO,要用顺序IO,节省大量的物理耗时,比如Kafka、RockerMQ都是使用顺序IO。

2、磁盘读写常见指标

(1)IOPS(Input/Output Operations per Second)

指每秒能处理的I/O个数,表示块存储处理读写(输出/输入)的能力,单位为次,有顺序IOPS和随机IOPS。

(2)吞吐量/带宽(Throughput)

(3)访问时延(Latency)

(4)容量(Capacity)

是指存储空间大小,单位为TiB、GiB、Mib、Kib,块存储容量按照二进制单位计算:

# 查看容量 [root@localhost test]# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 1.9G 0 1.9G 0% /dev tmpfs 1.9G 0 1.9G 0% /dev/shm tmpfs 1.9G 8.7M 1.9G 1% /run tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup /dev/sda1 40G 22G 19G 54% / tmpfs 379M 0 379M 0% /run/user/0 

(5)使用率(Utilization)

(6)IO等待队列长度(Queue Length)

表示等待处理的I/O请求的数目,如果I/O请求压力持续超出磁盘处理能力,就会增大队列长度。

(7)饱和度

3、磁盘IOPS性能测试

(1)安装fio

yum install -y fio 

(2)使用

参数 说明
filename 待测试的文件或块设备
如果是文件,则是测试文件系统的性能;例:-filename=/work/fstest/fio.img
如果是块设备,则是测试裸设备的性能;例:-filename=/dev/vda1(容易损坏磁盘)

ioengin IO引擎fio支持多种引擎,例如:cpuio、mmap、sync、psync、filecreate、libaio等
常用libaio是Linux异步读写IO(Linuxnative asynchronous I/O)
iodepth 表示使用AIO时,同时发出I/O数的上限为128
direct 是否采用直接IO(direct IO)方式进行读写
如果采用直接IO,则取值-direct=1,否则取值-direct=0
一般是用直接IO写此时,测试结果更加真实

rw 读写模式
read:顺序读测试,使用方式-rw=read
write:顺序写测试,使用方式-rw=write
randread:随机读测试,使用方式-rw=randread
randwrite:随机写测试,使用方式-rw=randwrite
randrw:随机读写,-rw=randrw;默认比率为5:5




numjobs 测试进程的并发数,比如-numjobs=16
bs 单次IO的大小,比如-bs=4k
size 测试文件的大小,比如-size=1G
sync 设置同步模式,同步-sync=1,异步-sync=0
runtime 设置测试运行的时间,单位秒,比如-runtime=100
group_reporting 结果把多线程汇总输出
# 随机读 fio -direct=1 -iodepth=128 -rw=randread -ioengine=libaio -bs=4k -size=1G -runtime=10 -filename=iotest1 -name=iotest1 # 随机写 fio -direct=1 -iodepth=128 -rw=randwrite -ioengine=libaio -bs=4k -size=1G -runtime=10 -filename=iotest2 -name=iotest2 # 顺序读 fio -direct=1 -iodepth=128 -rw=read -ioengine=libaio -bs=4k -size=1G -runtime=10 -filename=iotest3 -name=iotest3 # 顺序写 fio -direct=1 -iodepth=128 -rw=write -ioengine=libaio -bs=4k -size=1G -runtime=10 -filename=iotest4 -name=iotest4 # 随机读 fio -direct=1 -iodepth=128 -rw=randrw -ioengine=libaio -bs=4k -size=1G -runtime=10 -filename=iotest5 -name=iotest5 

在这里插入图片描述

4、固态硬盘SSD

(1)结构

固态硬盘由固态电子元器组成,没有盘片、磁臂等机械部件,不需要磁道寻址,靠电容存储数据。

(2)磁盘数据的擦写

5、磁盘分区

(1)概念

计算机中存放信息的主要存储设备就是硬盘,但是硬盘不能直接使用,必须对硬盘进行分割成一块块的硬盘区域就是磁盘分区。

(2)硬盘分区类型

不同类型磁盘支持分区的数量有限制。

(3)容量

(4)Linux系统下磁盘分区设备名称

设备 介绍 设备在Linux中的文件名
IDE硬盘Hard Disk Integrated Drive Electronics电子集成驱动器
/dev/hd是IDE接口硬盘分区,一般用于普通桌面和服务器
/dev/hd[字母递增][数字递增]
SCSI光盘Solid Disk Small Computer System Interface 小型计算机系统接口
/dev/sd是SCSI接口硬盘分区,一般用于服务器
/dev/sd[字母递增][数字递增]
virtio虚拟磁盘Virtual Disk /dev/vd虚拟磁盘分区,一般用于在虚拟机上扩展存储空间 /dev/vd[字母递增][数字递增]

(5)管理磁盘分区

# 分区管理 [root@localhost test]# fdisk -l # 只有一块磁盘/dev/sda Disk /dev/sda: 42.9 GB,  bytes,  sectors Units = sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk label type: dos Disk identifier: 0x0009ef1a # 只有一个分区/dev/sda1 Device Boot Start End Blocks Id System /dev/sda1 * 2048   83 Linux 
# 查看容量、占用空间、剩余空间 [root@localhost test]# df -h -T Filesystem Type Size Used Avail Use% Mounted on devtmpfs devtmpfs 1.9G 0 1.9G 0% /dev tmpfs tmpfs 1.9G 0 1.9G 0% /dev/shm tmpfs tmpfs 1.9G 8.7M 1.9G 1% /run tmpfs tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup /dev/sda1 xfs 40G 23G 18G 57% / tmpfs tmpfs 379M 0 379M 0% /run/user/0 

6、磁盘高可用:磁盘冗余阵列

(1)简介

简单来说,就是把相同的数据存储在多个硬盘的不同的地方,储存冗余数据增加了容错性

根据性能、容量、可靠性,有多个级别,比如RAID0、RAID1、RAID5、RAID10。

(2)RAID0磁盘阵列

至少需要两块硬盘,磁盘越多,读写速度越快,读写速度约等于一个磁盘的吞吐量 * 磁盘数,没有冗余。
这种方案磁盘利用率100%,安全性最低,一块硬盘出现故障就会导致数据损坏。
读写性能好。
(类似redis、mongodb的数据分片存储)
在这里插入图片描述



(3)RAID1镜像阵列

(4)RAID5条带阵列

(5)RAID10、RAID50

7、磁盘IO性能分析

(1)iostat

sysstat提供了Linux性能监控的工具集,包括iostat、mpstat、pidstat、sar等。

iostat查看系统综合的磁盘IO情况

# -c 仅显示CPU状态统计信息 # -d 仅显示磁盘统计信息 # -k 或 -m 以Kb或Mb为单位显示,常用-h可读性高 # -p 指定显示IO的设备,ALL表示显示所有 # -x 显示详细信息 

在这里插入图片描述

字段 说明
【重要】r/s 每秒发送给磁盘的读请求次数,r/s+w/s 是磁盘 IOPS
【重要】w/s 每秒发送给磁盘的写请求次数,r/s+w/s 是磁盘IOPS
【重要】rkB/s 每秒从磁盘读取的数据量,rkB/s+wkB/s 是吞吐量
【重要】wkB/s 每秒向磁盘写入的数据量,rkB/s+wkB/s 是吞吐量
【重要】r_await 读请求处理完成等待时间,包括在队列中的等待时间和设备实际处理时间
rawait+w_await,是RT响应时间
【重要】w_await 写请求处理完成等待时间,包括在队列中的等待时间和设备实际处理时间
r_await+w_await,是RT响应时间
【重要】aqu-sz 平均请求队列长度
rareq-Sz 平均读请求大小
wareg-Sz 平均写请求大小
【重要】%util 磁盘处理I/O的时间百分比,表示的是磁盘的忙碌情况;如果>80%就是磁盘可能处于忙碌状态
-秒中有百分之多少的时间用于I/O操作,或者说一秒中有多少时间I/O队列是非空的

(2)iotop

三、深入理解操作系统IO底层

1、DMA(Direct Memory Access)

(1)应用程序从磁盘读写数据的时序图(未用DMA技术)

(2)使用DMA(Direct Memory Access)直接内存访问

在这里插入图片描述
在这里插入图片描述

(3)DMA技术里面的损耗

所以,内核缓冲区到用户缓冲区之间的拷贝工作仍然由CPU负责

DMA技术虽然能提高一部分性能,但是仍然有一些不必要的资源损耗,其中包括CPU的用户态和内核态的切换、CPU内存拷贝的消耗

2、零拷贝

(1)概念

零拷贝旨在减少不必要的内核缓冲区跟用户缓冲区之间的拷贝工作,从而减少CPU的开销和减少了kernel和user模式的上下文切换,提升性能。

从磁盘中读取文件通过网络发送出去,只需要拷贝2/3次和2/4的内核态和用户态的切换即可

(2)mmap实现

mmap+write是ZeroCopy的实现方式之一。

操作系统都使用虚拟内存,虚拟地址通过多级页表来映射物理地址,多个虚拟内存可以指向同一个物理地址,虚拟内存的总空间远大于物理内存空间。
如果把内核空间和用户空间的虚拟地址映射到同一个物理地址,就不需要来回复制数据了。
mmap系统调用函数会直接把内核缓冲区里的数据映射到用户空间,这样内核空间和用户空间就不需要进行数据拷贝操作,节省了CPU开销。

相关函数(C):mmap()读取,write()写出。

还是以应用程序从磁盘读取文件到发送到网络的流程为例,步骤:
1、应用程序先调用mmap()方法,将数据从磁盘拷贝到内核缓冲区,返回结束(DMA负责);
2、再调用write(),内核缓冲区的数据直接拷贝到内核socket buffer(CPU负责);
3、然后把内核缓冲区的Socket Buffer直接拷贝给Socket协议栈,即网卡设备中,返回结束(DMA负责)。
在这里插入图片描述



(3)sendfile实现

sendfile是ZeroCopy的另一种实现方式。

Linux kernal 2.1新增了一个发送文件的系统调用函数sendfile()
替代read()和write()两个系统调用,减少一次系统调用,即减少2次CPU上下文切换的开销。

调用sendfile(),从磁盘读取数据到内核缓冲区,然后直接把内核缓冲区的数据拷贝到socket buffer缓冲区里,再把内核缓冲区的Socket Buffer直接拷贝给Socket协议栈,即网卡设备中(DMA负责)。

相关函数(C):sendfile()

还是以应用程序从磁盘读取文件到发送到网络的流程为例,步骤:
1、应用程序先调用sendfile()方法,将数据从磁盘拷贝到内核缓冲区(DMA负责);
2、然后把内核缓冲区的数据直接拷贝到内核socket buffer(CPU负责);
3、然后把内核缓冲区的Socket Buffer直接拷贝给Socket协议栈,即网卡设备中,返回结束(DMA负责)。
在这里插入图片描述
没用零拷贝时,发生4次CPU上下文切换和4次数据拷贝。
使用sendfile(),CPU用户态和内核态上下文切换是2次,3次数据拷贝(2次DMA拷贝,1次CPU拷贝)





(4)改进的sendfile

linux2.4+版本之后改进了sendfile,利用DMA Gather(带有收集功能的DMA),变成了真正的零拷贝(没有CPU Copy)。

还是以应用程序从磁盘读取文件到发送到网络的流程为例,步骤:
1、应用程序先调用sendfile()方法,将数据从磁盘拷贝到内核缓冲区(DMA负责);
2、把内存地址、偏移量的缓冲区fd描述符 拷贝到Socket Buffer中去,(拷贝很少的数据,可忽略,本质和虚拟内存的解决方法思路一样,就是内存地址的记录);
3、然后把内核缓冲区的Socket Buffer直接拷贝给Socket协议栈,即网卡设备中,返回结束(DMA负责)。


在这里插入图片描述

(5)splice

Linux 从 2.6.17 支持 splice。

数据从磁盘读取到 OS 内核缓冲区后,在内核缓冲区直接可将其转成内核空间其他数据buffer,而不需要拷贝到用户空间。

3、总结

(1)零拷贝的目标

(2)零拷贝方式对比

四、Java中的IO操作

1、Java零拷贝

(1)概述

注意,API是否使用零拷贝依赖于底层的系统实现,如果系统本身不支持零拷贝,即使调用了相关API也不会进行零拷贝。

(2)普通文件读写方式

使用普通的java.io中的输入输出流,并不会使用零拷贝:

import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class Test { 
    public static void main(String[] args) { 
    String inputFilePath = ""; String outputFilePath = ""; long start = System.currentTimeMillis(); try(InputStream fis = new FileInputStream(inputFilePath); FileOutputStream fos = new FileOutputStream(outputFilePath)) { 
    byte[] buf = new byte[1024]; int len = 0; while ((len = fis.read(buf)) != -1) { 
    fos.write(buf); } } catch (IOException e) { 
    e.printStackTrace(); } System.out.println("cost:" + (System.currentTimeMillis() - start)); } } 

(3)认识FileChannel

Java NIO提供了一个FileChannel文件通道,存在于java.nio.channels.FileChannel中,是一个连接到文件的通道,可以通过文件通道读写文件,常用于高效的网络/文件的数据传输和大文件拷贝

应用程序使用FileChannel写完以后,数据是在PageCache上的,操作系统不定时的把PageCache的数据写入到磁盘,所以可以使用channel.force(true)把文件相关的数据强制刷入到磁盘,避免宕机数据丢失。

使用之前必须先打开它,但是无法直接new一个FileChannel,常规通过使用一个InputStream、OutputStreamRandomAccessFile来获取一个FileChannel实例。

RandomAccessFile randomAccessFile = new RandomAccessFile("/test.txt", "rw"); FileChannel channel = randomAccessFile.getChannel(); 

(4)Java – mmap的实现

import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class MmpTest { 
    / MapMode :只读、读写、可读可写 position文件开始位置 size:映射文件区域大小 public abstract MappedByteBuffer map(MapMode mode, long position, long size) throws IOException; */ public static void main(String[] args) { 
    try { 
    // 获取文件 FileChannel readChannel = FileChannel.open(Paths.get("/test/1.txt"), StandardOpenOption.READ); MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size()); FileChannel writeChannel = FileChannel.open(Paths.get("/test/3.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); //数据传输 writeChannel.write(data); readChannel.close(); writeChannel.close(); }catch (Exception e){ 
    System.out.println(e.getMessage()); } } } 

(5)Java – sendfile实现

对应方法:fileChannel.transferTo(long position, long count, WritableByteChannel target)
将字节从此通道的文件传输到给定的可写入字节通道,返回值为真实拷贝的size,最大拷贝2G,超出2G的部分将丢弃。

fileChannel.transferFrom(ReadableByteChannel src, long position, long count),与transferTo类似,To和From见名思意。

这两个方法允许将一个通道连接到另一个通道,不需要在用户态和内核态来回复制,同时通道间的内核态数据也无需复制
transferTo()只有源为FileChannel才支持transfer这种高效的复制方式,其他如SocketChannel都不支持transfer模式
一般可以做FileChannel ->FileChannel和FileChannel->SocketChannel的transfer零拷贝,至于SocketChannel->FileChannel是不支持的

import java.nio.channels.FileChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class SendfileTest { 
    public static void main(String[] args) { 
    try { 
    FileChannel readChannel = FileChannel.open(Paths.get("C://test/1.txt"), StandardOpenOption.READ); long len = readChannel.size(); long position = readChannel.position(); FileChannel writeChannel = FileChannel.open(Paths.get("E://test1/3.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); //数据传输 //开始发送数据:在Java中使用零拷贝技术调用transferTo方法,这个方法底层使用了零拷贝技术 // 在Linux系统下 使用transferTo 方法,没有文件大小限制,可以将文件调用一次transferTo方法即可传输完成 //但是在Windows系统下调用一次transferTo 方法,最多只能发送 8m 的数据,所以需要将文件进行分段传输 // transferTo 参数介绍: // 第一个参数:从文件的哪里开始读取 // 第二个参数:读取多少字节 // 第三个参数:将读取的字节,放入需要写入的Channel中 // 返回:实际已传输的字节数,可能为零 readChannel.transferTo(position, len, writeChannel); readChannel.close(); writeChannel.close(); } catch (Exception e) { 
    System.out.println(e.getMessage()); } } } 

2、不同Java的IO性能对比

(1)代码实践

import java.io.*; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; public class IOTest { 
    / * 随时清除缓存:echo 3 > /proc/sys/vm/drop_caches * 启动参数:java IOTest.java "io" "/test.zip" "/test2.zip" */ public static void main(String[] args) { 
    String type = args[0]; String inputFilePathStr = args[1]; String outputFilePathStr = args[2]; long start = System.currentTimeMillis(); switch (type) { 
    case "io": // 1.7G文件,耗时 非常慢 inputStreamCopyFile(inputFilePathStr, outputFilePathStr); break; case "buffer": // 1.7G文件,耗时 81992 bufferStreamCopyFile(inputFilePathStr, outputFilePathStr); break; case "mmap": // 1.7G文件,耗时 30216 mmapStreamCopyFile(inputFilePathStr, outputFilePathStr); break; case "sendfile": // 1.7G文件,耗时 29422 sendfileStreamCopyFile(inputFilePathStr, outputFilePathStr); break; } // 用时 System.out.println(System.currentTimeMillis() - start); } private static void sendfileStreamCopyFile(String inputFilePathStr, String outputFilePathStr) { 
    try(FileChannel inputChannel = new FileInputStream(inputFilePathStr).getChannel(); FileChannel outputChannel = new FileOutputStream(outputFilePathStr).getChannel()) { 
    // 小于2G的文件,可以直接读,超过的部分会被丢弃,也可以用transferTo outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); // 大于2G的文件,多次读写 // long size = inputChannel.size(); // for (long left = size; left > 0;) { 
    // long transferSize = inputChannel.transferTo(size - left, left, outputChannel); // left = left - transferSize; // } } catch (IOException e) { 
    e.printStackTrace(); } } private static void mmapStreamCopyFile(String inputFilePathStr, String outputFilePathStr) { 
    try(FileChannel inputChannel = new FileInputStream(inputFilePathStr).getChannel(); FileChannel outputChannel = new FileOutputStream(outputFilePathStr).getChannel()) { 
    long size = inputChannel.size(); MappedByteBuffer mbbi = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, size); MappedByteBuffer mbbo = outputChannel.map(FileChannel.MapMode.READ_WRITE, 0, size); for (int i = 0; i < size; i++) { 
    byte b = mbbi.get(i); mbbo.put(i, b); } } catch (IOException e) { 
    e.printStackTrace(); } } private static void bufferStreamCopyFile(String inputFilePathStr, String outputFilePathStr) { 
    try(BufferedInputStream fis = new BufferedInputStream(new FileInputStream(inputFilePathStr)); BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(outputFilePathStr))) { 
    byte[] buf = new byte[1]; int len = 0; while ((len = fis.read(buf)) != -1) { 
    fos.write(buf); } } catch (IOException e) { 
    e.printStackTrace(); } } private static void inputStreamCopyFile(String inputFilePathStr, String outputFilePathStr) { 
    try(InputStream fis = new FileInputStream(inputFilePathStr); FileOutputStream fos = new FileOutputStream(outputFilePathStr)) { 
    byte[] buf = new byte[1]; int len = 0; while ((len = fis.read(buf)) != -1) { 
    fos.write(buf); } } catch (IOException e) { 
    e.printStackTrace(); } } } 

(2)各种IO性能差异的原因

mmap和sendfile性能差别并不大,但是都比普通的IO要快很多。

3、不同中间件的零拷贝

(1)Nginx

(2)RocketMQ

RocketMQ主要是mmap,也有小部分使用sendfile。

RocketMQ在消息存盘和网络发送使用mmap,单个CommitLog文件大小默认1GB,要在用户进程内处理数据,然后再发送出去的话,用户空间和内核空间的数据传输就是不可避免的

(3)Kafka

Kafka主要是sendfile,也有mmap。

Kafka在客户端和broker进行数据传输时,broker使用sendfile系统调用,类似【FileChannel.transferTo】API,将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络发送,即Linux的sendfile。

(4)Netty

Netty 的零拷贝主要包含三个方面:

1、在网络通信上,Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

2、在缓存操作上,Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免了各个 ByteBuf 之间的拷贝。通过 wrap 操作,我们可以将 byte[]数组、ByteBuf、 ByteBuffer 等包装成一个 Netty ByteBuf 对象,进而避免了拷贝操作。ByteBuf支持slice 操作,因此可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf,避免了内存的拷贝。

3、在文件传输上,Netty 的通过 FileRegion 包装的 FileChannel.tranferTo 实现文件传输,它可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121821.html

(0)
上一篇 2025-10-21 13:00
下一篇 2025-10-21 13:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信