天天看点

Netty系列二 Linux IO NIO 多路复用 Reactor模式

阻塞I/O

Netty系列二 Linux IO NIO 多路复用 Reactor模式
Netty系列二 Linux IO NIO 多路复用 Reactor模式

非阻塞IO

Netty系列二 Linux IO NIO 多路复用 Reactor模式
Netty系列二 Linux IO NIO 多路复用 Reactor模式

同步

用户进程自己去查询数据是否就绪

异步

不用自己,内核把数据拷贝到了buffer,通知用户程序去读取

多路复用

Netty系列二 Linux IO NIO 多路复用 Reactor模式

多个IO事件会注册到select上,select监听多个IO,当有就绪的IO事件,select返回IO的状态,程序调用IO会阻塞

select的实现方式有如下几种:

select
Netty系列二 Linux IO NIO 多路复用 Reactor模式

select底层,内核存储fd使用的是数组,默认1024,不能扩容,select底层没有开辟空间存储fd,所以每次用户程序都要将fd传输给select

poll

poll过程和select一样,内核存储fd使用的是链表

epoll
Netty系列二 Linux IO NIO 多路复用 Reactor模式

epoll底层开辟了空间使用红黑树会存储fd,所以用户态不需要拷贝fd到内核态

epoll相比select,poll的优势

1.select/poll把fd的监听列表放在用户空间,由用户空间管理,导致在用户空间和内核空间之间频繁重复拷贝大量fd;epoll在内核建立fd监听列表(实际是红黑树),每次通过epoll_ctl增删改即可

2.select/poll每当有fd内核事件时,都唤醒当前进程,然后遍历监听列表全部fd,检查所有就绪fd并返回;epoll在有fd内核事件时,通过回调把该fd放到就绪队列中,只需返回该就绪队列即可,不需要每次遍历全部监听fd

信号驱动
Netty系列二 Linux IO NIO 多路复用 Reactor模式

首先允许套接字使用信号驱动I/O.在这种模式下,系统调用将会立即返回,然后程序可以处理其它的工作。当数据准备就绪的时候,系统会向程序进程发送一个SIGIO信号。我们随后就可以在信号处理函数中调用read读取数据报,并通知主循环数据已经准备好待处理,也可以立即通知主循环,让它读取数据报。

异步I/O

Netty系列二 Linux IO NIO 多路复用 Reactor模式

相对于同步IO,异步IO不是顺序执行的,用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,用户进程就可以做其他的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的

BIO模式

Netty系列二 Linux IO NIO 多路复用 Reactor模式
BIO(同步阻塞)模式流程

流程:

1.服务器端的Server是一个线程,线程中执行一个死循环来阻塞的监听客户端的连接请求和通信。

2.当客户端向服务器端发送一个连接请求后,服务器端的Server会接受客户端的请求,ServerSocket.accept()从阻塞中返回,得到一个与客户端连接相对于的Socket。

3.构建一个handler,将Socket传入该handler。创建一个线程并启动该线程,在线程中执行handler,这样与客户端的所有的通信以及数据处理都在该线程中执行。当该客户端和服务器端完成通信关闭连接后,线程就会被销毁。

4.然后Server继续执行accept()操作等待新的连接请求。

BIO模式优点

1.使用简单,容易编程

2.在多核系统下,能够充分利用了多核CPU的资源。即,当I/O阻塞系统,但CPU空闲的时候,可以利用多线程使用CPU资源

BIO模式缺点

1.该模式的本质问题在于严重依赖线程,但线程Java虚拟机非常宝贵的资源。随着客户端并发访问量的急剧增加,线程数量的不断膨胀将服务器端的性能将急剧下降。

2.线程生命周期的开销非常高。线程的创建与销毁并不是没有代价的。在Linux这样的操作系统中,线程本质上就是一个进程,创建和销毁都是重量级的系统函数。

3.资源消耗。内存:大量空闲的线程会占用许多内存,给垃圾回收器带来压力。;CPU:如果你已经拥有足够多的线程使所有CPU保持忙碌状态,那么再创建更过的线程反而会降低性能。

