Netty模型架构

Netty是一个NIO的异步驱动网络应用客户端服务器框架,能够快速轻松地开发协议服务器和客户端等网络应用程序。它极大地简化并简化了TCP和UDP套接字服务器等网络编程。具备丰富的协议,如FTP,SMTP,HTTP以及各类二进制和基于文本的传统协议react

JDK原生NIO程序的问题数据库

JDK原生也有一套网络应用程序API,可是存在一系列问题,主要以下:编程

a. NIO的类库和API繁杂,使用麻烦,你须要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等设计模式

b. 须要具有其它的额外技能作铺垫,例如熟悉Java多线程编程,由于NIO编程涉及到Reactor模式,你必须对多线程和网路编程很是熟悉,才能编写出高质量的NIO程序缓存

c. 可靠性能力补齐,开发工做量和难度都很是大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等,NIO编程的特色是功能开发相对容易,可是可靠性能力补齐工做量和难度都很是大安全

d. JDK NIO的BUG,例如臭名昭著的epoll bug,它会致使Selector空轮询,最终致使CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,可是直到JDK1.7版本该问题仍旧存在,只不过该bug发生几率下降了一些而已,它并无被根本解决服务器

Netty的特色

Netty的对JDK自带的NIO的API进行封装,解决上述问题,主要特色有:微信

  • 设计优雅 适用于各类传输类型的统一API - 阻塞和非阻塞Socket 基于灵活且可扩展的事件模型,能够清晰地分离关注点 高度可定制的线程模型 - 单线程,一个或多个线程池 真正的无链接数据报套接字支持(自3.1起)网络

  • 使用方便 详细记录的Javadoc,用户指南和示例 没有其余依赖项,JDK 5(Netty 3.x)或6(Netty 4.x)就足够了多线程

  • 高性能 吞吐量更高,延迟更低 减小资源消耗 最小化没必要要的内存复制

  • 安全 完整的SSL / TLS和StartTLS支持

  • 社区活跃,不断更新 社区活跃,版本迭代周期短,发现的BUG能够被及时修复,同时,更多的新功能会被加入

Netty常见的使用场景以下:

  • 互联网行业 在分布式系统中,各个节点之间须要远程服务调用,高性能的RPC框架必不可少,Netty做为异步高新能的通讯框架,每每做为基础通讯组件被这些RPC框架使用。 典型的应用有:阿里分布式服务框架Dubbo的RPC框架使用Dubbo协议进行节点间通讯,Dubbo协议默认使用Netty做为基础通讯组件,用于实现各进程节点之间的内部通讯。canal使用的是netty和数据库进行长链接,及时推送bin-log文件。

  • 游戏行业 不管是手游服务端仍是大型的网络游戏,Java语言获得了愈来愈普遍的应用。Netty做为高性能的基础通讯组件,它自己提供了TCP/UDP和HTTP协议栈。 很是方便定制和开发私有协议栈,帐号登陆服务器,地图服务器之间能够方便的经过Netty进行高性能的通讯

  • 大数据领域 经典的Hadoop的高性能通讯和序列化组件Avro的RPC框架,默认采用Netty进行跨界点通讯,它的Netty Service基于Netty框架二次封装实现

2 Netty高性能设计

Netty做为异步事件驱动的网络,高性能之处主要来自于其I/O模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据

I/O模型

用什么样的通道将数据发送给对方,BIO、NIO或者AIO,I/O模型在很大程度上决定了框架的性能

阻塞I/O

传统阻塞型I/O(BIO)能够用下图表示:

特色

  • 每一个请求都须要独立的线程完成数据read,业务处理,数据write的完整操做

问题

  • 当并发数较大时,须要建立大量线程来处理链接,系统资源占用较大

  • 链接创建后,若是当前线程暂时没有数据可读,则线程就阻塞在read操做上,形成线程资源浪费

I/O复用模型

 

在I/O复用模型中,会用到select,这个函数也会使进程阻塞,可是和阻塞I/O所不一样的的,这两个函数能够同时阻塞多个I/O操做,并且能够同时对多个读操做,多个写操做的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操做函数

Netty的非阻塞I/O的实现关键是基于I/O复用模型,这里用Selector对象表示:

