zoukankan      html  css  js  c++  java
  • Netty章节十七:Zero-copy,零拷贝

    Zero-copy,零拷贝

    程序示例

    普通IO拷贝

    客户端向服务器发送数据,服务器接收

    Server

    public class OldIOServer {
        public static void main(String[] args)throws Exception {
            ServerSocket serverSocket = new ServerSocket(8899);
            System.out.println("server start");
            while (true){
                //阻塞,等待连接到来
                Socket socket = serverSocket.accept();
    
                DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
    
                try {
                    byte[] bytes = new byte[4096];
    
                    while (true){
                        int readCount = dataInputStream.read(bytes);
                        if(readCount == -1){
                            break;
                        }
                    }
                }catch (Exception ex){
                    ex.printStackTrace();
                }
            }
        }
    }
    

    Client

    public class OldIOClient {
        public static void main(String[] args)throws Exception {
            Socket socket = new Socket("localhost", 8899);
    
            String filePath = "/home/tar.gz/pdf-books-master.zip";
            FileInputStream fileInputStream = new FileInputStream(filePath);
            DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
            byte[] bytes = new byte[4096];
            int readCount = 0;
            //读取数据的总数量
            int total = 0;
    
            //开始时间
            long startTime = System.currentTimeMillis();
    
            while ((readCount = fileInputStream.read(bytes)) >= 0){
                total += readCount;
                //向服务端写数据
                dataOutputStream.write(bytes);
            }
    
            System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));
    
            dataOutputStream.close();
            socket.close();
            fileInputStream.close();
        }
    }
    

    零拷贝方式

    客户端向服务器发送数据,服务器接收,零拷贝方式

    Server

    public class NewIOServer {
        public static void main(String[] args)throws Exception {
            //创建一个端口映射到8899端口
            InetSocketAddress address = new InetSocketAddress(8899);
    
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            ServerSocket serverSocket = serverSocketChannel.socket();
            //如果想绑定到某一个端口号上,但是那个端口号正处于超时状态,则不能绑定成功,此设置可以绑定成功
            serverSocket.setReuseAddress(true);
            //绑定端口
            serverSocket.bind(address);
    
            ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
    
            System.out.println("server start");
            while (true){
                SocketChannel socketChannel = serverSocketChannel.accept();
                //返回来的socketchannel默认就是阻塞的,如果要注册到selector上,必须设置成非阻塞的
                socketChannel.configureBlocking(true);
    
                int readCount = 0;
                while (readCount != -1){
                    try {
                        readCount = socketChannel.read(byteBuffer);
                    }catch (Exception ex){
                        ex.printStackTrace();
                    }
    
                    //重新读
                    byteBuffer.rewind();
                }
            }
        }
    }
    

    Client

    public class NewIOClient {
        public static void main(String[] args)throws Exception {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost",8899));
            socketChannel.configureBlocking(true);
    
            String filePath = "/home/tar.gz/pdf-books-master.zip";
            FileChannel channel = new FileInputStream(filePath).getChannel();
    
            //系统毫秒数
            long startTime = System.currentTimeMillis();
            //transferTo(传输的起始位置,传输的最大字节数,目标channel)
            long transferCount = channel.transferTo(0, channel.size(), socketChannel);
    
            System.out.println("发送总字节数:" + transferCount +
                    ", 耗时:" + (System.currentTimeMillis() - startTime));
    
        }
    }
    

    DMA

    DMA: 是计算机科学中的一种内存访问技术。它允许某些计算机内部的硬件子系统(计算机外设),可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理 。在同等程度的处理器负担下,DMA是一种快速的数据传送方式。很多硬件的系统会使用DMA,包含硬盘控制器、绘图显卡、网卡和声卡。

    DMA传输步骤:

    DMA: 是计算机科学中的一种内存访问技术。它允许某些计算机内部的硬件子系统(计算机外设),可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理 。在同等程度的处理器负担下,DMA是一种快速的数据传送方式。很多硬件的系统会使用DMA,包含硬盘控制器、绘图显卡、网卡和声卡。

    DMA传输步骤:

    1. 能向CPU发出系统保持(HOLD)信号,提出总线接管请求
    2. 当CPU发出允许接管信号后,负责对总线的控制,进入DMA方式
    3. 能对存储器寻址及能修改地址指针,实现对内存的读写
    4. 能决定本次DMA传送的字节数,判断DMA传送是否结束
    5. 发出DMA结束信号,使CPU恢复正常工作状态

    用户态、内核态: 从下图可以更进一步对内核所做的事有一个“全景式”的印象。主要表现为:向下控制硬件资源,向内管理操作系统资源:包括进程的调度和管理、内存的管理、文件系统的管理、设备驱动程序的管理以及网络资源的管理,向上则向应用程序提供系统调用的接口。从整体上来看,整个操作系统分为两层:用户态和内核态,这种分层的架构极大地提高了资源管理的可扩展性和灵活性,而且方便用户对资源的调用和集中式的管理,带来一定的安全性。

    传统数据传输方式(IO拷贝过程)

    整体示意图

    简单示意图及解释

    进行了4上下文切换,和4次copy操作

    其它图示及解释

    拷贝过程

    上下文切换

    涉及的步骤
    1. 用户发出 read 指令,触发第一次上下文切换:从用户上下文切换到内核上下文;然后DMA copy ,执行第一次复制,从磁盘将数据copy到内核空间缓冲区中
    2. 用户 read 指令返回,触发第二次上下文切换:从内核上下文切换到用户上下文;CPU copy,执行第二次复制,将数据从内核空间缓冲区copy到用户空间缓冲区。
    3. 用户发出 write 指令,触发第三次上下文切换:从用户上下文切换到内核上下文;CPU copy,执行第三次复制,将数据从用户空间缓存复制到socket缓冲区。
    4. 用户 writes 指令返回,触发第四次上下文切换:从内核上下文切换到用户上下文;DMA copy,执行第四次拷贝被执行这次拷贝独立地、异步地发生,将socket缓冲区的数据拷贝到NIC缓冲区。

    以上是没有错误的理想方案。如果传输的文件大于内核空间缓冲区和socket缓冲区,则拷贝和上下文切换的数量将更多。总之,传统的数据传输方法至少具有4个上下文切换和4个拷贝。

    Zero-copy

    通过组合传统的数据传输方法,不难发现,如果它是纯静态数据,则完全不需要第二和第三副本。从内核空间缓冲区到socket缓冲区画一条线。并且此过程是在内核上下文中发生的,很明显,此功能的实现需要底层操作系统的支持。

    第一次优化

    整体示意图

    简单示意图

    拷贝过程

    上下文切换

    涉及的步骤
    1. 用户发出一个 transfer(传输)指令 transferTo()方法,触发第一次上下文切换:从用户上下文切换到内核上下文;用户接收transfer(传输)指令的响应,触发第二次上下文切换:从内核上下文切换到用户上下文。
    2. 在 transferTo()方法 期间涉及三次拷贝,第一次从磁盘拷贝到内核缓冲区(DMA copy)、第二次从内核缓冲区拷贝到socket缓冲区(CPU copy)、第三次从socket缓冲区拷贝到NIC缓冲区(DMA copy)。

    此时,减少了2次上下文切换和1次拷贝操作,性能得到提高。从图中的流中我们可以很容易的看出,内核缓冲区到socket缓冲区这个拷贝,似乎没有必要。如果NIC支持从内核缓冲区直接读取,那么我们就可以省略拷贝到socket缓冲区的步骤从而减少1次拷贝。事实上,已经有了相关的硬件支持,这种操作称为网卡的收集操作。

    第二次优化

    整体示意图

    简单示意图

    涉及的步骤
    1. DMA copy 将文件内容拷贝到内核缓冲区;
    2. 仅将数据位置和长度的信息描述符附加到socket缓冲区。DMA直接将数据从内核缓冲区拷贝到NIC缓冲区;因此省去了内核缓冲区拷贝到socket缓冲区(CPU拷贝);

    总之,结合硬件(底层网络接口卡)和底层操作系统的支持,可以将数据传输优化为两个上下文切换器,两个DMA拷贝(实际上,元数据传输仍然可以忽略不计:位置数据描述符),然后将信息描述符的长度附加到socket缓冲区中)。

    零拷贝是一种有效的磁盘到网卡传输方法,它依赖于底层操作系统:零拷贝是kafka的有效保证之一,这意味着java和Scala已经受支持并且已经成熟。在nginx的http配置中,有一个选项sendfile。On可以打开零拷贝,作为c语言编写的nginx对底层系统的调用,它有一个独特的优势。

    最完善的Zero-copy示意图

    在Linux2.4版本之后就采用了这种实现了真正的Zero-copy

    这里的protocol engine(协议引擎)会真正的完成数据发送,在发送数据的时候它会从两个buffer中读取数据(gather搜集操作)。从socket buffer里确认了位置以及长度之后,直接把kernel buffer里的数据发送到网络

    性能测试

  • 相关阅读:
    标题栏中小图标和文字垂直居中的解决办法
    width:100%和width:inherit
    css盒子模型的宽度问题
    position:absolute和margin:auto 连用实现元素水平垂直居中
    超链接a的download属性 实现文件下载功能
    JavaScript的String对象的属性和方法
    原生JavaScript 封装ajax
    深入理解JVM之对象分配流程
    http协议详解
    在RMI方式实现RPC时,为什么业务实现类UserServiceImpl中要显示的创建无参构造方法
  • 原文地址:https://www.cnblogs.com/mikisakura/p/13177467.html
Copyright © 2011-2022 走看看