NIO界最强“Hello World”,不服来辩!!!

简介: NIO界最强“Hello World”,不服来辩!!!

1、主从多Reactor模型


不知道大家有没有发现面向网络编程的框架都非常突出线程模型,其实也不难理解,因为网络编程是IO密集型,合理使用线程对提高性能有着非常显著的作用。


在网络编程模型中,有一个非常经典的线程模型:主从多Reactor模型,其示意图如下所示:

3eb7a946c84cef98e3788c70dd43cae9.png

我们首先简单介绍一下上图中涉及的几个重要角色:


  • Acceptor
    请求接收者,在实践时其职责类似服务器,并不真正负责连接请求的建立,而只将其请求委托 Main Reactor 线程池来实现,起到一个转发的作用。
  • Main Reactor
    主 Reactor 线程组,主要负责连接事件,并将IO读写请求转发到 SubReactor 线程池。当然在一些需要对客户端进行权限控制等场景下,权限校验的职责可以放到 Main Reactor 线程池,即 Main Reactor 也可以注册通道的读写事件,读取客户端权限校验相关的数据包,执行权限验证,权限验证通过后再将2通道注册到IO线程。
  • Sub Reactor
    Main Reactor 通常监听客户端连接后会将通道的读写转发到 Sub Reactor 线程池中一个线程(负载均衡),负责数据的读写。在 NIO 中 通常注册通道的读(OP_READ)、写事件(OP_WRITE)。


上面的图非常经典,除了介绍了线程模型相关知识外,其实还罗列出了网络通信的核心步骤:


  • 编码/解码(通信协议)
  • 认证授权
  • 网络读、写
  • 业务逻辑处理线程池


上述的线程模型非常经典,那我们能否基于NIO,来模拟实现上面的线程模型呢?


2、基于NIO实现主从多Reactor模型


我听一个技术圈大佬对我说过这样一句话:NIO是一名程序员进阶的第一个门槛,也是必需跨过的门槛。


笔者将尝试用NIO实现Reactor模型,成为NIO界最强的"Hello Wold"


为了接下来的代码进行展示与讲解,首先先罗列出其核心类图:

dce7e87e6078d09c870d2580c0f5541f.png

各个类的职责说明情况如下:


  • Acceptor
    服务端接收器,主要绑定端口,将ServerSocketChannel转发到MainReactor,及主从多Reactor模型中的主Reactor中。如果服务端绑定多个端口,则会创建多个ServerSocketChannel,但通常情况只会绑定到一个端口。
  • MainReactor
    主Reactor,主要承担接受客户端的连接请求,即主要负责OP_ACCEPT事件的处理,并将创建的连接(SocketChannel)转发到从Reactor线程组,由其处理读写事件
  • SubReactorThreadGroup
    从Reactor线程组,维护多个Reactor线程,并提供负载均衡。
  • SubReactorThread
    从Reactor,主要负责IO读写,俗称IO线程。


在上源码之前我们再来看一下整个示例到时序图:

26cdeff63481bad6d5297d620e3ae5d6.png


核心的关键点如下:


  • Acceptor主要是创建ServerSocketChannel,然后转发给MainReactor
  • MainReactor中维护一个NIO Selector事件选择器,注册OP_ACCEPT事件,每处理一个OP_ACCEPT,表示一个新的客户端连接,在服务端会创建一个SocketChannel对象,接着将该对象转发到从Reactor
  • SubReactorThreadGroup
    主从多Reactor模型中的从Reactor组,包含多个从Reactor线程,新的连接会进行负载均衡,选择其中一个Reactor来处理特定的SocketChannel。
  • SubReactorThread
    从Reactor线程,会维护一个Nio Selector,会将SocketChannel注册到该事件选择器,处理读、写请求,一个Selector中会包含多个Channel(SocketChannel),但一个SocketChannel在其生命周期中只会注册到一个Selector,这样可以简化处理模型。


程序员希望不服就干,接下来我将展示基于NIO实现多Reactor的代码,应该是全网目前第一个直接用代码实现。


温馨提示:为了便于排版,本文中的代码统一用截图进行展示,代码获取方式:私信回复 reactorcode 即可获取。


