这里我们需要理解的一点是Reactor线程模型是基于同步非阻塞IO实现的。对于异步非阻塞IO的实现是Proactor模型。

一 Reactor 单线程模型

Reactor单线程模型就是指所有的IO操作都在同一个NIO线程上面完成的,也就是IO处理线程是单线程的。NIO线程的职责是: 
(1)作为NIO服务端,接收客户端的TCP连接;

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

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

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

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

Reactor模式使用的是同步非阻塞IO(NIO),所有的IO操作都不会导致阻塞,理论上一个线程可以独立的处理所有的IO操作(selector会主动去轮询哪些IO操作就绪)。从架构层次看,一个NIO线程确实可以完成其承担的职责,比如上图的Acceptor类接收客户端的TCP请求消息,当链路建立成功之后,通过Dispatch将对应的ByteBuffer转发到指定的handler上,进行消息的处理。

对于一些小容量的应用场景下,可以使用单线程模型,但是对于高负载、大并发的应用场景却不适合,主要原因如下: 
(1)一个NIO线程处理成千上万的链路,性能无法支撑,即使CPU的负荷达到100%;

(2)当NIO线程负载过重,处理性能就会变慢,导致大量客户端连接超时然后重发请求,导致更多堆积未处理的请求,成为性能瓶颈。

(3)可靠性低,只有一个NIO线程,万一线程假死或则进入死循环,就完全不可用了,这是不能接受的。

二 Reactor 多线程模型

Reactor多线程模型与单线程模型最大的区别在于,IO处理线程不再是一个线程,而是一组NIO处理线程。原理如下图所示:

Reactor多线程模型的特点如下: 
(1)有一个专门的NIO线程—-Acceptor线程用于监听服务端,接收客户端的TCP连接请求。

(2)网络IO操作—-读写等操作由一个专门的线程池负责,线程池可以使用JDK标准的线程池实现,包含一个任务队列和N个可用的线程,这些NIO线程就负责读取、解码、编码、发送。

(3)一个NIO线程可以同时处理N个链路,但是一个链路只对应一个NIO线程。

Reactor多线程模型可以满足绝大多数的场景,除了一些个别的特殊场景:比如一个NIO线程负责处理客户所有的连接请求,但是如果连接请求中包含认证的需求(安全认证),在百万级别的场景下,就存在性能问题了,因为认证本身就要消耗CPU,为了解决这种情景下的性能问题,产生了第三种线程模型:Reactor主从线程模型。

三 主从Reactor 多线程模型

主从Reactor线程模型的特点是:服务端用于接收客户端连接的不再是一个单独的NIO线程,而是一个独立的NIO的线程池。Acceptor接收到客户端TCP连接请求并处理完成后(可能包含接入认证),再将新创建的SocketChannel注册到IO线程池(sub reactor)的某个IO处理线程上并处理编解码和读写工作。Acceptor线程池仅负责客户端的连接与认证,一旦链路连接成功,就将链路注册到后端的sub Reactor的IO线程池中。 线程模型图如下:

利用主从Reactor模型可以解决服务端监听线程无法有效处理所有客户连接的性能不足问题,这也是netty推荐使用的线程模型。

四 netty的线程模型

netty的线程模型是可以通过设置启动类的参数来配置的,设置不同的启动参数,netty支持Reactor单线程模型、多线程模型和主从Reactor多线程模型。

服务端启动时创建了两个NioEventLoopGroup,一个是boss,一个是worker。实际上他们是两个独立的Reactor线程池,一个用于接收客户端的TCP连接,另一个用于处理Io相关的读写操作,或者执行系统/定时任务的task。

boss线程池作用: 
(1)接收客户端的连接,初始化Channel参数 
(2)将链路状态变更时间通知给ChannelPipeline

worker线程池作用: 
(1)异步读取通信对端的数据报,发送读事件到ChannelPipeline 
(2)异步发送消息到通信对端,调用ChannelPipeline的消息发送接口 
(3)执行系统调用Task
(4)执行定时任务Task

通过配置boss和worker线程池的线程个数以及是否共享线程池等方式,netty的线程模型可以在单线程、多线程、主从线程之间切换。

为了提升性能,netty在很多地方都进行了无锁设计。比如在IO线程内部进行串行操作,避免多线程竞争造成的性能问题。表面上似乎串行化设计似乎CPU利用率不高,但是通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁串行线程设计性能更优。

nettyd的NioEventLoop读取到消息之后,直接调用ChannelPipeline的fireChannelRead(Object msg),只要用户不主动切换线程,一直都是由NioEventLoop调用用户的Handler,期间不进行线程切换,这种串行化设计避免了多线程操作导致的锁竞争,性能角度看是最优的。

 import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; /**
* Created by xxx on 2018/1/5 PM5:23.
*/
public class Test {
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
// 绑定port,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听port关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}
}

netty 服务端的创建过程

Netty 屏蔽NIO通信的底层细节:

  1. 首先创建ServerBootstrap,他是Netty服务端的启动辅助类

  2. 设置并绑定Reactor线程池。

    Netty的Reactor线程池是EventLoopGroup,它实际就是EventLoop线 程的数组。

    EventLoop的职责是处理全部注冊到本线程多路复用器Selector上的Channel

  3. 设置NioServerSocketChannel。Netty通过工厂类,利用反射创建NioServerSocketChannel对象

  4. 设置TCP參数

  5. 链路建立的时候创建并初始化ChannelPipeline.它本质就是一个负责处理网络事件的职责链,负责管理和运行ChannelHandler。

    网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline依据ChannelHandler的运行策略调度ChannelHandler的运行

    1. 绑定并启动监听port
    2. 绑定port,并启动。将会启动NioEventLoop负责调度和运行Selector轮询操作,选择准备就绪的Channel集合。当轮询到准备就绪的Channel之后,就由Reactor线程NioEventLoop运行ChannelPipeline的对应方法。终于调度并运行ChannelHandler。

