Netty原理剖析

Netty原理剖析1Netty 简介 Netty 是一个高性能 异步事件驱动的 NIO 框架 基于 JAVANIO 提供的 API 实现 本文简单聊一下 Netty 的实现原理

1. Netty简介

Netty是一个高性能、异步事件驱动的NIO框架,基于JAVA NIO提供的API实现。它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。 作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。

2. Netty线程模型

在JAVA NIO方面Selector给Reactor模式提供了基础,Netty结合Selector和Reactor模式设计了高效的线程模型。先来看下Reactor模式:

2.1 Reactor模式

Wikipedia这么解释Reactor模型:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。首先Reactor模式首先是事件驱动的,有一个或者多个并发输入源,有一个Server Handler和多个Request Handlers,这个Service Handler会同步的将输入的请求多路复用的分发给相应的Request Handler。可以如下图所示:

这里写图片描述

从结构上有点类似生产者和消费者模型,即一个或多个生产者将事件放入一个Queue中,而一个或者多个消费者主动的从这个队列中poll事件来处理;而Reactor模式则没有Queue来做缓冲,每当一个事件输入到Service Handler之后,该Service Handler会主动根据不同的Evnent类型将其分发给对应的Request Handler来处理。

2.2 Reator模式的实现

关于Java NIO 构造Reator模式,Doug lea在《Scalable IO in Java》中给了很好的阐述,这里截取PPT对Reator模式的实现进行说明

这是最简单的Reactor单线程模型,由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会被阻塞,理论上一个线程可以独立处理所有的IO操作。这时Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分发请求到处理链中。

对于一些小容量应用场景,可以使用到单线程模型。但对于高负载,大并发的应用却不合适,主要原因如下:

  1. 当一个NIO线程同时处理成百上千的链路,性能上无法支撑,即使NIO线程的CPU负荷达到100%,也无法完全处理消息
  2. 当NIO线程负载过重后,处理速度会变慢,会导致大量客户端连接超时,超时之后往往会重发,更加重了NIO线程的负载。
  3. 可靠性低,一个线程意外死循环,会导致整个通信系统不可用

为了解决这些问题,出现了Reactor多线程模型。

相比上一种模式,该模型在处理链部分采用了多线程(线程池)。

在绝大多数场景下,该模型都能满足性能需求。但是,在一些特殊的应用场景下,如服务器会对客户端的握手消息进行安全认证。这类场景下,单独的一个Acceptor线程可能会存在性能不足的问题。为了解决这些问题,产生了第三种Reactor线程模型

该模型相比第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接;并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。

2.3 Netty模型

2.2中说完了Reactor的三种模型,那么Netty是哪一种呢?其实Netty的线程模型是Reactor模型的变种,那就是去掉线程池的第三种形式的变种,这也是Netty NIO的默认模式。Netty中Reactor模式的参与者主要有下面一些组件:

  1. Selector
  2. EventLoopGroup/EventLoop
  3. ChannelPipeline

Selector即为NIO中提供的SelectableChannel多路复用器,充当着demultiplexer的角色,这里不再赘述;下面对另外两种功能和其在Netty之Reactor模式中扮演的角色进行介绍。

3.EventLoopGroup/EventLoop

当系统在运行过程中,如果频繁的进行线程上下文切换,会带来额外的性能损耗。多线程并发执行某个业务流程,业务开发者还需要时刻对线程安全保持警惕,哪些数据可能会被并发修改,如何保护?这不仅降低了开发效率,也会带来额外的性能损耗。

为了解决上述问题,Netty采用了串行化设计理念,从消息的读取、编码以及后续Handler的执行,始终都由IO线程EventLoop负责,这就意外着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险。这也解释了为什么Netty线程模型去掉了Reactor主从模型中线程池。

EventLoopGroup是一组EventLoop的抽象,EventLoopGroup提供next接口,可以总一组EventLoop里面按照一定规则获取其中一个EventLoop来处理任务,对于EventLoopGroup这里需要了解的是在Netty中,在Netty服务器编程中我们需要BossEventLoopGroup和WorkerEventLoopGroup两个EventLoopGroup来进行工作。通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程,也就是说BossEventLoopGroup的线程数参数为1。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO处理。

EventLoop的实现充当Reactor模式中的分发(Dispatcher)的角色。

4.ChannelPipeline

ChannelPipeline其实是担任着Reactor模式中的请求处理器这个角色。

ChannelPipeline的默认实现是DefaultChannelPipeline,DefaultChannelPipeline本身维护着一个用户不可见的tail和head的ChannelHandler,他们分别位于链表队列的头部和尾部。tail在更上层的部分,而head在靠近网络层的方向。在Netty中关于ChannelHandler有两个重要的接口,ChannelInBoundHandler和ChannelOutBoundHandler。inbound可以理解为网络数据从外部流向系统内部,而outbound可以理解为网络数据从系统内部流向系统外部。用户实现的ChannelHandler可以根据需要实现其中一个或多个接口,将其放入Pipeline中的链表队列中,ChannelPipeline会根据不同的IO事件类型来找到相应的Handler来处理,同时链表队列是责任链模式的一种变种,自上而下或自下而上所有满足事件关联的Handler都会对事件进行处理。

