Netty In Action(一)

前言

这篇主要来记录一下Netty相关的知识点,如比较容易混淆的BIO\NIO\AIO,以及Netty的线程模式、零拷贝技术等。

初识

在BIO流程中,会先将数据拷贝到内核缓存区,再由内核缓冲区拷贝到用户缓冲区,如果用户进程在读取的时候,内核缓冲区的数据还没准备好,那么用户进程就会被阻塞,一直等到数据准备完毕。

而NIO(非阻塞IO,与Java NIO不一样的概念),在读取的时候,如果当前内核缓冲区没有数据,会直接放回一个error,不会阻塞,那么我们就可以通过轮询的方式,判断返回的是否是success,如果是的话表明数据有了。但是NIO的缺点就是要轮询,消耗CPU。

IO多路复用就是来解决轮询的问题。用户线程将要处理的文件描述符注册到select中,在调用select函数的时候,select函数会将当前线程进行阻塞,一直等到注册中有就绪的IO事件时,才会唤醒当前线程对就绪的IO进行处理。select虽然会阻塞当前线程与BIO有点类型,但是select的好处就是可以监控多个IO流,并且没有想NIO一样忙轮询。它实现的本质是采用了回调与文件驱动poll函数。

既然IO多路复用可以监控多个IO流,那么它就采用了事件分发的reactor模式,即有专门来处理IO连接线程组,也有专门来处理IO读写的线程组,以此提高效率。

BIO

同步阻塞

你想输入的替代文字

主要瓶颈在线程上。每个连接都会建立一个线程。虽然线程消耗比进程小,但是一台机器实际上能建立的有效线程有限,以Java来说,1.5以后,一个线程大致消耗1M内存!且随着线程数量的增加,CPU切换线程上下文的消耗也随之增加,在高过某个阀值后,继续增加线程,性能不增反降

NIO

同步非阻塞(编码复杂,需处理半包问题)

你想输入的替代文字

性能高,不用一个连接就建一个线程,可以一个线程处理所有的连接!相应的,编码就复杂很多,从上面的代码就可以明显体会到了。还有一个问题,由于是非阻塞的,应用无法知道什么时候消息读完了,就存在了半包问题!

所有的请求注册到一个Selector多路复用器上,多路复用器轮询channle,然后去处理对应的事件。

buffer 、 channel、selector

AIO

异步非阻塞

服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成再通知服务器应用去启动线程进行处理,这里体现了异步的操作。NIO之所以是同步阻塞,就是因为IO请求都是服务器线程自己做的,没有其他线程做好了,再去通知它。

Netty的线程模型

Netty是对Reactor模型的一个实现。那么什么是Reactor模型?Reactor是基于事件驱动的分发处理模型。就拿NIO来说,首先多路复用器先轮询到了一个channel的Accept事件,这个时候它就会把这个ServerSocket重新注册为可Read事件,好让下次多路复用器下次可以读。而Reactor的思想是事件的分发处理,即有专门的处理连接事件的个体(Handlers),然后有专门处理读写的个体(Acceptor)。还有一个分派的个体(Reactor)。这个就是分发处理模型。

  • Reactor 将I/O事件分派给对应的Handler
  • Acceptor 处理客户端新连接,并分派请求到处理器链中
  • Handlers 执行非阻塞读/写 任务

Reactor模型有三种:单Reactor单线程模型、单Reactor多线程模型、多Reactor多线程模型

Reactor单线程模型

你想输入的替代文字

这个模型和上面的NIO流程很类似,只是将消息相关处理独立到了Handler中去了!

单Reactor多线程模型

Reactor多线程模型就是将Handler中的IO操作和非IO操作分开,操作IO的线程称为IO线程,非IO操作的线程称为工作线程!这样的话,客户端的请求会直接被丢到线程池中,客户端发送请求就不会堵塞!但是当用户进一步增加的时候,Reactor会出现瓶颈!

你想输入的替代文字

主从Reactor模型

你想输入的替代文字

主Reactor用于响应连接请求,从Reactor用于处理IO操作请求!这个模型就是Netty的实现了。MainReactor 对应bossGroup,从Reactor对应workerGroup。

Netty的零拷贝技术

零拷贝:就是在操作数据的时候,减少数据从一块缓冲区到另外一个缓冲区的拷贝,因为减少了一次拷贝,所以CPU效率提高了。

在操作系统层面上零拷贝通常是指,减少用户态和内核态的一次拷贝。如正常情况下:先把磁盘上的数据读入到内核态,然后内核态拷贝到用户态,这个时候如果用户态需要进行网络传输,又需要把这个数据拷贝到SocketBuffer中。如下图:

你想输入的替代文字

而零拷贝技术就是让内核态中的数据直接拷贝到SockerBuffer中:

你想输入的替代文字

在NIO中它的零拷贝体现在用户态和用户态之间的一次减少拷贝。

你想输入的替代文字

在NIO中,NIO可以申请到直接内存,先来说一下为什么JVM中的数据不能直接拷贝到SocketBuffer中。有以下两点原因:

第一是因为JVM的GC缘故,会导致JVM堆处于一个变化的状态,有可能导致传输数据被回收之类的不可预料问题。所以必须先将JVM中的数据拷贝到直接内存(直接内存是在JVM之外的用户态中)。

第二是JVM如果要传输到SocketBuffer,两者需要一个传输协调的问题,JVM不想要花费这部分的时间来等待,所以直接拷贝到直接内存,然后直接内存再拷到SocketBuffer就和JVM没关系了。

你想输入的替代文字