2.1 Acceptor代码实现


Acceptor代码实现如下:

597a5bbdc537e7ea5f4c376db23998ef.png

Acceptor代码实现比较简单,就是利用NIO提供的API创建ServerSocketChannel,并设置为非阻塞模式,并调用bind方法绑定端口,然后转发到MainReactor中。


2.2 MainReactor代码实现


MainReactor代码实现如下图所示:

14a1d71423d4dc6d6e6ae1a6da52d828.png


代码实现要点:


  • 创建Selector选择器对象,并注册OP_ACCEPT事件,用于监听客户端的链接
  • 创建从Reactor线程组
  • NIO事件选择经典使用方法,通过循环调用selector的select方法,此方法会返回当前就绪的事件,例如读事件就绪表明此时调用该通道的网络读API,一定会返回有效数据
  • 如果当前事件是OP_ACCEPT,则调用ServerSocketChannel的accept方法,创建一个SocketChannel对象,并转发到从Reactor线程组中。


2.3 SubReactorThreadGroup代码实现


SubReactorThreadGroup的角色是充当从Reactor线程组,其代码实现如下图所示:

0f17b8a4a21befd06be716bd6077e01a.png

主要的关键点如下:


  • 内部持有一个业务线程池,
  • 用于执行业务逻辑,可类比Dubbo中的用于执行服务提供这各个Service的业务代码
  • 通过next()进行负载均衡,选择一个从Reactor。
  • 这里使用了NioTask,主要是因为Selector虽然是线程安全的,但其内部持有的Key Set是线程不安全的,故这里为保证Selector的现线程安全性,selector的任何方法只会在从Reactor线程中执行,这里只是添加到任务队列中,最终会由从Reactor线程去执行。


2.4 SubReactorThread代码实现


该类是从Reactor到核心逻辑,接下来将详细介绍其实现。

813659d538fdc8bbd80f1468bc0b6b63.png

主要是提供增加IO读写任务(NioTask)添加到任务队列中,并在执行业务逻辑时转发到业务线程池


接下来详细介绍SubReactorThread到run方法实现,重点关注读写事件的处理流程。


83bf837a3ddc35baba0615c9ada65a97.png

Step1:NIO事件处理的核心要点:


  • 事件选择器的常用套路,调用select进行事件就绪选择,然后遍历就绪集合。
  • 如果事件类型为写事件,直接将数据通过调用通道的write方法将数据写入到网络,这里的核心关键点:

调用SocketChannel的write方法会返回本次写入的字节数,如果等于0,并且数据还未全部写入,则说明写入缓存区已满,需要再次注册写事件

  • 如果事件类型为读事件,调用SocketChannel的read方法将从网络中读取数据,这里有几个侧重点需要说明:
  • 一次read读取有可能并没有完整夺取一个请求,该方法可以调用多次,当返回0时可以停止继续读取。
  • read方法将数据读取到ByteBuffer(缓存区),如何从缓存区中读取一个完整的请求是关键,因为该缓存区中可能包含一个请求,也可以包含多个。这里的解决方案就是:编码、解码,关于这部分内容,大家可以参考文章:https://mp.weixin.qq.com/s/GDyG7wWLU5YijKvhFwljXg
  • 经过解码,从请求流中解码一个请求后,将请求转发到业务线程池。

0cb07147e7c5a7ca8a3b02b2ca067357.png

处理完事件选择相关的任务后,开始执行应用程序的任务,例如读事件注册、写操作。这里的关键点是调用SocketChannel的write方法会返回本次写入的字节数,如果等于0,并且数据还未全部写入,则说明写入缓存区已满,需要再次注册写事件,否则应该取消写事件


2.5 代码获取


代码不利于排版,故本文中的代码全部使用截图


