NIO/BIO

NIO/BIO
    BIO网络通讯
        概述
            网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通讯,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端经过链接操做向服务端监听的地址发起链接请求,经过三次握手创建链接,若是链接创建成功,双方就能够经过网络套接字(Socket)进行通讯。
            在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起链接操做。链接成功以后,双方经过输入和输出流进行同步阻塞式通讯。
            BIO通讯模型的服务端,一般由一个独立的Acceptor线程负责监听客户端的链接,它接收到客户端链接请求以后为每一个客户端建立一个新的线程进行链路处理,处理完成以后,经过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通讯模型。
        该模型最大的问题就是缺少弹性伸缩能力,
            当客户端并发访问量增长后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,因为线程是Java虚拟机很是宝贵的系统资源,当线程数膨胀以后,系统的性能将急剧降低,随着并发访问量的继续增大,系统会发生线程堆栈溢出、建立新线程失败等问题,并最终致使进程宕机或者僵死,不能对外提供服务。
            BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须建立一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端链接。在高性能服务器应用领域,每每须要面向成千上万个客户端的并发链接,这种模型显然没法知足高性能、高并发接入的场景。
        引入线程池
            为了解决同步阻塞I/O面临的一个链路须要一个线程处理的问题,后来有人对它的线程模型进行了优化——后端经过一个线程池来处理多个客户端的请求接入,造成客户端个数M:线程池最大线程数N的比例关系,其中M能够远远大于N。
            经过线程池能够灵活地调配线程资源,设置线程的最大值,防止因为海量并发接入致使线程耗尽。
            因为线程池和消息队列都是有界的,并且避免了为每一个请求都建立一个独立线程形成的线程资源耗尽问题
            所以,不管客户端并发链接数多大,它都不会致使线程个数过于膨胀或者内存溢出。相比于传统的一链接一线程模型,是一种改良。
        BIO通讯框架的弊病
            引入线程池避免了建立大量线程,可是因为底层的通讯依然采用同步阻塞模型,其实并未从根本上解决问题
            当对Socket的输入流进行读取操做的时候,它会一直阻塞下去,直到发生以下三种事件。
                1)有数据可读;
                2)可用数据已经读取完毕;
                3)发生空指针或者I/O异常。
            这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通讯线程将被长时间阻塞,若是对方要60s才可以将数据发送完成,读取一方的I/O线程也将会被同步阻塞60s,在此期间,其余接入消息只能在消息队列中排队。
            BIO的网络通讯,读和写操做都是同步阻塞的,阻塞的时间取决于对方I/O线程的处理速度和网络I/O的传输速度。
            本质上来说,咱们没法保证生产环境的网络情况和对端的应用程序能足够快,若是咱们的应用程序依赖对方的处理速度,它的可靠性就很是差
            不管是否引入线程池技术,若是底层用的是BIO通讯框架,都是没法从根本上解决通讯线程阻塞问题。
        BIO 4种产生阻塞的方法:
            1-4
                 //accept方法会产生阻塞,直到有客户端链接
                 //传统的BIO会产生阻塞:
                 //1.服务端accept()方法会产生阻塞
                //当有客户端接入时,accpet()方法不阻塞
                //可是客户端没有任何的流输入,因此产生了阻塞
                //2.即read()方法也会产生阻塞
                //3.若是服务端未启动,客户端就链接的话会报错,Connection refused
                //可是,须要留意的是,这个异常在程序启动后,并非立刻抛出的
                //而是卡顿了一秒钟才出现的
                //这个现象的缘由:
                //客户端程序启动=》尝试链接服务端=》等待服务端的连接响应=》服务端没有启动=》
                //客户端收到服务端的响应,报出错误提示
                //实际上,对应客户端,socket.connect()这个方法也会产生阻塞
                4.//结果证实,当不断向outputStream里写数据时,写到必定大小后,会产生阻塞
                //即Write方法也会产生阻塞
    NIO
        NIO概述
            Non-Blocking I/O,是一种非阻塞通讯模型。不一样的语言或操做系统都有其不一样的实现。
            java.nio是jdk1.4版本引入的一套API,咱们能够利用这套API实现非阻塞的网络编程模型。
            目前不管是何种应用,都是分布式架构,由于分布式架构可以抗高并发,实现高可用,负载均衡以及存储和处理海量数据,而分布式架构的基础是网络通讯。
            随着当前大数据和实时计算技术的兴起,高性能 RPC 框架与网络编程技术再次成为焦点。
                Fackebook的Thrift框架
                scala的 Akka框架
                实时流领域的 Storm、Spark框架
                开源分布式数据库中的 Mycat、VoltDB
                 Java 领域里大名鼎鼎的 NIO 框架——Netty,则被众多的开源项目或商业软件所采用。
    BIO和NIO的对比
        BIO
            1.阻塞通讯模型,典型表明是ServerSocket 和Socket accept connect  read write 会产生阻塞。因此 BIO通讯模型的弊端在于:若是有大量请求,会建立大量线程,一是可能形成内存溢出,此外,线程多了以后,会形成cpu的负载太高,由于要作线程管理和上下文切换。 虽然引入线程池,也未能解决根本问题,由于底层仍是同步阻塞模型。 同步阻塞模型一是性能低,二是不可靠,取决于对端环境。
            2.面向流处理,即阻塞最根本的原子在于流的read和wirte方法是阻塞方法
            Java NIO 管道是2个线程之间的单向数据链接。
            Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取
        BIO的使用场景:访问量少,长请求(好比下载一个大文件等场景)
        NIO的适用场景:高并发,高访问量,短请求
        NIO
            1.非阻塞通讯模型
            2.面向缓冲区(Buffer)
            3.NIO传输数据的方式:把数据放到缓冲区,而后经过Channel进行传输。
    Buffer
        Buffer的子类,对应了8种基本数据类型里的七种(没有boolean类型),分别是:
            1.ByteBuffer
            2.CharBuffer
            3.DoubleBuffer
            4.FolatBuffer
            5.IntBuffer
            6.LongBuffer
            7.ShortBuffer
        1.建立缓冲区
            static  ByteBuffer allocate(int capacity)参数:capacity 缓冲区的容量,以字节为单位
        2.向缓冲区里写数据
            put(byte b)
            put(byte[] src)
            putXxx()
        3.从缓冲区里读数据
            get()
            当向缓冲区里写入字节时,好比1,当调用get()方法时,获得的倒是0
        4.Buffter缓冲区的4个关键元素
            ①capacity,缓存区总容量
            ②limit的大小<=capacity的大小,建立缓冲区时,默认=capacity
            ③position,初始位置在缓存区的0位,当写入数据时,数据的写入位置就是position的位置
            写完后,position的位置+1。
            ④mark,标记
        5.filp()方法:
            flip()反转缓冲区,做用至关于buffer.limit(buffer.position());+    buffer.position(0);
            HasRemaining方法:
                告知当前位置(position)和限制位(limit)之间是否还有元素
        6.rewind()重绕缓冲区
            将position置为0
        7.clear()清空缓冲区
            //clear的做用是清空缓冲区,但并不真正的清除数据,而是把postion置为0,limit置为容量上限
            //因此,当get(0)时,是能够获得原缓冲区数据的。可是咱们通常都是在clear()方法以后,写数据,而后filp()
            //因此,并不影响缓冲区的重用。
    Channel
        Channel:通道,面向缓冲区,进行双向传输
        总接口:Channel
        其中重点关注他的4个子类
            操做TCP的SocketChannel
                SocketChannel默认也是非阻塞的,须要个更改下configureBlocking(false);
                ServerSocketChannel是一个抽象类,不能直接new,因此调用其静态方法open()
                    建立出来以后,默认是阻塞的,若是要设置成非阻塞模式,须要设置:
                    ssc.configureBlocking(false); 属性为false表示非阻塞
            和ServerSocketChannel
                SocketChannel默认也是非阻塞的,须要个更改下configureBlocking(false);
            操做UDP的DatagramChannel
            操做文件的FileChannel
                 * 这个方法用来测试FileChannel,FileChannel只能经过FileInputStream,FileOutputStream和
                 * RandomAccessFile的getChannel()方法获得。
                 * FileChannel在文件操做上,性能上没什么差异。读或写都是经过缓冲区来操做。此外还提供了一些额外方法,好比能够指定从文件的某个位置开始读或写
                 * 若是FileChannel是经过FileInputStream获得,那他只能读文件,不能写文件。
                 * 经过RandomAccessFile获得的FileChannel,既能够对指定文件读也能够写。而且均可以指定开始读或写的位置
    Selector
        Selector 多路复用器:
            能够理解为路由器和交换机
            在一个Selector上能够同时注册多个非阻塞的通道,从而只须要不多的线程数既能够管理许多通道。特别适用于开启许多通道可是每一个通道中数据流量都很低的状况
        java

 


        //select()是选择器selector查询是否有事件触发的方法,好比accpet事件是否触发,若是accpet事件触发:
        //就意味着有客户端接入了。注意,select()是一个阻塞方法。当有事件被触发时,阻塞放开。
        //引入selector的好处是:线程没必要每时每刻都去工做、去查询客户端是否有新事件,没有事件的时候,线程就睡觉,休息
        //有事件发生,selector会知道,线程再醒来工做。这样一来,能够避免了线程无心义的空转,节省cpu资源,同时也不影响工做
    Zero-Copy
        原理概述
            zero copy(零复制)是一种特殊形式的内存映射,它容许你将Kernel内存直接映射到设备内存空间上。其实就是设备能够经过直接内存访问(direct memory access,DMA)方式来访问Kernal Space。
        咱们拿Kafka举例,Kafka会对流数据作持久化存储以实现容错
            这意味着当Consumer从Kafka消费数据时,会有不少data从硬盘读出以后,会原封不动的经过socket传输给用户。
            数据都被拷贝了四次!
        Zero Copy的具体实现
            实际上第二次和第三次copy是毫无心义的。应用程序仅仅缓存了一下data就原封不动的把它发回给socket buffer。实际上,data应该直接在read buffer和socket buffer之间传输,
        为何要使用kernel buffer作中介
            使用kernel buffer作中介(而不是直接把data传到user buffer中)看起来比较低效(多了一次copy)。
            然而实际上kernel buffer是用来提升性能的。
            在进行读操做的时候,kernel buffer起到了预读cache的做用。当写请求的data size比kernel buffer的size小的时候,这可以显著的提高性能
            在进行写操做时,kernel buffer的存在可使得写请求彻底异步。
            但悲剧的是,当读请求的data size远大于kernel buffer size的时候,这个方法自己变成了性能的瓶颈。
        数据库

相关文章
相关标签/搜索