Loading...
墨滴

lijunsong

2021/05/26  阅读:39  主题:默认主题

NIO,AIO,Netty,GRPC

NIO

基于Channel和Buffer进行操作,数据总是从Channel读取到Buffer中,或者从Buffer写入到Channel中,NIO和传统IO之间最大区别在于,IO面向流的,NIO是面向Buffer的。

三大核心

  1. Channel(通道)

IO中的stream是单向的,如:InputStream,OutputStream,但Channel是双向的,即读写操作都可以进行

  1. Buffer(缓冲区)

客户端发送数据时,必须先将数据存入Buffer,如何将Buffer中数据写入Channel;服务端接收数据必须通过Channel将数据读入到Buffer,如何再从Buffer取出数据来处理。

  • 使用缓冲区优化读写流

NIO 与传统 I/O 不同,它是基于块(Block)的,它以块为基本单位处理数据。在 NIO 中,最为重要的两个组件是缓冲区(Buffer)和通道(Channel)。 Buffer 是一块连续的内存块,是 NIO 读写数据的缓冲。Buffer 可以将文件一次性读入内存再做后续处理,而传统的方式是边读文件边处理数据。Channel 表示缓冲数据的源头或者目的地,它用于读取缓冲或者写入数据,是访问缓冲的接口。

  • 使用 DirectBuffer 减少内存复制

NIO 还提供了一个可以直接访问物理内存的类 DirectBuffer。普通的 Buffer 分配的是 JVM 堆内存,而 DirectBuffer 是直接分配物理内存。 数据要输出到外部设备,必须先从用户空间复制到内核空间,再复制到输出设备,而 DirectBuffer 则是直接将步骤简化为从内核空间复制到外部设备,减少了数据拷贝。 这里拓展一点,由于 DirectBuffer 申请的是非 JVM 的物理内存,所以创建和销毁的代价很高。DirectBuffer 申请的内存并不是直接由 JVM 负责垃圾回收,但在 DirectBuffer 包装类被回收时,会通过 Java 引用机制来释放该内存块。

  • 优化 I/O,避免阻塞

传统 I/O 的数据读写是在用户空间和内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。 NIO 的 Channel 有自己的处理器,可以完成内核空间和磁盘之间的 I/O 操作。在 NIO 中,我们读取和写入数据都要通过 Channel,由于 Channel 是双向的,所以读、写可以同时进行。

  1. Selector

用于监听多个通道的事件,如:连接打开,数据到达,因此单个线程可以监听多个数据通道。 只有当目前有数据时,才处理,没有数据时,就什么都不获取,而不是保持线程的阻塞。不必为了每个连接都创建一个线程,不用去维护多个线程,并且避免了多个线程之间的上下文切换导致的开销。

public class NIOClient {
    /**
     * 先启动NIOServer,再启动NIOClient
     */

    @SneakyThrows
    public static void main(String[] args) {
        Socket socket = new Socket("127.0.0.1"9090);
        OutputStream out = socket.getOutputStream();
        String s = "hello world1";
        out.write(s.getBytes());
        out.close();
    }
}
@Slf4j
public class NIOServer {
    @SneakyThrows
    public static void main(String[] args) {
        Selector selector = Selector.open();

        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.configureBlocking(false);
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        ServerSocket serverSocket = ssChannel.socket();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1"9090);
        serverSocket.bind(address);

        while (true) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();

            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();

                    /**
                     * 服务器为每个连接创建一个SocketChannel
                     */

                    SocketChannel socketChannel = ssChannel1.accept();
                    socketChannel.configureBlocking(false);
                    /**
                     * 这个新连接主要用于客户端读取数据
                     */

                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    log.info(readDataFromSocketChannel(socketChannel));
                    socketChannel.close();
                }
                iterator.remove();
            }
        }
    }

    @SneakyThrows
    private static String readDataFromSocketChannel(SocketChannel socketChannel) {
        StringBuilder data = new StringBuilder();
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            buffer.clear();
            int n = socketChannel.read(buffer);

            //EOF
            if (n == -1) {
                break;
            }

            buffer.flip();
            int limit = buffer.limit();
            char[] dst = new char[limit];
            for (int i = 0; i < limit; i++) {
                dst[i] = (char) buffer.get(i);
            }
            data.append(dst);
            buffer.clear();
        }
        return data.toString();
    }
}

AIO

AIO(Asynchronous IO) 即异步非阻塞 IO,指的是 Java 7 中,对 NIO 有了进一步的改进,也称为 NIO2,引入了异步非阻塞 IO 方式。

在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

Netty

封装了JAVA NIO,提供高性能、异步时间驱动的NIO框架

  • Reactor模式是一种被动的处理,即有事件发生时才处理。而Proator模式则是主动发起异步调用,然后循环检测完成事件。

优点:Reactor实现相对简单,对于链接多,但耗时短的处理场景高效;

缺点:Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;

Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序;

Proactor:异步接收和同时处理多个服务请求的事件驱动程序。

netty5中使用了ForkJoinPool,增加了代码的复杂度,但是对性能的改善却不明显,官方推荐Netty4.x

Netty应用场景 1.分布式开源框架中dubbo、Zookeeper,RocketMQ底层rpc通讯使用就是netty。

Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点,如果数据量大,就会造成不必要的资源浪费

Netty使用了NIO中的另一大特性——零拷贝(接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝),当他需要接收数据的时候,他会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去,在netty里面通过ByteBuf可以直接对这些数据进行直接操作,从而加快了传输速度

grpc

主要使用场景:

低延时、高可用的分布式系统; 移动端与云服务端的通讯; 使用protobuf,独立于语言的协议,支持多语言之间的通讯; 可以分层扩展,如:身份验证,负载均衡,日志记录,监控等。

lijunsong

2021/05/26  阅读:39  主题:默认主题

作者介绍

lijunsong