相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
14天前
|
安全 Java 数据库连接
【Java每日一题】——第四十一题:编写程序描述影视歌三栖艺人。
【Java每日一题】——第四十一题:编写程序描述影视歌三栖艺人。
41 0
|
14天前
|
算法 Java 程序员
火爆Boss直聘的2023最牛字节Java面试手册!助你狂拿千份offer!
当下程序员现状 根据一些调查报告,可以了解到当下程序员的现状。 首先,从年龄分布来看,年轻的程序员占据了主导地位。 30岁以下的开发者占比最高,为81%,而40岁以上的开发者仅占3%。 这意味着,程序员这个行业在一定程度上是年轻化的,同时也面临着一些中年转行或者技术更新换代的问题。 在性别方面,男性程序员的比例在90%以上,女性程序员的比例较低。 这可能和传统观念中将程序员视为男性职业有关。然而,随着技术的普及和女性对计算机科学的兴趣逐渐提高,女性程序员的比例也在逐渐增加。 从职业发展来看,程序员的职业发展相对较慢。 虽然程序员的薪资普遍较高,但是工作压力也很大,需要不断学习和更
102 0
|
14天前
|
消息中间件 Dubbo Java
24年国内头条最牛的Java面试八股文1000集,不接受反驳!
年后这个时间段, 找工作面试不要停!! 很多朋友据我了解,技术水平和工作经验都很不错,但是面试频频败北。 大家复盘下来发现问题不严重,但是很普遍,10个人里面8个都存在,那就是面试前不做准备。 技巧和避坑先不论,面试题型就不熟悉,没有系统过下大厂真题和必问项目,真正对线上面试官时被打的措手不及。 想要从容应对,就要提前建立把握和自信,这不但来自自身的技术能力水平,更来源于对面试时将要发生的各种情况有预判,做到心中有数。 这里整理了一套跳槽涨薪大厂Java知识点解析及面试题解析,涵盖20个技术栈的大厂面试题及详解文档,各大厂技术重点、面试难点、进阶要点,帮助大家“临阵磨枪”,如有需要的
|
6月前
|
设计模式 架构师 Java
我说想去京东面架构师,阿里表哥手甩我Java大厂技术题,让我滚蛋
8月来了,也是面试准备和冲刺的高峰期了,这里必须要和大家再强调一下要准备的7大方面!总结起来包括:1至2门你最熟悉的编程语言+数据结构和算法题+计算机网络+操作系统+设计模式+数据库+开发框架。
|
8月前
|
Dubbo Java 关系型数据库
解决90%BAT大厂!京东在职大佬实测总汇“java面试真题”颠覆认知
金九银十的高峰已经到来,但现在没找到满意工作的小伙伴还是有很多。包括我的朋友因为工作上的一些小问题毅然决然的裸辞来,结果现在的就业环境弄的到现在都没找到一份合适的。面试也是一波又一波的把他打倒了。
|
存储 缓存 安全
破4!《我想进大厂》之Java基础夺命连环16问
说好了面试系列已经完结了,结果发现还是真香,嗯,以为我发现我的Java基础都没写,所以这个就算作续集了,续集第一篇请各位收好。 等到你们收到这篇文章的时候,公众号读者数量就破4000了,可不是4万,就庆祝下,存稿都发出来了,下周又得肝了!
破4!《我想进大厂》之Java基础夺命连环16问
|
算法 Java 测试技术
六十六、备战蓝桥杯-第十三届模拟赛(Java组)
六十六、备战蓝桥杯-第十三届模拟赛(Java组)
六十六、备战蓝桥杯-第十三届模拟赛(Java组)
程序人生 - 996(三)马云长文再谈“996”:和被剥削没关系,现在的人不傻
程序人生 - 996(三)马云长文再谈“996”:和被剥削没关系,现在的人不傻
113 0
|
存储 安全 算法
万字长文丨7个经典问题,助你拿下Java面试(建议收藏)
万字长文丨7个经典问题,助你拿下Java面试(建议收藏)
J3
|
Java
【西行 - 大圣闹天宫】 Java 流程控制
不论是哪种编程语言,都会提供两种基本的流程控制结构:分支结构和循环结构。 分支结构用于实现根据条件选着性的执行某段代码,循环结构则是用于实现循环条件重复执行某段代码。 Java 中同样提供了这两种流程控制语法: 分支语句:if、switch。 循环语句:while、do while、for 以及JDK1.5 后的 foreach循环。
J3
94 0
【西行 - 大圣闹天宫】 Java 流程控制

热门文章

最新文章