引用:

https://www.cnblogs.com/TomSnail/p/6158249.html

https://www.cnblogs.com/heavenhome/articles/6554262.html

https://my.oschina.net/andylucc/blog/614295

1.3 Reactor

Reactor是一个同步的I/O多路复用模型,它没有Proactor模式那么复杂,原理图如下:

  • 用户发起IO操作到事件分离器
  • 事件分离器调用相应的处理器处理事件
  • 事件处理完成,事件分离器获得控制权,继续相应处理

1.4 Proactor和Reactor的比较

  • Reactor模型简单,Proactor复杂
  • Reactor是同步处理方式,Proactor是异步处理方式
  • Proactor的IO事件依赖操作系统,操作系统须支持异步IO
  • 同步与异步是相对于服务端与IO事件来说的,Proactor通过操作系统异步来完成IO操作,当IO完成后通知事件分离器,而Reactor需要自己完成IO操作

2 Reactor多线程模型

前面已经简单介绍了Proactor和Reactor模型,在实际中Proactor由于需要操作系统的支持,实现的案例不多,有兴趣的可以看一下Boost Asio的实现,我们主要说一下Reactor模型,Netty也是使用Reactor实现的。

但单线程的Reactor模型每一个用户事件都在一个线程中执行:

  • 性能有极限,不能处理成百上千的事件
  • 当负荷达到一定程度时,性能将会下降
  • 单某一个事件处理器发送故障,不能继续处理其他事件

2.1 多线程Reactor

使用线程池的技术来处理I/O操作,原理图如下:

  • Acceptor专门用来监听接收客户端的请求
  • I/O读写操作由线程池进行负责
  • 每个线程可以同时处理几个链路请求,但一个链路请求只能在一个线程中进行处理

2.2 主从多线程Reactor

在多线程Reactor中只有一个Acceptor,如果出现登录、认证等耗性能的操作,这时就会有单点性能问题,因此产生了主从Reactor多线程模型,原理如下:

  • Acceptor不再是一个单独的NIO线程,而是一个独立的NIO线程池
  • Acceptor处理完后,将事件注册到IO线程池的某个线程上
  • IO线程继续完成后续的IO操作
  • Acceptor仅仅完成登录、握手和安全认证等操作,IO操作和业务处理依然在后面的从线程中完成

3 Netty中Reactor模型的实现

Netty同时支持Reactor的单线程、多线程和主从多线程模型,在不同的应用中通过启动参数的配置来启动不同的线程模型。

通过线程池的线程个数、是否共享线程池方式来切换不同的模型

3.1 Netty中的Reactor模型

Netty中的Reactor模型如下图:

  • Acceptor中的NioEventLoop用于接收TCP连接,初始化参数
  • I/O线程池中的NioEventLoop异步读取通信对端的数据报,发送读事件到channel
  • 异步发送消息到对端,调用channel的消息发送接口
  • 执行系统调用Task
  • 执行定时Task

3.2 NioEventLoop

NioEventLoop是Netty的Reactor线程,它在Netty Reactor线程模型中的职责如下:

1. 作为服务端Acceptor线程,负责处理客户端的请求接入
2. 作为客户端Connecor线程,负责注册监听连接操作位,用于判断异步连接结果
3. 作为IO线程,监听网络读操作位,负责从SocketChannel中读取报文
4. 作为IO线程,负责向SocketChannel写入报文发送给对方,如果发生写半包,会自动注册监听写事件,用于后续继续发送半包数据,直到数据全部发送完成

如下图,是一个NioEventLoop的处理链:

  • 处理链中的处理方法是串行化执行的
  • 一个客户端连接只注册到一个NioEventLoop上,避免了多个IO线程并发操作

3.2.1 Task

Netty Reactor线程模型中有两种Task:系统Task和定时Task
  • 系统Task:创建它们的主要原因是,当IO线程和用户线程都在操作同一个资源时,为了防止并发操作时锁的竞争问题,将用户线程封装为一个Task,在IO线程负责执行,实现局部无锁化
  • 定时Task:主要用于监控和检查等定时动作