Netty的IO线程NioEventLoop因为聚合了多路复用器Selector,能够同时并发处理成百上千个客户端链接。当线程从某客户端Socket通道进行读写数据时,若没有数据可用时,该线程能够进行其余任务。线程一般将非阻塞 IO 的空闲时间用于在其余通道上执行 IO 操做,因此单独的线程能够管理多个输入和输出通道。

因为读写操做都是非阻塞的,这就能够充分提高IO线程的运行效率,避免因为频繁I/O阻塞致使的线程挂起,一个I/O线程能够并发处理N个客户端链接和读写操做,这从根本上解决了传统同步阻塞I/O一链接一线程模型,架构的性能、弹性伸缩能力和可靠性都获得了极大的提高。

基于buffer

传统的I/O是面向字节流或字符流的,以流式的方式顺序地从一个Stream 中读取一个或多个字节, 所以也就不能随意改变读取指针的位置。在NIO中, 抛弃了传统的 I/O流, 而是引入了Channel和Buffer的概念. 在NIO中, 只能从Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel。基于buffer操做不像传统IO的顺序操做, NIO 中能够随意地读取任意位置的数据

事件驱动模型

一般,咱们设计一个事件处理模型的程序有两种思路

  • 轮询方式 线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑。

  • 事件驱动方式 发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式,实际上是设计模式中观察者模式的思路。

以GUI的逻辑处理为例,说明两种逻辑的不一样:

  • 轮询方式 线程不断轮询是否发生按钮点击事件,若是发生,调用处理逻辑

  • 事件驱动方式 发生点击事件把事件放入事件队列,在另外线程消费的事件列表中的事件,根据事件类型调用相关事件处理逻辑

主要包括4个基本组件:

 

  • 事件队列(event queue):接收事件的入口,存储待处理事件

  • 分发器(event mediator):将不一样的事件分发到不一样的业务逻辑单元

  • 事件通道(event channel):分发器与处理器之间的联系渠道

  • 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操做

 

能够看出,相对传统轮询模式,事件驱动有以下优势:

  • 可扩展性好,分布式的异步架构,事件处理器之间高度解耦,能够方便扩展事件处理逻辑

  • 高性能,基于队列暂存事件,能方便并行异步处理事件

 

Reactor线程模型

Reactor是反应堆的意思,Reactor模型,是指经过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。 服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor模式也叫Dispatcher模式,即I/O多了复用统一监听事件,收到事件后分发(Dispatch给某进程),是编写高性能网络服务器的必备技术之一。

Reactor模型中有2个关键组成:

  • Reactor Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件作出反应。 它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人

  • Handlers 处理程序执行I/O事件要完成的实际事件,相似于客户想要与之交谈的公司中的实际官员。Reactor经过调度适当的处理程序来响应I/O事件,处理程序执行非阻塞操做

 

 

取决于Reactor的数量和Hanndler线程数量的不一样,Reactor模型有3个变种

  • 单Reactor单线程

  • 单Reactor多线程

  • 主从Reactor多线程

能够这样理解,Reactor就是一个执行while (true) { selector.select(); ...}循环的线程,会源源不断的产生新的事件,称做反应堆很贴切。

 

3.2.1 单Reactor单线程

其中,select是前面I/O复用模型介绍的标准网络编程API,能够实现应用程序经过一个阻塞对象监听多路链接请求,其余方案示意图相似

方案说明

  • Reactor对象经过select监控客户端请求事件,收到事件后经过dispatch进行分发
  • 若是是创建链接请求事件,则由Acceptor经过accept处理链接请求,而后建立一个Handler对象处理链接完成后的后续业务处理
  • 若是不是创建链接事件,则Reactor会分发调用链接对应的Handler来响应
  • Handler会完成read->业务处理->send的完整业务流程

优势 模型简单,没有多线程、进程通讯、竞争的问题,所有都在一个线程中完成

缺点

  • 性能问题:只有一个线程,没法彻底发挥多核CPU的性能 Handler在处理某个链接上的业务时,整个进程没法处理其余链接事件,很容易致使性能瓶颈
  • 可靠性问题:线程意外跑飞,或者进入死循环,会致使整个系统通讯模块不可用,不能接收和处理外部消息,形成节点故障

使用场景 客户端的数量有限,业务处理很是快速,好比Redis,业务处理的时间复杂度O(1)

