NetCore Netty 框架 BT.Netty.RPC 系列随讲 二 WHO AM I 之 NETTY/NETTY 与 网络通讯 IO 模型之关系?


一:NETTY 是什么?


Netty 是什么?  这个问题其实百度上一搜一堆。 这是官方话的描述:Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

其实没那么复杂,用通俗易懂的话来讲


1.1  NETTY  是一个框架

  

NETTY 是一个框架,

这个框架做了什么事情了,做了一件 Rpc 底层通讯对接的事情,

Rpc 底层通顺采用什么方式呢,就是采用基于SOCKET 的TCP/UDP (NIO+Reactor ) 长连接模式进行通讯(重点)

那么NETTY 框架在对接系统级别的通讯层之后 又以编程接口的模式提供给App层。


1.2 NETTY  是基于  Socket IO 通讯模型中的 NIO+Reactor 模型构建的

       NETTY 是基于NIO 的客户端,服务端编程框架。正式因为如此,使得NETTY  在处理高并发量的时候,有很大的线程优势。那么我们接下来要讲讲什么是NIO 模型?

    对应NIO(Non-Blocking I/O) 模型的 对立模型就是BIO(Blocking I/O) 模型。除此之外还有一种AIO 模型(不做讲解)。 我们先了解下BIO 模型。

  •   了解下  BIO  
  1.   BIO 在系统内核中的模型
eAVNZfR

从上图中可以看到:

当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。

典型的阻塞IO模型的例子为:

data = socket.read();

如果数据没有就绪,就会一直阻塞在read方法。并且新soket 连接过来,都会产生新的socket 线程管理,如下图所示。





timg-2



        从上面图中我们可以看出在BIO 模型中,可以清楚的了解到 :一个套接字需要使用一个线程处理,它的过程就是建立连接、读数据、写数据,然而在这些过程中都有可能会发生阻塞。这就是为什么我们首先会接触到这种方式的原因,因为它简单,一个线程只处理一个Socket,但如果是Server端,在并发连接时就需要更多的线程才能完成工作。我们熟悉的 .NET FRAMEWORK WEB 应用的 寄宿主 IIS 就使用此种模式。

    

         下面这张图描述了BIO 在系统内核中的场景图: 客人来了,就让一个新的服务员服务,只等到客人走了,这个服务员的工作任务也就完成了。 说明这个餐厅的服务质量很好,但是效率很低,服务员的利用率跟饱和度低,占用资源浪费,并且成本增加。

   socketIO



  • 了解下NIO

             NIO 分为两种,一种是传统的NIO 因为传统的NIO 为早期的版本,后来系统内核增加了 NIO+Reactor

我们在来看一张图早期版本NIO 内核SOCKET 管理模型:


timg---3 

从上图了解到:

早期的NIO 的原理:当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操    作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。

典型的传统非阻塞IO模型一般如下:


while(true){

data = socket.read();

if(data!= error){

处理数据

break;

}

}

  但是对于早期传统非阻塞IO(NIO)就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很 少使用while循环这种方式来读取数据。


  为了解决这个问题,后期的NIO 版本增加 引入了Reactor 模式,也就是 (线程+SELECT)。也就是现在RPC 服务框架基于底层内核用的最多的NIO+Reactor   模型。

 

  • NIO+Reactor  模型(NIO多路复用模型):

        同样我们了解下NIO多路复用模型的系统内核SOCKET维护流程:

      2eAVjiF (1)

(1)当用户进程调用了select (这个select 非常关键,下面会讲到),那么整个进程会被block;

(2)而同时,kernel会“监视”所有select负责的socket;

(3)当任何一个socket中的数据准备好了,select就会返回;

(4)这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程(空间)


(多路复用IO 模型) 是基于事件驱动的思想,采用了Reactor模式,相对于BIO,NIO一个明显的优势就是不需要为每一个Socket套接字分配一个线程,而是在一个线程中可以处理多个Socket套接字相关的工作。感兴趣的可以去了解一下Reactor模式,在这里给一个链接 -> 详解Reactor

 


timg1

从上面图中可以看出:

      多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。

     在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用

  另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。

  不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。


下面这张是对NIO+Reactor  的系统内核的场景描述图: 多个客人来了,有一个服务员先来接待,并且同步复制所有的客人的信息给相关的处理部门,部门根据自己感兴趣的来处理的相对应的的消息。


NIO



   总结从场景图中可以看出/或者从众看各个系列文章: NIO多路复用模型是基于 一个线程接待了多个客户端套接字的管理,那么反过来说,在这个线程中产生了一个管理者,那么这个管理者称之为 selector  那么这个 (线程+selector ) 是属于netty 层呢还是属于系统级别层呢? 我们以上所述的,都是属于系统本身级别,也就是说 (线程+selector )  还是属于系统级别层的,那么netty 做了些什么呢? NETTY 它是基于系统的NIO +Reactor模型构建的,并且实现了 与操作系统给的NIO+Reactor 模型中的 selector 对象 进行对接,将它封装NETTY框架之中之后,提供了一个易于操作的使用模式和接口,用户使用起来更加方便便捷。


          那么NETTY 与多路复用模型IO 中的 SELECTOR 对象又有什么关系呢? 我们先了解下具体的SELECTOR 管理了哪些内容? 我们先来看一张图:

        


u=1276492790,3867025050&fm=11&gp=0


       上图中很清楚的了解到: 多路复用模型中的Selector 维护了 每个socket 连接的channel ,并为这个channel产了相对于的key ,  放入 MAP 中,从程序角度讲,系统的多路复用器(SELECTOR)  中维护了 map<string ,channel> 对应的集合关系。  从NETTY角度来讲,NETTY 关心的是 系统多路复用IO 模型的 SELECTOR ,因为通过它,就能获取到读写消息事件,并且新的连接的注册事件,还有连接断开事件等,并且通过SELECTOR 找到对应的 socket channel ,就可以对具体的连接进行读写操作了。


三:NETTY  能做什么?


     我们使用通用的应用程序或者类库来实现互相通讯,比如,我们经常使用一个 HTTP 客户端库来从 web 服务器上获取信息,或者通过 web 服务来执行一个远程的调用。

  然而,有时候一个通用的协议或他的实现并没有很好的满足需求。比如我们无法使用一个通用的 HTTP 服务器来处理大文件、电子邮件以及近实时消息,比如金融信息和多人游戏数据。我们需要一个高度优化的协议来处理一些特殊的场景。例如你可能想实现一个优化了的 Ajax 的聊天应用、媒体流传输或者是大文件传输器,你甚至可以自己设计和实现一个全新的协议来准确地实现你的需求。

  另一个不可避免的情况是当你不得不处理遗留的专有协议来确保与旧系统的互操作性。在这种情况下,重要的是我们如何才能快速实现协议而不牺牲应用的稳定性和性能。

并且借助NETTY ,可以 快速搭建一套 基于NIO 的RPC 服务框架体系。我们熟知的 java 的 dobule ,RMI、Hessian。

但是:NETTY 也有自己的缺点:


         NIO 的 Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。


   因此在开发技术选型的时候,不能盲目的选择,还是配合业务场景,根据实际情况来选择.

原文地址:https://www.cnblogs.com/god-wind/p/10938203.html