基于以上原因,NioEventLoop不是一个纯粹的IO线程,它还会负责用户线程的调度

单线程模型:

private EventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(group)
.childHandler(new HeartbeatInitializer());

  

多线程模型:

private EventLoopGroup boss = new NioEventLoopGroup(1);
private EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(boss,work)
.childHandler(new HeartbeatInitializer());

  

主从多线程:

private EventLoopGroup boss = new NioEventLoopGroup();
private EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
                .group(boss,work)
                .childHandler(new HeartbeatInitializer());

  

 

3.2.2 IO线程的分配细节

线程池对IO线程进行资源管理,是通过EventLoopGroup实现的。线程池平均分配channel到所有的线程(循环方式实现,不是100%准确),一个线程在同一时间只会处理一个通道的IO操作,这种方式可以确保我们不需要关心同步问题。

3.2.3 Selector

NioEventLoop是Reactor的核心线程,那么它就就必须实现多路复用。

Selector的过程如下:

  • 首先oldWakenUp = wakenUp.getAndSet(false)
  • 如果队列中有任务, selectNow()
  • 如果没有select(),直达channel准备就绪,但此过程中循环次数超过限值也将rebuidSelectoror退出循环
  • 执行processSelectedKeys和runAllTasks

epoll-bug的处理

在netty中对java nio的epoll bug进行了处理,就是设置一个阀值,如果超过了就rebuidSelector来避免epoll()死循环

3.2.4 NioEevntLoopGroup

EventExecutorGroup:提供管理EevntLoop的能力,他通过next()来为任务分配执行线程,同时也提供了shutdownGracefully这一优雅下线的接口

EventLoopGroup继承了EventExecutorGroup接口,并新添了3个方法

  • EventLoop next()
  • ChannelFuture register(Channel channel)
  • ChannelFuture register(Channel channel, ChannelPromise promise)

EventLoopGroup的实现中使用next().register(channel)来完成channel的注册,即将channel注册时就绑定了一个EventLoop,然后EvetLoop将channel注册到EventLoop的Selector上。

NioEventLoopGroup还有几点需要注意:

  • NioEventLoopGroup下默认的NioEventLoop个数为cpu核数 * 2,因为有很多的io处理
  • NioEventLoop和java的single线程池在5里差异变大了,它本身不负责线程的创建销毁,而是由外部传入的线程池管理
  • channel和EventLoop是绑定的,即一旦连接被分配到EventLoop,其相关的I/O、编解码、超时处理都在同一个EventLoop中,这样可以确保这些操作都是线程安全的

如上图,BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护着一个注册了ServerSocketChannel的Selector(boss)实例,BoosEventLoop不断轮询Selector将连接事件分离出来,通常是OP_ACCEPT事件,然后将accept得到的SocketChannel交给WorkerEventLoopGroup,WorkerEventLoopGroup会由next选择其中一个EventLoopGroup来将这个SocketChannel注册到其维护的Selector[work eventloop]并对其后续的IO事件进行处理。在Reactor模式中BossEventLoopGroup主要是对多线程的扩展,而每个EventLoop的实现涵盖IO事件的分离,和分发(Dispatcher)。

另一个版本解释:

引用自: https://www.infoq.cn/article/netty-threading-model?utm_source=infoq&utm_medium=popular_links_homepage

1.2. Reactor 模型

无论是 C++ 还是 Java 编写的网络框架,大多数都是基于 Reactor 模式进行设计和开发,Reactor 模式基于事件驱动,特别适合处理海量的 I/O 事件。

1.2.1. 单线程模型

Reactor 单线程模型,指的是所有的 IO 操作都在同一个 NIO 线程上面完成,NIO 线程的职责如下:

1)作为 NIO 服务端,接收客户端的 TCP 连接;

