阿里二面:NIO为什么不适合文件上传场景、如何优雅解决

见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过技术分享实现职场蜕变,成长为RocketMQ社区优秀布道师、大厂资深架构师,出版《RocketMQ技术内幕》一书,在CSDN中记录了我的成长历程,欢迎大家关注我,随时可私信我,一起交流进步。

该系列已分别介绍了服务端、客户端的启动流程,本文将重点剖析Netty是如何封装NIO的读事件。

温馨提示:本文虽然是源码分析,但强烈建议精读,因为根据源码阐述其背后的设计哲学,也用黑体进行了标注,请特别留意。

在阅读本篇文章之前,请稍微思考如下几个问题:

  • NIO为什么不适合文件上传等场景
  • NIO如何避免一个超大数据传送的连接对其他请求的影响
  • NIO如何处理半关闭

1、读事件概述

关于Read事件在SocketChannel与ServerSocketChannel所对应的操作不一样,在SocketChannel中,则对应数据读,而在ServerSocketChannel中则被被封装成接受客户端的连接请求。

NIO read事件入口在NioEventLoop的processSelectedKey方法,截图如下:
在这里插入图片描述
其核心入口为UnSafe的read方法,关于UnSafe的类层次结构如下图所示:
在这里插入图片描述

  • AbstractNioByteChannel$NioByteUnsafe#read
    SocketChannel对应的读事件处理流程,即IO读的处理实现。
  • AbstractNioMessageChannel$NioMessageUnsafe#read
    ServerSocketChannle对应的读事件处理流程。

接下来将分别介绍这两个流程。

2、IO读事件从处理流程

IO读事件由AbstractNioByteChannel内部类AbstractNioUnsafe的read方法实现,接下来重点剖析该方法,从中窥探Netty对IO读事件的处理。
在这里插入图片描述
Step1:如果没有开启自动注册读事件,在每一次读时间处理过后会取消读事件,默认为自动注册。

温馨提示:如果通道不注册读事件,将无法从通道中读取数据,即无法处理请求或接受响应。

如果没有开启自动读事件,需要应用程序在需要的时候手动调用通道的read方法。

取消读事件,Netty基于NIO给出了非常标准的实现,基本可以当场模板代码使用:
在这里插入图片描述
其实现关键点:首先判断键值对是否有效,然后通过位运算进行取消注册。
在这里插入图片描述
Step2:创建接受缓存区内存分配器,这里有两个关键点:

  • maxMessagesPerRead
    每一个通道在一次读事件处理过程中最多可以调用底层Socket进行读取的次数,默认为16次,这里的设计哲学是避免一个通道需要读取太多的数据,从而影响其他通道的数据读,因为在一个事件选择器中多个通道的读事件是串行执行的
  • RecvByteBufAllocator
    接受缓冲区的内存分配策略,分为分配固定大小(不够时扩容)、动态变化(根据历史分配的大小,动态条件合适的内存大小),这里主要的设计哲学是合理利用内存,并减少扩容,提高内存的分配效率与使用效率

在这里插入图片描述
Step3:循环处理读事件,最多处理maxMessagePerRead。接下来探讨一下单次读事件的处理流程。
在这里插入图片描述
Step4:进行一次IO读处理,其处理有如下几个关键点:

  • 首先分配一个ByteBuf,俗称接收缓存区,用来存放从网络中读取的内容。
  • 获取一下分配到的累积缓存区可写的字节数,这个后面有妙用
  • 调用底层网络读API从网卡中读取数据,NIO的读取实现如下所示:
    在这里插入图片描述
    即调用NIO中的SocketChannel进行读数据,其返回参数表示这次从网卡中读取到的字节数。如果读取到的字节少于0,则表示对端通道已关闭,己端也需要进行相应的处理,例如关闭通道
  • 读到一批数据后,会通过事件传播机制向事件链中传播channelRead事件,触发后续对该批数据的处理。

在这里插入图片描述
Step5:判断该通道是否需要继续读,其基本依据如下:

  • 如果未开启自动注册读事件,读完一次之后将不再继续读取。
  • 如果本次读取到的字节数小于接收缓存区,说明此刻网卡中没有可读数据,等下一次读事件触发再继续读。

在这里插入图片描述
Step6:一次或多次读操作结束后,会触发一次读完成事件,向整个事件链传播。

整个网络读处理流程就介绍到这了。

3、接受连接处理流程

在Netty中,服务端接收客户端的连接请求(OP_ACCEPT),被封装在channelRead 事件中,其代码入口为:AbstractNioMessageChannel 的内部类NioMessageUnsafe的read方法。
在这里插入图片描述
其大概的实现要点在前面已经介绍,这里主要看一下NioServerSocketChannel的doReadMessage。
在这里插入图片描述
通过使用底层的NIO接受一个连接,并获取NioSocketChannel对象。然后继续该对象向下传播channelRead事件,在后续的处理器中对该对象进行操作,例如将其注册读事件,从而触发网络的读操作,关于NioSocketChannel如何绑定读事件、注册业务相关的事件监听器机制已经在Netty进阶:手把手教你如何编写一个NIO服务端中详细介绍,本文就不再重复。

好了,本文就介绍到这里了,想必对开头部门提出的问题有了自己的答案了吧,您的一键三连是对我最大的鼓励,当然可以加笔者微信:dingwpmz,备注CSDN,共同交流探讨。


为了方便大家学习Netty,笔者将RocketMQ的网络通信模块抽取出一个通用的Netty开发框架,大家可以从github上下载,堪称Netty界最强Hello World。
在这里插入图片描述
下载链接:Netty通用开发框架github地址

中间件兴趣圈 CSDN认证博客专家 RocketMQ 资深架构师 中间件兴爱好者
微信搜一搜【中间件兴趣圈】,回复关键字[PDF]可获取大量学习资料。《RocketMQ技术内幕》作者、CSDN2020博客之星第二名。目前就职于中通快递研发中心担任资深架构师,负责消息中间件与全链路压测的实施与落地。欢迎大家加我个人微信:dingwpmz,拉您入技术交流群,共同发展,抱团取暖。擅长JAVA编程,对主流中间件RocketMQ、Dubbo、ElasticJob、Netty、Sentienl、Mybatis、Mycat等中间件有深入研究。
相关推荐
©️2020 CSDN 皮肤主题: 成长之路 设计师:Amelia_0503 返回首页
实付 79.00元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值