关于kubernetes:Kubernetes系统精讲-Go语言实战K8S集群可视化黄垆重醉竟无缘

download:Kubernetes零碎精讲 Go语言实战K8S集群可视化

摘要
次要介绍计算机网络传输一些基础的概念与原理学识,帮助更好的对java网络编程有一个明显的认知和学习。本博文将介绍一下有对于计算机网络相干学识和NIO相干学识。

NIO通信模型基础概念
阻塞(Block)与非阻塞(Non-Block)
阻塞和非阻塞是过程在拜访数据的时候,数据是否准备就绪的一种处理形式,当数据没有筹备的时候。

阻塞:经常需要等待缓冲区中的数据筹备好过后才处理其余的事件,否则一直等待在那里。

非阻塞:当咱们的过程拜访咱们的数据缓冲区的时候,如果数据没有筹备好则间接返回,不会等待。如果数据已经筹备好,也间接返回。

阻塞 IO :

非阻塞 IO :

同步(Synchronous)与异步(Asynchronous)
同步和异步都是基于应用程序和操作系统处理 IO 事件所采纳的形式。比如

同步:是应用程序要直接参与 IO 读写的操作。

异步:所有的 IO 读写交给操作系统去处理,应用程序只需要等待告诉。

同步形式在处理 IO 事件的时候,必须阻塞在某个方法下面等待咱们的 IO 事件实现(阻塞 IO 事件或者通过轮询 IO事件的形式),对于异步来说,所有的 IO 读写都交给了操作系统。这个时候,咱们可能去做其余的事件,并不需要去实现真正的 IO 操作,当操作实现 IO 后,会给咱们的应用程序一个告诉。所以异步相比较于同步带来的间接好处就是在咱们处理IO数据的时候,异步的形式咱们可能把这部分等待所消耗的资源用于处理其余事务,晋升咱们服务自身的性能。

同步 IO :

异步 IO :

BIO(传统IO):
BIO是一个同步并阻塞的IO模式,传统的 java.io 包,它基于流模型实现,提供了咱们最熟知的一些 IO 功能,比如File抽象、输入输出流等。交互方式是同步、阻塞的形式,也就是说,在读取输出流或者写入输入流时,在读、写动作实现之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性次序。

NIO(Non-blocking/New I/O)
NIO 是一种同步非阻塞的 I/O 模型,于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可能理解为 Non-blocking,不单纯是 New。它反对面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 绝对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都反对阻塞和非阻塞两种模式。对于高负载、高并发的(网络)利用,应使用 NIO 的非阻塞模式来开发

BIO与NIO的对比

IO模型

BIO

NIO

通信

面向流

面向缓冲

处理

阻塞 IO

非阻塞 IO

触发

选择器

NIO 的 Server 通信的简略模型:

BIO 的 Server 通信的简略模型:

NIO的个性:

一个线程可能处理多个通道,缩小线程创建数量;
读写非阻塞,节约资源:没有可读/可写数据时,不会发生阻塞导致线程资源的浪费
Reactor模型原理
单线程的 Reactor 模型

多线程的 Reactor 模型

多线程主从 Reactor 模型

Netty架构原理
Netty 简介:Netty 是一个 NIO 客户端服务器框架,可疾速轻松地开发网络应用程序,例如协定服务器和客户端。它极大地简化和简化了网络编程,例如 TCP 和 UDP 套接字服务器。

Netty 执行流程

Netty的底层原理
堆栈内存
堆栈内存指的是堆内存和栈内存:堆内存是GC治理的内存,栈内存是线程内存。堆内存结构:

还有一个更粗疏的结构图(包含MetaSpace还有code cache):注意在Java8当前PermGen被MetaSpace代替,运行时可主动扩容,并且默认是无限大。

咱们看上面一段代码来简略理解下堆栈的关系:

public static void main(String[] args) {
Object o = new Object();
}

其中new Object()是在堆下面调配,而Object o这个变量,是在main这个线程栈下面。

