大家好,欢迎来到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、OutputStream
或RandomAccessFile
来获取一个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