3.2.2 单Reactor多线程

 

 

方案说明

  • Reactor对象经过select监控客户端请求事件,收到事件后经过dispatch进行分发
  • 若是是创建链接请求事件,则由Acceptor经过accept处理链接请求,而后建立一个Handler对象处理链接完成后的续各类事件
  • 若是不是创建链接事件,则Reactor会分发调用链接对应的Handler来响应
  • Handler只负责响应事件,不作具体业务处理,经过read读取数据后,会分发给后面的Worker线程池进行业务处理
  • Worker线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给Handler进行处理
  • Handler收到响应结果后经过send将响应结果返回给client

优势 能够充分利用多核CPU的处理能力

缺点

  • 多线程数据共享和访问比较复杂
  • Reactor承担全部事件的监听和响应,在单线程中运行,高并发场景下容易成为性能瓶颈
 

3.2.3 主从Reactor多线程

针对单Reactor多线程模型中,Reactor在单线程中运行,高并发场景下容易成为性能瓶颈,可让Reactor在多线程中运行

 

 

方案说明

  • Reactor主线程MainReactor对象经过select监控创建链接事件,收到事件后经过Acceptor接收,处理创建链接事件
  • Accepto处理创建链接事件后,MainReactor将链接分配Reactor子线程给SubReactor进行处理
  • SubReactor将链接加入链接队列进行监听,并建立一个Handler用于处理各类链接事件
  • 当有新的事件发生时,SubReactor会调用链接对应的Handler进行响应
  • Handler经过read读取数据后,会分发给后面的Worker线程池进行业务处理
  • Worker线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给Handler进行处理
  • Handler收到响应结果后经过send将响应结果返回给client

优势

  • 父线程与子线程的数据交互简单职责明确,父线程只须要接收新链接,子线程完成后续的业务处理
  • 父线程与子线程的数据交互简单,Reactor主线程只须要把新链接传给子线程,子线程无需返回数据

这种模型在许多项目中普遍使用,包括Nginx主从Reactor多进程模型,Memcached主从多线程,Netty主从多线程模型的支持

3.2.4 总结

3种模式能够用个比喻来理解: 餐厅经常雇佣接待员负责迎接顾客,当顾客入坐后,侍应生专门为这张桌子服务

  • 单Reactor单线程 接待员和侍应生是同一我的,全程为顾客服务
  • 单Reactor多线程 1个接待员,多个侍应生,接待员只负责接待
  • 主从Reactor多线程 多个接待员,多个侍应生

Reactor模式具备以下的优势:

  • 响应快,没必要为单个同步时间所阻塞,虽然Reactor自己依然是同步的
  • 编程相对简单,能够最大程度的避免复杂的多线程及同步问题,而且避免了多线程/进程的切换开销;
  • 可扩展性,能够方便的经过增长Reactor实例个数来充分利用CPU资源
  • 可复用性,Reactor模型自己与具体事件处理逻辑无关,具备很高的复用性

3.3 Proactor模型

在Reactor模式中,Reactor等待某个事件或者可应用或个操做的状态发生(好比文件描述符可读写,或者是socket可读写),而后把这个事件传给事先注册的Handler(事件处理函数或者回调函数),由后者来作实际的读写操做,其中的读写操做都须要应用程序同步操做,因此Reactor是非阻塞同步网络模型。若是把I/O操做改成异步,即交给操做系统来完成就能进一步提高性能,这就是异步网络模型Proactor

 

 


Proactor是和异步I/O相关的,详细方案以下:

  • ProactorInitiator建立Proactor和Handler对象,并将Proactor和Handler都经过AsyOptProcessor(Asynchronous Operation Processor)注册到内核
  • AsyOptProcessor处理注册请求,并处理I/O操做
  • AsyOptProcessor完成I/O操做后通知Proactor
  • Proactor根据不一样的事件类型回调不一样的Handler进行业务处理
  • Handler完成业务处理

能够看出Proactor和Reactor的区别:Reactor是在事件发生时就通知事先注册的事件(读写在应用程序线程中处理完成);Proactor是在事件发生时基于异步I/O完成读写操做(由内核完成),待I/O操做完成后才回调应用程序的处理器来处理进行业务处理