2)作为 NIO 客户端,向服务端发起 TCP 连接;

3)读取通信对端的请求或者应答消息;

4)向通信对端发送消息请求或者应答消息。

Reactor 单线程模型示意图如下所示:

图 1-1 Reactor 单线程模型

由于 Reactor 模式使用的是异步非阻塞 IO,所有的 IO 操作都不会导致阻塞,理论上一个线程可以独立处理所有 IO 相关的操作。从架构层面看,一个 NIO 线程确实可以完成其承担的职责。例如,通过 Acceptor 类接收客户端的 TCP 连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户线程可以通过消息编码通过 NIO 线程将消息发送给客户端。

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

1)一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的 CPU 负荷达到 100%,也无法满足海量消息的编码、解码、读取和发送;

2)当 NIO 线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO 线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;

3)可靠性问题:一旦 NIO 线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。

为了解决这些问题,演进出了 Reactor 多线程模型,下面我们一起学习下 Reactor 多线程模型。

1.2.2. 多线程模型

Rector 多线程模型与单线程模型最大的区别就是有一组 NIO 线程处理 IO 操作,它的原理图如下:

图 1-2 Reactor 多线程模型

Reactor 多线程模型的特点:

1)有专门一个 NIO 线程 -Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求;

2)网络 IO 操作 - 读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送;

3)1 个 NIO 线程可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,防止发生并发操作问题。

在绝大多数场景下,Reactor 多线程模型都可以满足性能需求;但是,在极个别特殊场景中,一个 NIO 线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。在这类场景下,单独一个 Acceptor 线程可能会存在性能不足问题,为了解决性能问题,产生了第三种 Reactor 线程模型 - 主从 Reactor 多线程模型。

1.2.3. 主从多线程模型

主从 Reactor 线程模型的特点是:服务端用于接收客户端连接的不再是个 1 个单独的 NIO 线程,而是一个独立的 NIO 线程池。Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接入认证等),将新创建的 SocketChannel 注册到 IO 线程池(sub reactor 线程池)的某个 IO 线程上,由它负责 SocketChannel 的读写和编解码工作。Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操作。

它的线程模型如下图所示:

图 1-3 主从 Reactor 多线程模型

利用主从 NIO 线程模型,可以解决 1 个服务端监听线程无法有效处理所有客户端连接的性能不足问题。

它的工作流程总结如下:

  1. 从主线程池中随机选择一个 Reactor 线程作为 Acceptor 线程,用于绑定监听端口,接收客户端连接;
  2. Acceptor 线程接收客户端连接请求之后创建新的 SocketChannel,将其注册到主线程池的其它 Reactor 线程上,由其负责接入认证、IP 黑白名单过滤、握手等操作;
  3. 步骤 2 完成之后,业务层的链路正式建立,将 SocketChannel 从主线程池的 Reactor 线程的多路复用器上摘除,重新注册到 Sub 线程池的线程上,用于处理 I/O 的读写操作。

