背景

Netty是一款卓越的用于创建高性能网络应用程序的高级框架,它的核心特点有两个:异步事件驱动异步的特性使得Netty成为了一款高性能的io框架,而事件驱动则令它成为了最简单的异步io框架之一。

异步与Java NIO

在NIO诞生之前,大家开发网络应用程序时使用的都是BIO,即Blocking IO,netty中称之为OIO。简单描述一下BIO模型:Server端开启一个线程不断监听指定端口是否有建立连接的请求,接收到这样的请求并建立链接后,Server会为每一个新建立的连接开启一个独立的线程用于处理接收到的数据并返回结果。如下图:

bio-model

BIO模型所存在的不足:

  • 响应速度不够快:Java中创建一个Thread的开销是很大的,对于一些很简单的服务来说,新建一个Thread的时间可能比请求的处理时间还要长。
  • 线程数量非常大:随着请求数的增加,服务器所创建的线程数量随之增加,操作系统会为每个线程的调用栈都分配内存,导致程序所使用的内存急剧变大;同时,线程调度时上下文切换所带来的开销也会让每一个请求的耗时达到难以忍受的地步。

Java NIO就是为了解决BIO的以上问题而诞生的。(关于BIO,伪异步IO,NIO,AIO之间的具体问题可以参考其他文章描述,这里就不展开讨论了)

通过分析BIO的工作机制,人们发现虽然服务器创建了许多的线程,其中大多数都处于Blocking等待状态(等待socket可读/可写),处于运行状态的线程屈指可数。NIO的核心设计逻辑为:使用一个Selector线程用于观察所有已创建的连接,并把那些处于可读/可写状态的链接扔到线程池中运行处理逻辑,这样便完美解决了BIO模型的两个缺点。NIO的工作模型如下图所示:

nio-model

事件驱动

Java NIO框架的核心是Channel,Selector,Buffer。读取数据的常见流程如下:

  1. 将Channel注册到Selector中
  2. 使用Selector.select()找到处于Ready状态的Channel
  3. 将准备好的Channel放入ThreadPool中执行

整个流程固定而且繁琐,完全由用户来控制与操作。事实上,用户仅会关心如何处理接收到的数据,并不关心如何接收数据。Netty框架接管了这部分繁琐的流程,以事件驱动模型进行构建,采用Facade门面模式将NIO的细节封装起来,仅仅暴露一组简单而通用的API给用户使用。

Netty组件及结构

Netty整体架构如下图所示

netty-structure

Netty主要由六种组件组成:与线程密切相关的EventLoopGroup,EventLoop;与io,系统密切相关的Channel;与用户逻辑密切相关的ChannelPipeline与ChannelHandler;数据的承载者ByteBuf以及将所有组件所组合在一起的BootStrap。

  • EventLoopGroup:相当于ExecutorService,该类确定了整个Netty的线程/EventLoop的分配方式,每一个EventLoopGroup中包含有一个或多个EventLoop。
  • EventLoop:Netty事件循环的Engine,数据的读写与Event的传播都通过该类进行驱动。同时该类也是Netty线程调度的核心抽象,用于处理连接的生命周期中所发生的事件。EventLoop可以被多个Channel所共享。
  • Channel:Channel就是Netty中连接的抽象。每个Channel都有一个与之对应的ChannelPipeline。
  • ChannelPipeline:用于保存经过编排的ChannelHandler实例链的数据结构。每个ChannelPipeline中含有若干个ChannelHandler,ChannelHandler之间是有序的,ChannelPipeline将保证事件按顺序流经被每一个ChannelHandler实例。
  • ChannelHandler:用户逻辑的核心实现类。ChannelHandler采用ByteBuf作为数据容器。
  • ByteBuf:Netty中的数据容器类,与ByteBuffer类似,但提供了一些新的功能。
  • BootStrap:用于将Netty中的各组件组合起来。

ByteBuf

Netty基于Java NIO的ByteBuffer设计了一套用于承载数据的结构:ByteBuf