ChannelInBoundHandler对从客户端发往服务器的报文进行处理,一般用来执行半包/粘包,解码,读取数据,业务处理等;ChannelOutBoundHandler对从服务器发往客户端的报文进行处理,一般用来进行编码,发送报文到客户端。

关于Pipeline的更多知识可参考:浅谈管道模型(Pipeline)

5.Buffer

Netty提供的经过扩展的Buffer相对NIO中的有个许多优势,作为数据存取非常重要的一块,我们来看看Netty中的Buffer有什么特点。

1.ByteBuf读写指针

  • 在ByteBuffer中,读写指针都是position,而在ByteBuf中,读写指针分别为readerIndex和writerIndex,直观看上去ByteBuffer仅用了一个指针就实现了两个指针的功能,节省了变量,但是当对于ByteBuffer的读写状态切换的时候必须要调用flip方法,而当下一次写之前,必须要将Buffe中的内容读完,再调用clear方法。每次读之前调用flip,写之前调用clear,这样无疑给开发带来了繁琐的步骤,而且内容没有读完是不能写的,这样非常不灵活。相比之下我们看看ByteBuf,读的时候仅仅依赖readerIndex指针,写的时候仅仅依赖writerIndex指针,不需每次读写之前调用对应的方法,而且没有必须一次读完的限制。

2.零拷贝

  • Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
  • Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
  • Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。

3.引用计数与池化技术

  • 在Netty中,每个被申请的Buffer对于Netty来说都可能是很宝贵的资源,因此为了获得对于内存的申请与回收更多的控制权,Netty自己根据引用计数法去实现了内存的管理。Netty对于Buffer的使用都是基于直接内存(DirectBuffer)实现的,大大提高I/O操作的效率,然而DirectBuffer和HeapBuffer相比之下除了I/O操作效率高之外还有一个天生的缺点,即对于DirectBuffer的申请相比HeapBuffer效率更低,因此Netty结合引用计数实现了PolledBuffer,即池化的用法,当引用计数等于0的时候,Netty将Buffer回收致池中,在下一次申请Buffer的没某个时刻会被复用。

总结

Netty其实本质上就是Reactor模式的实现,Selector作为多路复用器,EventLoop作为转发器,Pipeline作为事件处理器。但是和一般的Reactor不同的是,Netty使用串行化实现,并在Pipeline中使用了责任链模式。

Netty中的buffer相对有NIO中的buffer又做了一些优化,大大提高了性能。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211905.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午9:19
下一篇 2026年3月18日 下午9:19


相关推荐

  • JAVA常用API整理

    JAVA常用API整理欢迎转载,转载请注明出处:https://blog.csdn.net/u013547284/article/details/71158100一、JAVA常用APIjava.lang.Math提供sin,cos,tan,exp,log,log10等类方法,PI和E等类字段java.lang.String(StringBuilder线程不安全,StringBuffer线程安…

    2022年7月26日
    9
  • nopcommerce mysql_nopCommerce如何支持MySQL

    nopcommerce mysql_nopCommerce如何支持MySQL此方法支持 nopCommerce2 4 以上版本 缺少的代码 可参照 nopCommerce2 6 源码 在工程 Easy Data 中 1 添加 MySqlConnect 和 MySqlDataPro 在 Easy Data 目录下添加两个 Class MySqlConnect 和 MySqlDataPro MySqlConnect using

    2026年3月19日
    2
  • python字符串转换成数字_python 字符与数字如何转换[通俗易懂]

    展开全部一、python中字符串转换成数字1、类中进行导入:importstring,str=’555’,num=string.atoi(str),num即为str转换成的数字转换为浮点数:string.atof(str)2、直接62616964757a686964616fe59b9ee7ad9431333365666261intint(str)即可。二、数字转换成字符串num=322,str…

    2022年4月18日
    89
  • IEC104规约流程

    IEC104规约流程该规约在DF8900中规约号为104,在DF8002或DF1800系统中规约号一般为99,但也有个别现场因实际情况而不同.97版或2002版IEC104在流程上没有什么变化,只是扩展了遥测遥信等信息体基地址..索引项目部分报文字节个数参数地址范围流程常用类型标识

    2022年6月20日
    50
  • rd,pe,ae,fae,marketing,sales都是干嘛的?

    rd,pe,ae,fae,marketing,sales都是干嘛的?一 ae 应用工程师 fae 场应用工程师 marketing 市场 sales 销售 ae 和 fae 是 ic 设计之后 ae 偏向 ic 设计 fae 偏向市场多点二 PE productengin 协同 IC 设计工程师定义 DFT designfortes 流片后对 IC 进行功能验证与 debug 根据 ICfunctionsp 来定义 IC 在晶圆级 颗粒级甚至系统级的

    2026年3月18日
    2
  • pycharm配置vue的问题

    pycharm配置vue的问题我在为 pycharm 配置 vue 编译环境时遇到 EditConfigur 界面如下 这里 Name 可以随便起 package json 填写项目的 package json 地址 Command 默认为 run 其中 Scripts 在填写时遇到了问题 我参考的网址上注明要写 server 相当于使用 npmrunserver 启动项目 但是我在编译时会报错 后来发现应该填写的是 dev 即相当于使用命令 npmrundev 启动项目 原来这里填写的 server 还是 dev 是与项目在 pack

    2026年3月17日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号