4.稳定性。在可创建线程的数量上存在一个限制。这个限制值将随着平台的不同而不同,并且受多个因素制约:a)JVM的启动参数、b)Threa的构造函数中请求的栈大小、c)底层操作系统对线程的限制 等。如果破坏了这些限制,那么很可能抛出OutOfMemoryError异常。

5.线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,不仅会带来许多无用的上下文切换,还可能导致执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统负载偏高、CPU sy(系统CPU)使用率特别高,导致系统几乎陷入不可用的状态。

6.容易造成锯齿状的系统负载。一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大。

7.若是长连接的情况下并且客户端与服务器端交互并不频繁的,那么客户端和服务器端的连接会一直保留着,对应的线程也就一直存在在,但因为不频繁的通信,导致大量线程在大量时间内都处于空置状态。

BIO模式适用场景

如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合

Rector模式

Netty系列二 Linux IO NIO 多路复用 Reactor模式

基于I/O多路复用模型,多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理

单Reactor单线程

Netty系列二 Linux IO NIO 多路复用 Reactor模式
步骤

Reactor是一个线程对象,该线程会启动事件循环,并使用Selector来实现IO的多路复用,注册一个Acceptor事件处理器到Reactor中,Acceptor事件处理器所关注的事件是ACCEPT事件,这样Reactor会监听客户端向服务器端发起的连接请求事件(ACCEPT事件)。

1.Reactor对象通过Select监听客户端Accept事件,收到事件后通过Dispatch进行分发给对应的Acceptor进行处理。Acceptor处理器通过accept()方法得到与这个客户端对应的连接(SocketChannel),然后将该连接所关注的READ事件以及对应的READ事件处理器注册到Reactor中。

2.当Reactor监听到读写事件时,则Reactor会分发对应的读写Handler处理。

3.每当处理完所有就绪的感兴趣的I/O事件后,Reactor线程会再次执行select()阻塞等待新的事件就绪并将其分派给对应处理器进行处理

注意,Reactor的单线程模式的单线程主要是针对于I/O操作而言,也就是所以的I/O的accept()、read()、write()以及connect()操作都在一个线程上完成的。

优点

模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成

缺点

1.性能问题,只有一个线程,无法完全发挥多核 CPU 的性能

2.可靠性问题,线程意外终止,或者进入死循环,会导致整个系统通信模块不可用

单Reactor多线程

Netty系列二 Linux IO NIO 多路复用 Reactor模式

相比于单Reactor单线程模式,单Reactor多线程增加了线程池来处理非I/O操作(业务处理),这样能够提高Reactor线程的I/O响应,不至于因为一些耗时的业务逻辑而延迟对后面I/O请求的处理

优点

可以充分的利用多核cpu 的处理能力

缺点

多线程数据共享和访问比较复杂,Reactor处理所有的事件的监听和响应,在单线程运行, 在高并发场景容易出现性能瓶颈

主从Reactor多线程

Netty系列二 Linux IO NIO 多路复用 Reactor模式
步骤

1.注册一个Acceptor事件(处理accept事件)处理器到MainReactor中,启动MainReactor的事件循环

2.客户端向服务端发起一个连接请求,MainReactor监听到该Accept事件并将该Acceptor事件派发给Acceptor处理器进行处理。Acceptor处理器通过accept()方法得到该连接的SocketChannel,然后将SocketChannel通过dispatch派发给subReactor

3.SubReactor线程池分配一个SubReactor线程给这个SocketChannel,将这个SocketChannel的R/W事件到SubReactor的selector中。

4.当有I/O事件就绪时,相关的SubReactor就将事件派发给相应的Handler。这里的subReactor线程只负责完成I/O的read()操作,业务处理是在线程池中完成的。

注意,所以的I/O操作(包括,I/O的accept()、read()、write()以及connect()操作)依旧还是在Reactor线程(mainReactor线程或subReactor线程)中完成的。Thread Pool(线程池)仅用来处理非I/O操作的逻辑

优点

1.父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。

2.父线程与子线程的数据交互简单,Reactor主线程只需要把新连接传给子线程,子线程无需返回数据。

继续阅读