理论上Proactor比Reactor效率更高,异步I/O更加充分发挥DMA(Direct Memory Access,直接内存存取)的优点,可是有以下缺点:

  • 编程复杂性 因为异步操做流程的事件的初始化和事件完成在时间和空间上都是相互分离的,所以开发异步应用程序更加复杂。应用程序还可能由于反向的流控而变得更加难以Debug
  • 内存使用 缓冲区在读或写操做的时间段内必须保持住,可能形成持续的不肯定性,而且每一个并发操做都要求有独立的缓存,相比Reactor模式,在socket已经准备好读或写前,是不要求开辟缓存的
  • 操做系统支持 Windows 下经过 IOCP 实现了真正的异步 I/O,而在 Linux 系统下,Linux2.6才引入,目前异步I/O还不完善

所以在Linux下实现高并发网络编程都是以Reactor模型为主

 

Netty线程模型

Netty主要基于主从Reactors多线程模型(以下图)作了必定的修改,其中主从Reactor多线程模型有多个Reactor:MainReactor和SubReactor:

  • MainReactor负责客户端的链接请求,并将请求转交给SubReactor

  • SubReactor负责相应通道的IO读写请求

  • 非IO请求(具体逻辑处理)的任务则会直接写入队列,等待worker threads进行处理

这里引用Doug Lee大神的Reactor介绍:Scalable IO in Java里面关于主从Reactor多线程模型的图

 

特别说明的是: 虽然Netty的线程模型基于主从Reactor多线程,借用了MainReactor和SubReactor的结构,可是实际实现上,SubReactor和Worker线程在同一个线程池中:

EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workerGroup)
 .channel(NioServerSocketChannel.class)

 

上面代码中的bossGroup 和workerGroup是Bootstrap构造方法中传入的两个对象,这两个group均是线程池

  • bossGroup线程池则只是在bind某个端口后,得到其中一个线程做为MainReactor,专门处理端口的accept事件,每一个端口对应一个boss线程

  • workerGroup线程池会被各个SubReactor和worker线程充分利用

异步处理

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能马上获得结果。实际处理这个调用的部件在完成后,经过状态、通知和回调来通知调用者。

Netty中的I/O操做是异步的,包括bind、write、connect等操做会简单的返回一个ChannelFuture,调用者并不能马上得到结果,经过Future-Listener机制,用户能够方便的主动获取或者经过通知机制得到IO操做结果。

当future对象刚刚建立时,处于非完成状态,调用者能够经过返回的ChannelFuture来获取操做执行的状态,注册监听函数来执行完成后的操,常见有以下操做:

  • 经过isDone方法来判断当前操做是否完成

  • 经过isSuccess方法来判断已完成的当前操做是否成功

  • 经过getCause方法来获取已完成的当前操做失败的缘由

  • 经过isCancelled方法来判断已完成的当前操做是否被取消

  • 经过addListener方法来注册监听器,当操做已完成(isDone方法返回完成),将会通知指定的监听器;若是future对象已完成,则理解通知指定的监听器

例以下面的的代码中绑定端口是异步操做,当绑定操做处理完,将会调用相应的监听器处理逻辑

 

  serverBootstrap.bind(port).addListener(future -> {
        if (future.isSuccess()) {
            System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
        } else {
            System.err.println("端口[" + port + "]绑定失败!");
        }
    });

相比传统阻塞I/O,执行I/O操做后线程会被阻塞住, 直到操做完成;异步处理的好处是不会形成线程阻塞,线程在I/O操做期间能够执行别的程序,在高并发情形下会更稳定和更高的吞吐量。

3 Netty架构设计

功能特性

前面介绍完Netty相关一些理论介绍,下面从功能特性、模块组件、运做过程来介绍Netty的架构设计

 

摘自官网

引用https://mp.weixin.qq.com/s/IKejrutZktx8OOkHHHc2Cg## 

  • 传输服务 支持BIO和NIO

  • 容器集成 支持OSGI、JBossMC、Spring、Guice容器

  • 协议支持 HTTP、Protobuf、二进制、文本、WebSocket等一系列常见协议都支持。 还支持经过实行编码解码逻辑来实现自定义协议

  • Core核心 可扩展事件模型、通用通讯API、支持零拷贝的ByteBuf缓冲对象

模块组件

Bootstrap、ServerBootstrap

