这是Netty的一个重要优化,为了解决I/O操作速度影响性能,采用零拷贝的技术

1、零拷贝

sendFile(Kafka也是用该技术优化性能):发送文件描述符,如果硬件支持,图二的文件缓冲区和Socket缓冲区可以共享,只需要两次DMA拷贝就可以

1.1 源码DefaultFileRegion.transferto()方法

我们看一下源码,Netty的文件传输零拷贝方法就是该方法,方法下面圈出来的一行是(FileChannel)file.tranferto (java API)就是 sendFile,采用零拷贝技术,省去了从用户空间中转的过程(见上图)

1.2 我们通过一个案例看一下零拷贝和普通拷贝的区别

我们尝试传输一个大小230M的文件,来看下普通传输和零拷贝性能的差异。

下面我们创建一个普通ServerSocket服务端,一个传统的文件传输方式的TranditionClient,一个零拷贝传输方式的NewIOClient,两者都想服务端传输同一个文件,比较传输时间。代码如下:

1.2.1 首先写一个Server

public class Server {
    public static void main(String[] args) throws Exception {
        //创建serversocket 对象--8088服务
        ServerSocket serverSocket = new ServerSocket(8088);
        //循环监听连接
        while (true){
            Socket socket = serverSocket.accept();//客户端发起网络请求---连接
            //创建输⼊流对象
            DataInputStream dataInputStream = new
                    DataInputStream(socket.getInputStream());
            int byteCount=0;
            try{
                byte[] bytes = new byte[1024];        //创建缓冲区字节数组
                while(true){
                    int readCount = dataInputStream.read(bytes, 0,
                            bytes.length);
                    byteCount=byteCount+readCount;
                    if(readCount==-1){
                        System.out.println("服务端接受:"+byteCount+"字节");
                        break;
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

1.2.2 普通文件传输方式

public class TranditionClient {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost",8088);
        // 文件大小230M
        String fileName = "/Users/bing/Downloads/Joplin-3.0.14-arm64.DMG";
        //创建输⼊流对象
        InputStream inputStream = new FileInputStream(fileName);
        //创建输出流
        DataOutputStream dataOutputStream = new
                DataOutputStream(socket.getOutputStream());
        byte[] buffer = new byte[1024];
        long readCount = 0;
        long total=0;
        long startTime = System.currentTimeMillis();
        //TODO 这里要发生2次copy
        while ((readCount=inputStream.read(buffer))>=0){
            total+=readCount;
            //TODO 网络发送:这里要发生2次copy
            dataOutputStream.write(buffer);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("发送总字节数:"+total+",耗时:"+(endTime-startTime)+" ms");
        //释放资源
        dataOutputStream.close();
        socket.close();
        inputStream.close();
    }
}

1.2.3 零拷贝传输方式

public class NewIOClient {
    public static void main(String[] args) throws Exception {
        //socket套接字
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost",8088));
        socketChannel.configureBlocking(true);
        //文件 大小230M
        String fileName = "/Users/bing/Downloads/Joplin-3.0.14-arm64.DMG";
        //FileChannel 文件读写、映射和操作的通道
        FileChannel fileChannel = new FileInputStream(fileName).getChannel();
        long startTime = System.currentTimeMillis();
        //transferTo⽅法⽤到了零拷⻉,底层是sendfile,这里只需要发生2次copy和2次上下文切换
        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);

        long endTime = System.currentTimeMillis();
        System.out.println("发送总字节数:"+transferCount+"耗时:"+(endTime-startTime)+" ms");
        //释放资源
        fileChannel.close();
        socketChannel.close();
    }
}

1.2.4 结果

通过多次测试得到结果,零拷贝方式比传统方式快很多,如下:

1)传统传输方式,耗时530ms左右:

2)零拷贝方式,耗时150ms左右:

总结

零拷贝同样是Kafka采用的优化I/O操作的方案,这是对I/O操作优化的一个常用的手段,只有支持零拷贝的操作系统,都可以适应零拷贝的优化方案

参考

Netty与网络编程

一文彻底弄懂零拷贝原理


杜若
70 声望3 粉丝