应用程序所有的部分都使用堆内存,而后栈内存通过一个线程运行来使用。
不论对象什么时候创建,他都会存储在堆内存中,栈内存蕴含它的引用。栈内存只蕴含原始值变量好和堆中对象变量的引用。
存储在堆中的对象是全局可能被拜访的,然而栈内存不能被其余线程所拜访。
通过JVM参数-Xmx咱们可能指定最大堆内存大小,通过-Xss咱们可能指定每个线程线程栈占用内存大小
.
堆外内存
狭义的堆外内存:除了堆栈内存,剩下的就都是堆外内存了,包含了jvm本身在运行过程中调配的内存,codecache,jni里调配的内存,DirectByteBuffer调配的内存等等

广义的堆外内存 – DirectByteBuffer:而作为java开发者,咱们常说的堆外内存溢出了,其实是广义的堆外内存,这个次要是指java.nio.DirectByteBuffer在创建的时候分配内存,咱们这篇文章里也次要是讲广义的堆外内存,因为它和咱们平时碰到的问题比较密切。

为啥要使用堆外内存。通常因为:

在过程间可能共享,缩小虚拟机间的复制。
对垃圾回收进展的改善:如果利用某些长期存活并大量存在的对象,常常会出发YGC或者FullGC,可能考虑把这些对象放到堆外。过大的堆会影响Java利用的性能。如果使用堆外内存的话,堆外内存是直接受操作系统治理( 而不是虚拟机 )。这样做的后果就是能保持一个较小的堆内内存,以缩小垃圾收集对利用的影响。
在某些场景下可能晋升程序I/O摆布的性能。少去将数据从堆内内存拷贝到堆外内存的步骤。
JNI调用与内核态及用户态
内核态:cpu可能拜访内存的所有数据,包含外围设备,例如硬盘,网卡,cpu也可能将自己从一个程序切换到另一个程序。
用户态:只能受限的拜访内存,且不容许拜访外围设备,占用cpu的能力被剥夺,cpu资源可能被其余程序获取。
零碎调用:为了使下层利用能够拜访到这些资源,内核为下层利用提供拜访的接口。
Java调用原生方法即JNI就是零碎调用的一种。:咱们举个例子,文件读取;Java本身并不能读取文件,因为用户态没有权限拜访外围设备。需要通过零碎调用切换内核态进行读取。

目前,JAVA的IO形式有基于流的传统IO还有基于块的NIO形式(诚然文件读取其实不是严格意义上的NIO,哈哈)。面向流意味着从流中一次可能读取一个或多个字节,拿到读取的这些做什么你说了算,这里没有任何缓存(这里指的是使用流没有任何缓存,接收或者发送的数据是缓存到操作系统中的,流就像一根水管从操作系统的缓存中读取数据)而且只能次序从流中读取数据,如果需要跳过一些字节或者再读取已经读过的字节,你必须将从流中读取的数据先缓存起来。面向块的处理形式有些不同,数据是先被 读/写到buffer中的,根据需要你可能管制读取什么地位的数据。这在处理的过程中给用户多了一些灵敏性,然而,你需要额定做的工作是查看你需要的数据是否已经全副到了buffer中,你还需要保障当有更多的数据进入buffer中时,buffer中未处理的数据不会被覆盖。咱们这里只分析基于块的NIO形式,在JAVA中这个块就是ByteBuffer。

零拷贝原理
大部分web服务器都要处理大量的动态内容,而其中大部分都是从磁盘文件中读取数据而后写到socket中。咱们以这个过程为例子,来看下不同模式下Linux工作流程

一般Read/Write模式

//从文件中读取,存入tmp_buf
read(file, tmp_buf, len);
//将tmp_buf写入socket
write(socket, tmp_buf, len);

当调用 read 零碎调用时,通过 DMA(Direct Memory Access)将数据 copy 到内核模式。
而后由 CPU 管制将内核模式数据 copy 到用户模式下的 buffer 中。
read 调用实现后,write 调用首先将用户模式下 buffer 中的数据 copy 到内核模式下的 socket buffer 中。
最初通过 DMA copy 将内核模式下的 socket buffer 中的数据 copy 到网卡设施中传送。
从下面的过程可能看出,数据白白从内核模式到用户模式走了一圈,浪费了两次 copy(第一次,从kernel模式拷贝到user模式;第二次从user模式再拷贝回kernel模式,即下面4次过程的第2和3步骤。),而这两次 copy 都是 CPU copy,即占用CPU资源。