Bootstrap意思是引导,一个Netty应用一般由一个Bootstrap开始,主要做用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动引导类,ServerBootstrap是服务端启动引导类。

 

Future、ChannelFuture

正如前面介绍,在Netty中全部的IO操做都是异步的,不能马上得知消息是否被正确处理,可是能够过一会等它执行完成或者直接注册一个监听,具体的实现就是经过Future和ChannelFutures,他们能够注册一个监听,当操做执行成功或失败时监听会自动触发注册的监听事件。

 

Channel

Netty网络通讯的组件,可以用于执行网络I/O操做。 Channel为用户提供:

  • 当前网络链接的通道的状态(例如是否打开?是否已链接?)

  • 网络链接的配置参数 (例如接收缓冲区大小)

  • 提供异步的网络I/O操做(如创建链接,读写,绑定端口),异步调用意味着任何I / O调用都将当即返回,而且不保证在调用结束时所请求的I / O操做已完成。调用当即返回一个ChannelFuture实例,经过注册监听器到ChannelFuture上,能够I / O操做成功、失败或取消时回调通知调用方。

  • 支持关联I/O操做与对应的处理程序

不一样协议、不一样的阻塞类型的链接都有不一样的 Channel 类型与之对应,下面是一些经常使用的 Channel 类型

  • NioSocketChannel,异步的客户端 TCP Socket 链接

  • NioServerSocketChannel,异步的服务器端 TCP Socket 链接

  • NioDatagramChannel,异步的 UDP 链接

  • NioSctpChannel,异步的客户端 Sctp 链接

  • NioSctpServerChannel,异步的 Sctp 服务器端链接 这些通道涵盖了 UDP 和 TCP网络 IO以及文件 IO.

     

Selector

Netty基于Selector对象实现I/O多路复用,经过 Selector, 一个线程能够监听多个链接的Channel事件, 当向一个Selector中注册Channel 后,Selector 内部的机制就能够自动不断地查询(select) 这些注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络链接完成等),这样程序就能够很简单地使用一个线程高效地管理多个 Channel 。

 

NioEventLoop

NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:

  • I/O任务 即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。

  • 非IO任务 添加到taskQueue中的任务,如register0、bind0等任务,由runAllTasks方法触发。

两种任务的执行时间比由变量ioRatio控制,默认为50,则表示容许非IO任务执行的时间与IO任务的执行时间相等。

 

NioEventLoopGroup

NioEventLoopGroup,主要管理eventLoop的生命周期,能够理解为一个线程池,内部维护了一组线程,每一个线程(NioEventLoop)负责处理多个Channel上的事件,而一个Channel只对应于一个线程。

 

ChannelHandler

ChannelHandler是一个接口,处理I / O事件或拦截I / O操做,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序。

ChannelHandler自己并无提供不少方法,由于这个接口有许多的方法须要实现,方便使用期间,能够继承它的子类:

  • ChannelInboundHandler用于处理入站I / O事件

  • ChannelOutboundHandler用于处理出站I / O操做

或者使用如下适配器类:

  • ChannelInboundHandlerAdapter用于处理入站I / O事件

  • ChannelOutboundHandlerAdapter用于处理出站I / O操做

  • ChannelDuplexHandler用于处理入站和出站事件

     

ChannelHandlerContext

保存Channel相关的全部上下文信息,同时关联一个ChannelHandler对象

 

ChannelPipline

保存ChannelHandler的List,用于处理或拦截Channel的入站事件和出站操做。 ChannelPipeline实现了一种高级形式的拦截过滤器模式,使用户能够彻底控制事件的处理方式,以及Channel中各个的ChannelHandler如何相互交互。

 下图引用Netty的Javadoc4.1中ChannelPipline的说明,描述了ChannelPipeline中ChannelHandler一般如何处理I/O事件。 I/O事件由ChannelInboundHandler或ChannelOutboundHandler处理,并经过调用ChannelHandlerContext中定义的事件传播方法(例如ChannelHandlerContext.fireChannelRead(Object)和ChannelOutboundInvoker.write(Object))转发到其最近的处理程序。

 

 

入站事件由自下而上方向的入站处理程序处理,如图左侧所示。 入站Handler处理程序一般处理由图底部的I / O线程生成的入站数据。 一般经过实际输入操做(例如SocketChannel.read(ByteBuffer))从远程读取入站数据。

