东莞企业网站设计专业服务,怎么做网页漂亮,广州做网站比较有名的公司,在线网络培训平台1、I/O阻塞书上说BIO、NIO等都属于I/O模型#xff0c;但是I/O模型这个范围有点含糊#xff0c;我为此走了不少弯路。我们日常开发过程中涉及到NIO模型应用#xff0c;如Tomcat、Netty中等线程模型#xff0c;可以直接将其视为 网络I/O模型 。本文还是在基础篇章中介绍几种I…1、I/O阻塞书上说BIO、NIO等都属于I/O模型但是I/O模型这个范围有点含糊我为此走了不少弯路。我们日常开发过程中涉及到NIO模型应用如Tomcat、Netty中等线程模型可以直接将其视为 网络I/O模型 。本文还是在基础篇章中介绍几种I/O模型方式后面就默认只讲解网络I/O模型了。1.1、I/O分类BIO、NIO、AIO等都属于I/O模型所以它们优化的都是系统I/O的性能因此首先我们要清楚常见的I/O有哪些分类1.2、I/O过程和性能I/O(Input/Output)即数据的输入/输出为什么大家很关心I/O的性能呢因为I/O存在的范围很广在高并发的场景下这部分性能会被无限放大。而且与业务无关是可以有统一解决方案的。所有的系统I/O都分为两个阶段等待就绪和数据操作。举例来说读函数分为等待系统可读和真正的读同理写函数分为等待网卡可以写和真正的写等待就绪 等待数据就绪一般是将数据加载到 内核缓存区 。无论是从磁盘、网络读取数据程序能处理的都是进入内核态之后的数据在这之前cpu会阻塞住等待数据进入内核态。数据操作 数据就绪后一般是将内核缓存中的数据加载到 用户缓存区 。需要说明的是等待就绪的阻塞是不使用CPU的是在“空等”而真正的读写操作的阻塞是使用CPU的真正在”干活”而且这个过程非常快属于memory copy带宽通常在1GB/s级别以上可以理解为基本不耗时。这就出现一个奇怪的现象 -- 不使用CPU的“等待就绪”却比实际使用CPU的“数据操作”占用CPU时间更多 。传统阻塞I/O模型即在读写数据过程中会发生阻塞现象。当用户线程发出I/O请求之后内核会去查看数据是否就绪如果没有就绪就会等待数据就绪而用户线程就会处于阻塞状态用户线程交出CPU。当数据就绪之后内核会将数据拷贝到用户线程并返回结果给用户线程用户线程才会解除block状态。明确的是让当前工作线程阻塞等待数据就绪是很浪费线程资源的事情上述三种I/O都有一定的优化方案磁盘I/O 现代电脑中都有一个DMA(Direct Memory Access 直接内存访问) 的外设组件可以将I/O数据直接传送到主存储器中并且传输不需要CPU的参与以此将CPU解放出来去完成其他的事情。网络I/O NIO、AIO等I/O模型通过向事件选择器注册I/O事件基于就绪的事情来驱动执行I/O操作避免的等待过程。内存I/O 内存部分没涉及到太多阻塞优化点在于减少用户态和内核态之间的数据拷贝。nio中的零拷贝就有mmap和sendfile等实现方案。1.3、网络I/O阻塞这里仔细的讲讲网络I/O模型中的阻塞即socket的阻塞。在计算机通信领域socket 被翻译为“套接字”它是计算机之间进行通信的一种约定或一种方式是在tcp/ip协议上抽象出来的一层网络通讯协议。同上面I/O的过程一样网络I/O也同样分成两个部分等待网络数据到达网卡读取到内核缓冲区。从内核缓冲区复制数据到用户态空间。每个 socket 被创建后都会分配两个缓冲区输入缓冲区和输出缓冲区输入缓冲区 当使用 read()/recv() 读取数据时(1)首先会检查缓冲区如果缓冲区中有数据那么就读取否则函数会被阻塞直到网络上有数据到来。(2)如果要读取的数据长度小于缓冲区中的数据长度那么就不能一次性将缓冲区中的所有数据读出剩余数据将不断积压直到有 read()/recv() 函数再次读取。(3)直到读取到数据后 read()/recv() 函数才会返回否则就一直被阻塞。输出缓冲区 当使用 write()/send() 发送数据时(1)首先会检查缓冲区如果缓冲区的可用空间长度小于要发送的数据那么 write()/send() 会被阻塞(暂停执行)直到缓冲区中的数据被发送到目标机器腾出足够的空间才唤醒 write()/send() 函数继续写入数据。(2) 如果TCP协议正在向网络发送数据那么输出缓冲区会被锁定不允许写入write()/send() 也会被阻塞直到数据发送完毕缓冲区解锁write()/send() 才会被唤醒。(3)如果要写入的数据大于缓冲区的最大长度那么将分批写入。(4)直到所有数据被写入缓冲区 write()/send() 才能返回。由此可见在网络I/O中会有很多的因素导致数据的读取和写入过程出现阻塞创建socket连接也一样。socket.accept()、socket.read()、socket.write()这类函数都是同步阻塞的当一个连接在处理I/O的时候系统是阻塞的该线程当前的cpu时间片就浪费了。2、阻塞优化2.1、BIO、NIO、AIOBIO、NIO、AIO对比以socket.read()为例子传统的BIO里面socket.read()如果TCP RecvBuffer里没有数据函数会一直阻塞直到收到数据返回读到的数据。对于NIO如果TCP RecvBuffer有数据就把数据从网卡读到内存并且返回给用户反之则直接返回0永远不会阻塞。最新的AIO(Async I/O)里面会更进一步不但等待就绪是非阻塞的就连数据从网卡到内存的过程也是异步的。换句话说BIO里用户最关心“我要读”NIO里用户最关心”我可以读了”在AIO模型里用户更需要关注的是“读完了”。NIONIO的优化体现在两个方面网络I/O模式 的优化通过非阻塞的模式提高了CPU的使用性能。内存I/O 的优化零拷贝等方式让数据在内核态和用户态之前的传输消耗降低了。NIO一个重要的特点是 socket主要的读、写、注册和接收函数在等待就绪阶段都是非阻塞的真正的I/O操作是同步阻塞的(消耗CPU但性能非常高) 。NIO的主要事件有几个读就绪、写就绪、有新连接到来。我们首先需要注册当这几个事件到来的时候所对应的处理器。然后在合适的时机告诉事件选择器我对这个事件感兴趣。对于写操作就是写不出去的时候对写事件感兴趣对于读操作就是完成连接和系统没有办法承载新读入的数据的时对于accept一般是服务器刚启动的时候而对于connect一般是connect失败需要重连或者直接异步调用connect的时候。其次用一个死循环选择就绪的事件会执行系统调用 (Linux 2.6之前是select、poll2.6之后是epollWindows是IOCP) 还会阻塞的等待新事件的到来。新事件到来的时候会在selector上注册标记位标示可读、可写或者有连接到来。2.2、Reactor模式Reactor模式称之为响应器模式通常用于 NIO 非阻塞IO的网络通信框架中。Reactor设计模式用于处理由一个或多个客户端并发传递给应用程序的的服务请求可以理解成 Reactor模式是用来实现网络NIO的方式 。Reactor是一种事件驱动机制是处理并发I/O常见的一种模式用于同步I/O其中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上同时主线程阻塞在多路复用器上一旦有I/O事件到来或是准备就绪多路复用器将返回并将相应I/O事件分发到对应的处理器中。Reactor模式主要分为下面三个部分事件接收器Acceptor 主要负责接收请求连接接收请求后会将建立的连接注册到分离器中。事件分离器Reactor 依赖于循环监听多路复用器Selector是阻塞的一旦监听到事件就会将事件分发到事件处理器。(例如监听读事件等到内核态数据就绪后将事件分发到HandlerHandler将数据读到用户态再做处理)事件处理器Handler 事件处理器主要完成相关的事件处理比如读写I/O操作。2.3、三种Reactor模式单线程Reactor模式一个线程单线程建立连接(Acceptor)、监听accept、read、write事件(Reactor)、处理事件(Handler)都只用一个单线程。多线程Reactor模式一个线程 一个线程池单线程建立连接(Acceptor)和 监听accept、read、write事件(Reactor)复用一个线程。工作线程池处理事件(Handler)由一个工作线程池来执行业务逻辑包括数据就绪后用户态的数据读写。主从Reactor模式三个线程池主线程池建立连接(Acceptor)并且将accept事件注册到从线程池。从线程池监听accept、read、write事件(Reactor)包括等待数据就绪时内核态的数据I读写。工作线程池处理事件(Handler)由一个工作线程池来执行业务逻辑包括数据就绪后用户态的数据读写。3、Tomcat线程模型3.1、Api网络请求过程我们先补一下基础知识讲解后端接口的响应过程。一个http连接里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步accept 接收客户端的连接请求创建socket连接(tcp三次握手创建连接)。read 从socket读取数据包括等待读就绪和实际读数据。decode 解码因为网络上的数据都是以byte的形式进行传输的要想获取真正的请求必定需要解码。process 业务处理即服务端程序的业务逻辑实现。encode 编码同理因为网络上的数据都是以byte的形式进行传输的也就是socket只接收byte所以必定需要编码。send 往网络socket写回数据包括实际写数据和等待写就绪。3.2、各个线程模型在tomcat的各个版本中所支持的线程模型也发生了一步步演变。一方面直接将默认线程模型从BIO变成了NIO。另一方面在后续几个版本中加入了对AIO和APR线程模型的支持这里要注意仅仅是支持而非默认线程模型。BIO 阻塞式IOtomcat7之前默认采用传统的java IO进行操作该模式下每个请求都会创建一个线程适用于并发量小的场景。NIO 同步非阻塞比传统BIO能更好的支持大并发tomcat 8.0 后默认采用该模式。AIO 异步非阻塞 (NIO2)tomcat8.0后支持。多用于连接数目多且连接比较长(重操作)的架构比如相册服务器充分调用OS参与并发操作编程比较复杂。APR tomcat 以JNI形式调用http服务器的核心动态链接库来处理文件读取或网络传输操作需要编译安装APR库(也就是说IO操作的部分直接调用native代码实现)。各个线程模型中NIO是作为目前最实用的线程模型因此也是目前Tomcat默认的线程模型因此本文对此着重讲解。3.3、BIO和NIOBIO模型在BIO模型中主要参与的角色有 Acceptor 和 Handler工作线程池 。对应于前文中Api的请求过程它们的分工如下Acceptor Accepter线程专门负责建立网络连接( accept )。新连接创建后交给Handler工作线程池处理请求。Handlers 针对每个请求的连接Handler工作线程池都会分配一个线程执行后面的所有步骤( read、decode、process、encode、send )。前文的知识点有铺垫 read 和 send 是面向网络I/O的在等待读写就绪过程中其实是CPU阻塞的。因此Handler工作线程池中的每个线程都会因为I/O阻塞而“空等待”造成浪费。NIO模型tomcat的NIO模型相比较于BIO模型多了个Poller角色 Acceptor 、 Poller 和 Handler工作线程池 。这三个角色是不是很熟悉如果将Poller换成Reactor是不是就是Reactor模型。没错tomcat的nio模型的确就是基于 主从Reactor模型 只不过将Reactor换了个名字而已。Acceptor Accepter线程专门负责建立网络连接( accept )。新连接创建后不是直接使用Worker线程处理请求而是先将请求发送给Poller缓冲队列。Poller 在Poller中维护了一个Selector对象当Poller从缓冲队列中取出连接后注册到该Selector中阻塞等待读写就绪( read等待就绪、send等待就绪 )。Handlers 遍历Selector找出其中就绪的IO操作并交给Worker线程处理( read内存读、decode、process、encode、send内存写 )。对比BIO模型中一个线程对应一个请求连接的完整过程因此tomcat服务能处理的最大连接数和最大线程数一致。NIO模型中在一个请求连接中对应的一个工作线程只处理I/O读写就绪后的非阻塞过程。因此tomcat服务能处理的最大连接数要远大于最大线程数量。3.4、参数设置针对于tomcat的nio模型可以做一些参数设置。因为springboot是内嵌tomcat的这些参数设置同样可以在properties配置文件中定义最大线程数(server.tomcat.threads.max) 工作线程池的最大线程数默认200。注意不是越大越好如果线程数过大那么CPU会花费大量的时间用于线程的切换整体效率会降低。最小线程数(server.tomcat.threads.min-spare) 工作线程池的最小线程数默认10。最大等待数(server.tomcat.accept-count) 当调用HTTP请求数达到tomcat的最大线程数时还有新的HTTP请求到来这时tomcat会将该请求放在等待队列中这个acceptCount就是指能够接受的最大等待数默认100。如果等待队列也被放满了这个时候再来新的请求就会被tomcat拒绝。最大连接数(server.tomcat.max-connections) 在同一时间tomcat能够接受的最大连接数默认8192。4、常见问题1、tomcat运行后出现 nio-8080-exec- 前缀的线程作用是什么是工作线程池中的线程。你们可以观察某个springboot运行项目的线程模型由于基本都是基于nio模型的tomcat应用因此都包括这些线程1个名称中包含Accepter的线程。2个名称中包含Poller的线程。10个工作线程名称从 nio-8080-exec-1 到 nio-8080-exec-10。如果并发交高默认最多有200个线程名称到 nio-8080-exec-200。2、tomcat中nio模型中存在poller单线程读取多个请求线程的数据会不会出现线程安全问题因为通过会使用ThreadLocal存储请求用户身份信息。不会。因为poller只是处理等待读就绪的环节一旦读就绪事件触发后真正的读取数据和处理业务逻辑都是由工作线程池中的某个线程跟到底可以放心大胆使用ThreadLocal。3、为什么我自己对比测试nio和bio性能提升不大nio线程模型优化的是线程利用率为了在高并发场景下基于有限的线程资源处理更多的请求连接。例如tomcat使用默认最大线程数200但你的并发请求数量连200都不到就算是BIO模型线程池中200个线程都没利用完。这时候你用NIO还是BIO区别不大甚至BIO模型处理还更快一些。但如果你的并发请求数到了2000、20000BIO模型就会出现性能瓶颈了超过200的请求都会阻塞住而NIO模型就能大展身手。来源https://www.tuicool.com/articles/FbAbArq