NIO下的IO模式
Zero-Copy技术省去了将操作系统的read buffer拷贝到程序的buffer,以及从程序buffer拷贝到socket buffer的步骤,间接将read buffer拷贝到socket buffer. Java NIO中的FileChannal.transferTo()方法就是这样的实现:

public void transferTo(long position,long count,WritableByteChannel target);
1.
transferTo()方法将数据从一个channel传输到另一个可写的channel上,其外部实现依赖于操作系统对zero copy技术的反对。在unix操作系统和各种linux的发型版本中,这种功能最终是通过sendfile()零碎调用实现。下边就是这个方法的定义:

include <sys/socket.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

BIO下的代码实现
BIOserver
package com.zhuangxiaoyan.nio.zerocopy;

import java.io.DataInputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**

  • @Classname NIOClient
  • @Description TODO
  • @Date 2021/11/1 7:25
  • @Created by xjl
    */

public class BIOServer {

public static void main(String[] args) throws Exception {
    ServerSocket serverSocket = new ServerSocket(7001);

    while (true) {
        Socket socket = serverSocket.accept();
        DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());

        try {
            byte[] byteArray = new byte[4096];

            while (true) {
                int readCount = dataInputStream.read(byteArray, 0, byteArray.length);

                if (-1 == readCount) {
                    break;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

}

NIOClient
package com.zhuangxiaoyan.nio.zerocopy;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;

/**

  • @Classname NIOClient
  • @Description TODO
  • @Date 2021/11/1 7:25
  • @Created by xjl
    */

public class BIOClient {

public static void main(String[] args) throws Exception {
    Socket socket = new Socket("localhost", 7001);


    String fileName = "D:\\softwaresavfile\\Github\\JAVA_NIO\\NIO\\src\\main\\resources\\VSCodeUserSetup-x64-1.61.2.exe";
    InputStream inputStream = new FileInputStream(fileName);

    DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());

    byte[] buffer = new byte[4096];
    long readCount;
    long total = 0;

    long startTime = System.currentTimeMillis();

    while ((readCount = inputStream.read(buffer)) >= 0) {
        total += readCount;
        dataOutputStream.write(buffer);
    }

    System.out.println("发送总字节数: " + total + ", 耗时: " + (System.currentTimeMillis() - startTime));

    dataOutputStream.close();
    socket.close();
    inputStream.close();
}

}

NIO下的代码实现
NIOServer
package com.zhuangxiaoyan.nio.zerocopy;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**

  • @Classname NIOServer
  • @Description TODO
  • @Date 2021/11/6 11:16
  • @Created by xjl
    */

public class NIOServer {

public static void main(String[] args) throws IOException {
    InetSocketAddress inetSocketAddress = new InetSocketAddress(7001);

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

    serverSocketChannel.bind(inetSocketAddress);

    //创建一个buffer
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    while (true) {
        SocketChannel socketChannel = serverSocketChannel.accept();
        int readcount = 0;
        while (-1 != readcount) {
            try {
                readcount = socketChannel.read(byteBuffer);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //倒带position = 0 mark作废
            byteBuffer.rewind();
        }
    }

}

}

NIOClient
登录后复制
package com.zhuangxiaoyan.nio.zerocopy;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**

  • @Classname NIOClient
  • @Description TODO
  • @Date 2021/11/6 11:22
  • @Created by xjl
    */

public class NIOClient {

public static void main(String[] args) throws IOException {

    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.connect(new InetSocketAddress("localhost", 7001));

    String fileName = "D:\\softwaresavfile\\Github\\JAVA_NIO\\NIO\\src\\main\\resources\\VSCodeUserSetup-x64-1.61.2.exe";

    //失去一个文件channel
    File file;
    FileChannel fileChannel = new FileInputStream(fileName).getChannel();

    //筹备发送
    long startTime = System.currentTimeMillis();

    //在liunx 在使用 transferto 形式可能实现传输 在window 下调用时候只能发送8M 如果大于时候就是需要分段进行传输文件

    long transfercount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
    System.out.println("发送的总的字节数=" + transfercount + "耗时:" + (System.currentTimeMillis() - startTime));
    fileChannel.close();
}

}

评论

发表回复

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

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理