出站事件由上下方向处理,如图右侧所示。 出站Handler处理程序一般会生成或转换出站传输,例如write请求。 I/O线程一般执行实际的输出操做,例如SocketChannel.write(ByteBuffer)。

在 Netty 中每一个 Channel 都有且仅有一个 ChannelPipeline 与之对应, 它们的组成关系以下:

 

 

一个 Channel 包含了一个 ChannelPipeline, 而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表, 而且每一个 ChannelHandlerContext 中又关联着一个 ChannelHandler。入站事件和出站事件在一个双向链表中,入站事件会从链表head日后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。

工做原理架构

初始化并启动Netty服务端过程以下:

 public static void main(String[] args) {
        // 建立mainReactor
        NioEventLoopGroup boosGroup = new NioEventLoopGroup();
        // 建立工做线程组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        final ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap 
                 // 组装NioEventLoopGroup 
                .group(boosGroup, workerGroup)
                 // 设置channel类型为NIO类型
                .channel(NioServerSocketChannel.class)
                // 设置链接配置参数
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.TCP_NODELAY, true)
                // 配置入站、出站事件handler
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) {
                        // 配置入站、出站事件channel
                        ch.pipeline().addLast(...);
                        ch.pipeline().addLast(...);
                    }
    });

        // 绑定端口
        int port = 8080;
        serverBootstrap.bind(port).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
            } else {
                System.err.println("端口[" + port + "]绑定失败!");
            }
        });
}

 

  • 基本过程以下:

  • 1 初始化建立2个NioEventLoopGroup,其中boosGroup用于Accetpt链接创建事件并分发请求, workerGroup用于处理I/O读写事件和业务逻辑

  • 2  基于ServerBootstrap(服务端启动引导类),配置EventLoopGroup、Channel类型,链接参数、配置入站、出站事件handler

  • 3 绑定端口,开始工做

结合上面的介绍的Netty Reactor模型,介绍服务端Netty的工做架构图:

 

 

server端包含1个Boss NioEventLoopGroup和1个Worker NioEventLoopGroup,NioEventLoopGroup至关于1个事件循环组,这个组里包含多个事件循环NioEventLoop,每一个NioEventLoop包含1个selector和1个事件循环线程。

每一个Boss NioEventLoop循环执行的任务包含3步:

  • 1 轮询accept事件

  • 2 处理accept I/O事件,与Client创建链接,生成NioSocketChannel,并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上 *3 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务,或者其它线程提交到该eventloop的任务。

每一个Worker NioEventLoop循环执行的任务包含3步:

  • 1 轮询read、write事件;

  • 2 处I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发生时进行处理

  • 3 处理任务队列中的任务,runAllTasks。

其中任务队列中的task有3种典型使用场景

  • 1 用户程序自定义的普通任务

  ctx.channel().eventLoop().execute(new Runnable() {
    @Override
    public void run() {
        //...
    }
});
  • 2 非当前reactor线程调用channel的各类方法 例如在推送系统的业务线程里面,根据用户的标识,找到对应的channel引用,而后调用write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中后被异步消费。

  • 3 用户自定义定时任务

ctx.channel().eventLoop().schedule(new Runnable() {
    @Override
    public void run() {

    }
}, 60, TimeUnit.SECONDS);

4 总结

如今稳定推荐使用的主流版本仍是Netty4,Netty5 中使用了 ForkJoinPool,增长了代码的复杂度,可是对性能的改善却不明显,因此这个版本不推荐使用,官网也没有提供下载连接。

Netty 入门门槛相对较高,实际上是由于这方面的资料较少,并非由于他有多难,你们其实均可以像搞透 Spring 同样搞透 Netty。在学习以前,建议先理解透整个框架原理结构,运行过程,能够少走不少弯路。

参考

Netty入门与实战:仿写微信 IM 即时通信系统

Netty官网

Netty 4.x学习笔记 - 线程模型

Netty入门与实战

理解高性能网络模型

Netty基本原理介绍

software-architecture-patterns.pdf

Netty高性能之道 —— 李林锋

《Netty In Action》

《Netty权威指南》

摘自:https://juejin.im/post/5b8f23b96fb9a019ec6a133d 贝聊科技