NioEventLoop IO线程浅析

做为Netty的Reactor线程,由于要处理网络IO读写,所以聚合一个多路复用器对象,它通过open获取一个多路复用器。他的操作主要是在run方法的for循环中运行的。

  1. 做为bossGroup的线程 他须要绑定NioServerSocketChannel 来监听client的connect请求,并处理连接和校验。
  2. 作为workGroup线层组的线程。须要将连接就绪的SocketChannel绑定到线程中。所以一个client连接至相应一个线程,一个线程能够绑定多个client连接。

从调度层面看。也不存在在EventLoop线程中 再启动其他类型的线程用于异步运行其他的任务。这样就避免了多线程并发操作和锁竞争,提升了I/O线程的处理和调度性能。

五 epoll bug

Selector.select 没有任务运行时,可能触发JDK的epoll BUG。这就是著名的JDK epoll BUG,JDK1.7早期版本号得到解决。

server直接表现为IO线程的CPU非常高,可能达到100%,可能会导致节点故障!

为什么会发生epoll Bug

Netty的修复策略为:

  1. 对Selector的select的操作周期进行统计

  2. 完成一次空的select操作进行一次计数

  3. 在某周期内(如100ms)连续N次空轮询, 说明触发了epoll死循环BUG

  4. 检测到死循环后,重建selector的方式让系统恢复正常

netty采用此策略,完美避免了此BUG的发生。

关于netty bug,更详细的可以参看: http://blog.csdn.net/huoyunshen88/article/details/45672295

proactor和reactor的详细图解(画的非常清晰)参看 https://www.cnblogs.com/TomSnail/p/6158249.html

Reactor 线程模型以及在netty中的应用的更多相关文章

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

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

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

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

  3. Netty Reactor 线程模型笔记

    引用: https://www.cnblogs.com/TomSnail/p/6158249.html https://www.cnblogs.com/heavenhome/articles/6554 ...

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

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

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

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

  6. 网络编程NIO之Reactor线程模型

    目录 单Reactor线程模型 基于工作线程的Reactor线程模型 多Reactor线程模型 多Reactor线程模型示例 结束语 上篇文章中写了一些NIO相关的知识以及简单的NIO实现示例,但是示 ...

  7. netty reactor线程模型分析

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

  8. [原创]一款基于Reactor线程模型的java网络爬虫框架

    AJSprider 概述 AJSprider是笔者基于Reactor线程模式+Jsoup+HttpClient封装的一款轻量级java多线程网络爬虫框架,简单上手,小白也能玩爬虫, 使用本框架,只需要 ...

  9. Netty中的三种Reactor(反应堆)

    目录: Reactor(反应堆)和Proactor(前摄器) <I/O模型之三:两种高性能 I/O 设计模式 Reactor 和 Proactor> <[转]第8章 前摄器(Proa ...

随机推荐

  1. bzoj 1996

    区间 dp $f[i][j][1/0]$ 表示将理想数列的 $[i,j]$ 区间排好的方案数 $f[i][j][1]$ 表示最后进去的是第 $i$ 个人 $f[i][j][0]$ 表示最后进去的是第 ...

  2. C语言学习笔记10-结构体、枚举、联合体

    C语言学习笔记10-结构体.枚举.联合体    待传

  3. 微信小程序之简单记账本开发记录(三)

    昨天已经编辑了主界面,在wxml文件中设置好跳转链接之后,就可以进行下一步的开发了 在pages中建立一个新的页面文件夹作为之后的支出页面 编辑后台,今天先搭建大致界面

  4. Eclipse修改为中文版

    1.打开浏览器访问 https://www.eclipse.org/babel/downloads.php 复制语言包的url地址,我选择最新的 2.打开eclipse,依次选择菜单栏“Help - ...

  5. k8s之yaml详解

    k8s之yaml详解 apiVersion: v1 #指定api版本,此值必须在kubectl apiversion中 kind: Pod #指定创建资源的角色/类型 metadata: #资源的元数 ...

  6. python数据分析与应用

    python数据分析与应用笔记 使用sklearn构建模型 1.使用sklearn转换器处理数据 import numpy as np from sklearn.datasets import loa ...

  7. iOS App的几种安全防范

    虽然公司的项目目前还不算健壮,安全问题对于大部分小公司来说似乎并没什么必要,不过要攻击的话,我有十足的把握,我们是无法承受冲击的.嘿嘿嘿~不过带着一颗入坑iOS的心思,搜集了一下资料后,还是做了一些尝 ...

  8. [go]ini配置文件解析

    // config.ini [app] server.port = 8080 name = resk enabled = false time = 10s ;我是一个注释 #mysql数据库配置 [m ...

  9. ubuntu18 faster-rcnn

    luo@luo-All-Series:~/MyFile$ luo@luo-All-Series:~/MyFile$ luo@luo-All-Series:~/MyFile$ git clone htt ...

  10. OpenStack Manila发展动态系列--Austin峰会

    1 Manila Mitaka版本概述 在Austin峰会上介绍到,Manila Mitaka发布版本Driver个数达到了18个, M版本新加入14家公司(中国公司继华为之后又有99cloud等公司 ...