深入分析 Netty Reactor 线程模型的实现原理
以生活中场景为例,类比说明 Netty 各核心组件的原理、交互流程
详细分析 Netty 核心源码流程
“ Netty是 一个异步的,事件驱动的网络应用程序框架,基于Java Nio的Api实现 ... ”
定义较长,看完估计懂的人感觉没营养,不懂的人依然茫然;不妨换个角度理解下。
大部分同学对 Spring Mvc、Mybatis 框架很熟悉,对其所实现功能、提供哪些 Api 都了然于大脑。
Netty 框架与 Java Nio 之间的关系, 类似于 Spring Mvc 框架与 Java Servlet 、Mybatis 框架与 Jdbc 之间的关系, 框架会让对应的功能开发更简洁、高效。
基于 Spring Mvc 框架开发 web 服务端应用,通常只需开发出 Controller、通用拦截器、参数/返回值解析器(框架有已提供多个选项)。
基于 Netty 框架开发 Nio 服务端应用,通常也只需开发出请求处理器、通用过滤器、编/解码器(框架有已提供多个选项)。
Netty 与 Spring Mvc 工作层次和原理很不一样,以上只是从框架对功能封装角度进行类比。
假设你是老板,需要开家电话客服公司,提供的服务很简单:接电话并与用户沟通。
你为了更高效地服务大量用户,准备设置两个团队A和B,职责分派如下:
A团队:守着呼叫总台,接听并转交给B团队某一个人的电话
B团队:守着自己的工位电话,记录转交来的用户电话号码并与用户进行沟通
A团队活少,理论上可多个人干,你公司初期接听业务量不大,一个人即可;
B团队活多,理论上一个人干也行,通常会多个人一起干(按公司业务量分配)。
在开工前你准备好办公场所,为了节省人力成本,你分别告诉A和B职位的中介,等有活时在招聘对应人过来,一旦人招聘来之后,就按对应工位的工作职责夜以继日地干活。
等一切就绪之后,你给A团队工位1扔去第一个任务(开机)并告诉中介A找人干活,然后就坐到一边休息去了。
A1员工开启呼叫台后,就能接到广大用户的咨询电话。当来电话后,A1员工将对应呼叫信息包装成任务【和老板的沟通方式一致】扔给B团队中的某一个工位任务表,若工位没人则告诉中介B找人干活。
【故事 End】
故事核心角色有 老板、A团队员工、A团队人力中介、B团队员工、B团队人力中介,重点要理解各角色的职责、交互方式(若已阅读过源码应该能对应到netty的各个组件)。
AB团队的员工在处理大多数任务时,会按照固定的流程顺序或倒序执行,下文用 Netty 组件描述,暂不用故事描述了(否则题主讲的难受,读者看的更难受)。
BossGroup(A团队)主要处理连接事件,各组件间关系,核心交互流程如下:
ThreadPreTaskExecutor:每次执行任务会创建新线程执行任务,NioEventLoopGroup 持有该线程池属性;
NioEventLoopGroup:是线程池组或线程池集群,启动 Netty server 的线程(幕后老板)提交任务到 bossGroup,bossGroup 轮询选择一个 NioEventLoop 并提交任务到任务队列中;
NioEventLoop:仅有一个线程的线程池,当往线程池第一次提交任务时,通过 ThreadPreTaskExecutor 创建线程并赋值给 NioEventLoop.thread 属性,并且持有 nio selector;
连接 op_accept 事件 select 出之后,会交给 NioServerSocketChannel 的内部 Unsafe 对象进行 read(op_accept事件读取的是连接),之后会通过 pipeline 方法传播 channelRead 事件;
Channel:Netty server 程序中有 NioServerSocketChannel (端口监听 channel ),NioSocketChannel (与 client 通讯 channel );
各自封装了 jdk nio channel,扩展了 bind、connect、register、close、read、write 等 io 事件;其内部类 UnSafe 主要用于和 jdk nio 中的类进行交互;
ChannelPipeline:与 channel 对象相互引用 ,内部包装了 ChannelHandler 链,类似于过滤器中的 FilterChain。
workGroup 读取 client 消息与 bossGroup 读取 client 连接流程基本一致,内容不一样而已。
业务 handler 处理完请求后通常会调用 netty Channel 的 writeAndFlush 方法,再经过 ChannelPipeline 中的 ChannelHandler 链传播到内部 Unsafe 对象,最终调用 javachannel 的 write 方法写出响应给 client。
read 消息,消息到网卡,经过 nio selector 后传播到应用程序的 ChannelHandler,从外向内传播(inbound 事件),对应用程序来说被动接收
jdkChannel(read到了消息) → unsafe → pipeline → head → businessHandler → tail
write 返回值,应用程序调用 channel.write 方法后传播到 Javachannel.write 写出到网卡并发送,从内向外传播(outbound 事件),是应用程序主动发起
nettychannel → pipeline → tail → businessHandler → head → unsafe → jdkChannel
inbound、outbound 唯一区别是事件传播方向不一样:
从 Java nio 层向应用程序层(Netty代码和用户代码)方法传播 是 inbound 事件
从应用程序层方法向 Java nio 层传播是 outbound 事件。
inbound 事件不一定都是由 client 发起,也可能是 Netty server 自身发起的。
如:channel 注册功能,从 Netty 框架代码经过 pipeline 传播到 Java nio 层属于 outbound 事件。当注册完成之后,Netty 框架会反方向(从Java nio层向应用程序层方法)传播 channelRegistered (inbound事件)。
ChannelOutboundInvoker :用于传播outbound事件(如:writeAndFlush)
ChannelInboundInvoker :用于传播Inbound事件 (如:fireChannelRead)
ChannelOutboundHandler :处理outbound事件业务逻辑(如:bind,write,connect)
ChannelInboundHandler :处理inbound事件逻辑 (如:channelActive,channelRead)
ChannelPipeline、ChannelHandlerContext 组件都实现了 ChannelOutboundInvoker 与 ChannelInboundInvoker,两者都可传递 inbound 和 outbound 事件。
Channel 组件只实现了 ChannelOutboundInvoker,若阅读到对应代码,可能会疑问:
为什么只实现了 ChannelOutboundInvoker?难道不能传播 inbound 事件?
通过上面流程得知,所有的 io 事件都会经过 Channel, 题主认为不实现 ChannelInboundInvoker 是因为用不到,原因如下:
inbound 事件是从 jdk nio 层向应用程序层传播,通过 javachannel 可以引用获取到 nettychannel 进而能引用到 unsafe 对象,直接调用 unsafe 或 pipeline 进行传播事件即可,netty 内部也是这么操作的。
outbound 事件是从应用程序层向 jdk nio 层传播,需要提供对应的 write、bind、close 等方法给调用方使用。
ChannelPipeline 中的 ChannelHandler 在被添加时会包装成 ChannelHandlerContext,具体的某一个 ChannelHandler 可只处理 inbound 或 outbound 事件。
但每个 ChannelHandlerContext 对象都具有传播 inbound 和 outbound 事件的能力,才能传播所有事件。
实现了 inbound 与 outbound 接口但强制设置 inbound 为 false(题主对这里理解还不成熟、暂不表述)。
head 比 tail 多了个 unsafe 属性,从调用层次来看 head 紧挨着 unsafe,事件传播时会相互调用。
finalclass HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler {
/**
* HeadContext比TailContext多了个 unsafe属性
*/
privatefinal Unsafe unsafe;
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, false(inbound), true(outbound));
unsafe = pipeline.channel().unsafe();
setAddComplete();
}
。。。
}
创建 NioServerSocketChannel
初始化 NioServerSocketChannel 参数
注册 channel 到 selector
绑定端口
端口绑定后,可接受客户端连接,并收发消息
注意 Main 线程、boss io 线程、worker io 线程之间的交互
代码流程较长、建议在电脑端对着源码阅读
三翼鸟数字化技术平台-智能运营平台团队主要以用户行为数据为基础,利用推荐引擎为用户提供“千人千面”的个性化推荐服务,改善用户体验,持续提升核心业务指标。通过构建高效、智能的线上运营系统,全面整合数据资产,实现数据分析-人群圈选-用户触达-后效分析-策略优化的运营闭环,并提供可视化报表,一站式操作提升数字化运营效率。