Netty 线程模型与Reactor 模式

前言

        Netty 的线程模型是基于NIO的Selector 构建的,使用了异步驱动的Reactor 模式来构建的线程模型,可以很好的支持成百上千的 SocketChannel 连接。由于 READ/WRITE 都是非阻塞的,可以充分提升I/O线程的运行效率 ,避免了IO阻塞导致线程挂起, 同时可以让一个线程支持对多个客户端的连接SocketChannel的 READ/WRITE 操作, 从根本上解决了传统阻塞IO的一线程处理一连接的弊端。

高效率的Reactor模式

Reactor 模式 

1是一种为处理服务请求并发,提交到一个或者多个服务处理程序的事件设计模式。当请求抵达后,服务处理程序使用解多路分配策略,然后同步地派发这些请求至相关的请求处理程序 2 3(来自维基百科:https://zh.wikipedia.org/wiki/反应器模式)

常见的reactor模式有以下三种

  1. 单线程reactor
  2. 多线程reactor
  3. 主从reactor

1、单线程reactor

ractor 单线程模式是指所有的I/O操作都在一个NIO线程完成,该线程的职责:

1.作为NIO服务端,接收客户端TCP连接

2.作为NIO客户端,向客户端发送TCP连接

3.READ/WRITE 客户端的请求

 

不过单线程的reactor 模式无法发挥多核的优势,因此对于高并发量的系统仍然存在瓶颈,主要原因如下:

1、reactor 线程既要处理来自客户端的连接,又要处理READ/WRITE/编码/解码。即便cpu 100% 也难以满足实际场景的需求

多线程Reactor 解决了这些问题

2、多线程reactor模型

reactor 多线程的实现最大的区别是拥有一个专门用来处理实际I/O 操作是线程池

优点:

1、拥有一个Acceptor 专门用来监听请求的I/O 类型

2、使用专门线程池可以提高acceptor的并发量,并且可以将同一个SocketChannel 放于同一个I/O 线程处理,同一个I/O线程可以处理多个SocketChannel的READ/WRITE事件

在大部分场景,该线程模型都能处理,但存在这样一种场景:单个Acceptor 线程 可能会因为需要监听大量的 SocketChannel 连接 或 I/O事件处理或在建立建立时需要进行安全的握手认证、黑白名单过滤,而导致出现性能瓶颈。所以这种场景下,单独一个Accceptor 会导致性能不足,便出现了第三种线程模型,主从Reactor 模型

3、主从reactor 多线程模型

相比多线程reactor模型,主从reactor多线程模型拥有了一个独立处理 SocketChannel 连接的线程池,当客户端从Acceptor建立连接之后,便将该连接绑定到subreactor 线程池中的某个线程中,然后由该线程绑定客户端感兴趣的I/O事件(READ/WRITE),监听客户端连接请求,最后处理。

mainReactor : 监听 ServerSocketChannel 、建立与 SocketChannel  的连接、将完成建立连接之后的Socket 交给subReactor

subReactor : 监听SocketChannel的 I/O事件,完成编解码、相应的业务处理(默认为CPU个数)

Netty的线程模型可以通过配置不同参数实现不同reactor的线程模型(netty4版本未发现多线程reactor版本实现方式)

1、单线程reactor

1NioEventLoopGroup loopGroup = new NioEventLoopGroup(1); 2ServerBootstrap b = new ServerBootstrap(); 3b.group(loopGroup, loopGroup) 4 .channel(NioServerSocketChannel.class) 5 .handler(new LoggingHandler(LogLevel.DEBUG)) 6 .childHandler(new ChannelInitializer<Channel>() { 7 @Override 8 protected void initChannel(Channel ch) throws Exception { 9 // xxx 10 } 11 }); 12try { 13 ChannelFuture future = b.bind(8086).sync(); 14 future.channel().closeFuture().sync(); 15} catch (InterruptedException e) { 16 e.printStackTrace(); 17} finally { 18 loopGroup.shutdownGracefully().sync(); 19}

2、主从reactor

1NioEventLoopGroup mainGroup = new NioEventLoopGroup(1); 2NioEventLoopGroup subGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors()); 3ServerBootstrap b = new ServerBootstrap(); 4b.group(mainGroup, subGroup) 5 .channel(NioServerSocketChannel.class) 6 .handler(new LoggingHandler(LogLevel.DEBUG)) 7 .childHandler(new ChannelInitializer<Channel>() { 8 @Override 9 protected void initChannel(Channel ch) throws Exception { 10 // xxx 11 } 12 }); 13 14try { 15 ChannelFuture future = b.bind(8086).sync(); 16 future.channel().closeFuture().sync(); 17} catch (InterruptedException e) { 18 e.printStackTrace(); 19} finally { 20 mainGroup.shutdownGracefully().sync(); 21 subGroup.shutdownGracefully().sync(); 22}

NioEventLoop 与 NioEventLoopGroup 介绍

1、NioEventLoopGroup 是通过实现JUC 包相关线程池的接口来达到自定义线程池的目的,而NioEventLoop 则持有一个线程,用来处理SocketChannel 建立连接之后的I/O事件。

2、在初始化 NioEventLoopGroup 时,将初始化一定数量 NioEventLoop,并将这些初始化之后的 NioEventLoops 的引用交给NioEventLoopGroup 管理

3、由接口的实现可以看出,他们都是实现了juc 线程池的顶层接口  Executor,因此都具有公共的 execute(Runable command)及 submit(Runable task) 方法,那么这些通用的方法将为 NioEventLoopGroup  把任务交给 子的NioEventLoop处理提供标准及便利

1//轮询一个子的 NioEventLoop 进行提交处理 2@Override 3public Future<?> submit(Runnable task) { 4 return next().submit(task); 5} 6...... 7@Override 8public void execute(Runnable command) { 9 next().execute(command); 10}

Netty 具体线程模

从整个线程模型来看 相关组件之间的是如何相互协作的

1、初始化NioEventLoopGroup ,  将为ServerSocketChannel 提供一个 bossGroup 线程池,为 SockerChannel 的I/O 事件处理 提供一个workGroup

2、使用ServerBootstrap 绑定端口等相关信息,此时会初始化一个ServerSocketChannel 和 bossGroup,并且将 ServerSocketChannel 绑定到 bossGroup 中的一个NioEventLoop 中进行监听客户端的连接请求

3、当 Client 发起连接请求时,首先经过三次握手通过后,然后服务端被触发,接着收到连接成功的通知(因为是异步所以是触发)

4、ServerSocketChannel 收到连接成功的通知后,将建立好的连接交给 workGroup中的某个NioEventLoop,然后将感兴趣的事件注册到 该 NioEventLoop 持有的Selector上,等待Client 下一次请求

5、当 Client 发起 READ/WRITE 相关的请求时,则提交给NioEventLoop  进行处理 

参考:

http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

4、jstack查看线程栈信息

1、介绍利用jps、top、jstack命令找到进程中耗时最大的线程,以及线程状态等等,同时最后还可以显示出死锁的线程查找:FoundoneJavaleveldeadlock即可1、jps获得进程号!(https://oscimg.oschina.net/oscnet/da00a309fa6