Netty Reactor 线程模型笔记的更多相关文章

  1. netty reactor线程模型分析

    netty4线程模型 ServerBootstrap http示例 // Configure the server. EventLoopGroup bossGroup = new EpollEvent ...

  2. 深入Netty逻辑架构,从Reactor线程模型开始

    本文是Netty系列第6篇 上一篇文章我们从一个Netty的使用Demo,了解了用Netty构建一个Server服务端应用的基本方式.并且从这个Demo出发,简述了Netty的逻辑架构,并对Chann ...

  3. 【Netty源码分析】Reactor线程模型

    1. 背景 1.1. Java线程模型的演进 1.1.1. 单线程 时间回到十几年前,那时主流的CPU都还是单核(除了商用高性能的小机),CPU的核心频率是机器最重要的指标之一. 在Java领域当时比 ...

  4. Netty高性能之Reactor线程模型

    Netty是一个高性能.异步事件驱动的NIO框架,它提供了对TCP.UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用 ...

  5. Reactor 线程模型以及在netty中的应用

    这里我们需要理解的一点是Reactor线程模型是基于同步非阻塞IO实现的.对于异步非阻塞IO的实现是Proactor模型. 一 Reactor 单线程模型 Reactor单线程模型就是指所有的IO操作 ...

  6. 面试官:Netty的线程模型可不只是主从多Reactor这么简单

    笔者看来Netty的内核主要包括如下图三个部分: 其各个核心模块主要的职责如下: 内存管理 主要提高高效的内存管理,包含内存分配,内存回收. 网通通道 复制网络通信,例如实现对NIO.OIO等底层JA ...

  7. Netty源码分析之Reactor线程模型详解

    上一篇文章,分析了Netty服务端启动的初始化过程,今天我们来分析一下Netty中的Reactor线程模型 在分析源码之前,我们先分析,哪些地方用到了EventLoop? NioServerSocke ...

  8. Netty IO线程模型学习总结

    Netty框架的 主要线程是IO线程.线程模型的好坏直接决定了系统的吞吐量.并发性和安全性. Netty的线程模型遵循了Reactor的基础线程模型.以下我们先一起看下该模型 Reactor线程模型 ...

  9. netty之==线程模型

    1.1 netty线程模型本质遵循了Reactor的基础线程模型,所以得先介绍Reactor模型  1.2  Reactor模型 无论是C++还是Java编写的网络框架,大多数都是基于Reactor模 ...

随机推荐

  1. java 序列化和反序列化的实现原理

    老是听说序列化反序列化,就是不知道到底什么是序列化,什么是反序列化?今天就在网上搜索学习一下,这一搜不要紧,发现自己曾经用过,竟然不知道那就是JDK类库中序列化和反序列化的API. ----什么是序列 ...

  2. 《nodejs开发指南》微博实例express4.x版

    之前一直执着于前端开发,最近几天,开始学起了nodejs.作为一名前端开发者,见到这样一门用javascript写的后台自然是很激动的.但是,后台毕竟不同于前端,在学习的过程中,还是会遇到不少问题. ...

  3. 初探AngularJs框架(三)

    一.实现todoList的demo 功能很简单,提供一个文本框,用户输入回车后添加新条目.每个条目可以在待处理和处理中两个区域间切换,每个条目都可以被删除,大致的界面如下图所示: 二.处理逻辑 首先将 ...

  4. highchart应用示例1--2个不同类型变量2个y轴

    1.ajax调用接口和处理数据 function getCityData() { var date1 = $('#datetimepicker1').val(); var date2 = $('#da ...

  5. javanio2

    package com.lanhuigu.nio.selector; import java.net.InetSocketAddress; import java.nio.ByteBuffer; im ...

  6. 底层代码创建GUI

    %底层代码创建GUI hf = figure(... 'Units','Normalized',... 'Color','w',... 'Position',[0.1 0.1 0.8 0.8]); h ...

  7. <转>jmeter(十二)关联之正则表达式提取器

    本博客转载自:http://www.cnblogs.com/imyalost/category/846346.html 个人感觉不错,对jmeter讲解非常详细,担心以后找不到了,所以转发出来,留着慢 ...

  8. SharePoint 解决管理员密码修改后各种问题的来袭

    问题描述:本人用的是Win10自带虚拟机Hyper-V.Windows Service 2012R2.SQL2014和SharePoint2016,由于有一段时间没有登录虚拟机,在此登录的时候提示密码 ...

  9. Java笔记 #04# 类的初始化顺序补充

    参考java中的类的初始化顺序详解 package org.sample; class Bread { Bread() { System.out.println("Bread()" ...

  10. Golang利用select实现超时机制

    所谓超时,比如上网浏览一些安全的网站,如果几分钟之后不做操作,那么就会让你重新登录.就所谓有时候出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞情况,这时候就可以用select来设置 ...