首页天道酬勤netty4,netty和netty-all

netty4,netty和netty-all

admin 08-27 16:02 392次浏览

一、NIO Reactor模型

1、Reactor模式思想:分而治之+事件驱动

1)分而治之

一个连接里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。

Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。

2)事件驱动

每个Task对应特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Handler执行。

3)几个角色

Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;

Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;

Acceptor:Handler的一种,绑定了connect事件。当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。

2、单线程Reactor

所有I/O操作都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor线程上完成的

1)优点:

不需要做并发控制,代码实现简单清晰。

2)缺点:

a)不能利用多核CPU;

b)一个线程需要执行处理所有的accept、read、decode、process、encode、send事件,处理成百上千的链路时性能上无法支撑;

c)一旦reactor线程意外跑飞或者进入死循环,会导致整个系统通信模块不可用。

3、多线程Reactor

一个Acceptor负责接收请求,一个Reactor Thread Pool负责处理I/O操作

特点:

a)有专门一个reactor线程用于监听服务端ServerSocketChannel,接收客户端的TCP连接请求;

b)网络IO的读/写操作等由一个worker reactor线程池负责,由线程池中的NIO线程负责监听SocketChannel事件,进行消息的读取、解码、编码和发送。

c)一个NIO线程可以同时处理N条链路,但是一个链路只注册在一个NIO线程上处理,防止发生并发操作问题。

4、主从多线程

一个Acceptor负责接收请求,一个Main Reactor Thread Pool负责连接,一个Sub Reactor Thread Pool负责处理I/O操作。

在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是在极个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。

特点:

a)服务端用于接收客户端连接的不再是个1个单独的reactor线程,而是一个boss reactor线程池;

b)服务端启用多个ServerSocketChannel监听不同端口时,每个ServerSocketChannel的监听工作可以由线程池中的一个NIO线程完成。

二、Netty线程模型

netty线程模型采用“服务端监听线程”和“IO线程”分离的方式,与多线程Reactor模型类似。

抽象出NioEventLoop来表示一个不断循环执行处理任务的线程,每个NioEventLoop有一个selector,用于监听绑定在其上的socket链路。

1、串行化设计避免线程竞争

netty采用串行化设计理念,从消息的读取->解码->处理->编码->发送,始终由IO线程NioEventLoop负责。整个流程不会进行线程上下文切换,数据无并发修改风险。

一个NioEventLoop聚合一个多路复用器selector,因此可以处理多个客户端连接。

netty只负责提供和管理“IO线程”,其他的业务线程模型由用户自己集成。

时间可控的简单业务建议直接在“IO线程”上处理,复杂和时间不可控的业务建议投递到后端业务线程池中处理。

2、定时任务与时间轮

NioEventLoop中的Thread线程按照时间轮中的步骤不断循环执行:

a)在时间片Tirck内执行selector.select()轮询监听IO事件;

b)处理监听到的就绪IO事件;

c)执行任务队列taskQueue/delayTaskQueue中的非IO任务。

 

3、线程管理

Netty线程模型的卓越性能取决于它对当前执行的Thread的身份确定,也就是说,确定他是否是分配给当前Channel以及它的EventLoop的那个线程(通过调用inEventLoop(Thread))。

为了确保一个Channel的整个生命周期中的I/O事件会被一个EventLoop负责,Netty通过inEventLoop()方法来判断当前执行的线程的身份,确定它是否是分配给当前Channel以及它的EventLoop的那一个线程。如果当前(调用)线程正是EventLoop中的线程,那么所提交的任务将会被直接执行,否则,EventLoop将调度该任务以便稍后执行,并将它放入内部的任务队列(每个EventLoop都有它自己的任务队列,从SingleThreadEventLoop的源码就能发现很多用于调度内部任务队列的方法),在下次处理它的事件时,将会执行队列中的那些任务。这种设计可以让任何线程与Channel直接交互,而无需在ChannelHandler中进行额外的同步。

从性能上来考虑,千万不要将一个需要长时间来运行的任务放入到任务队列中,它会影响到该队列中的其他任务的执行。解决方案是使用一个专门的EventExecutor来执行它(ChannelPipeline提供了带有EventExecutorGroup参数的addXXX()方法,该方法可以将传入的ChannelHandler绑定到你传入的EventExecutor之中),这样它就会在另一条线程中执行,与其他任务隔离。

4、线程模型选择

下面以服务端的配置为例,说明如何选择不同的线程模型。

①单线程模型

EventLoopGroup bossGroup = new NioEventLoopGroup(1); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup) .channel(NioServerSocketChannel.class) ......

以上示例中实例化了一个NIOEventLoopGroup,并传入线程数量为1,然后调用ServerBootstrap的group方法绑定线程组,看实现:

@Override public ServerBootstrap group(EventLoopGroup group) { return group(group, group); } public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) { super.group(parentGroup); if (childGroup == null) { throw new NullPointerException("childGroup"); } if (this.childGroup != null) { throw new IllegalStateException("childGroup set already"); } this.childGroup = childGroup; return this; }

从源码可知,实际仍然绑定了 bossGroup 和 workerGroup,只是都是同一个NioEventLoopGroup实例而已,这样Netty中的acceptor和后续的所有客户端连接的IO操作都是在一个线程中处理,这就相当于Reactor的单线程模型。

②多线程模型

EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) ......

创建1个线程的bossGroup线程组,这个线程负责处理客户端的连接请求,而workerGroup默认使用处理器个数*2的线程数量来处理I/O操作。这就相当于Reactor的多线程模型。

③主从多线程模型

EventLoopGroup bossGroup = new NioEventLoopGroup(4);EventLoopGroup workerGroup = new NioEventLoopGroup();try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) ......

三、NioEventLoop与NioChannel类关系

 

一个NioEventLoopGroup下包含多个NioEventLoop

每个NioEventLoop中包含有一个Selector,一个taskQueue,一个delayedTaskQueue

每个NioEventLoop的Selector上可以注册监听多个AbstractNioChannel

每个AbstractNioChannel只会绑定在唯一的NioEventLoop上

每个AbstractNioChannel都绑定有一个自己的DefaultChannelPipeline

四、NioEventLoop线程执行过程

1、轮询监听的IO事件

1)netty的轮询注册机制

netty将AbstractNioChannel内部的jdk类SelectableChannel对象注册到NioEventLoopGroup中的jdk类Selector对象上去,并且将AbstractNioChannel作为SelectableChannel对象的一个attachment附属上。

这样在Selector轮询到某个SelectableChannel有IO事件发生时,就可以直接取出IO事件对应的AbstractNioChannel进行后续操作。

2)循环执行阻塞selector.select(timeoutMIllis)操作直到以下条件产生

a)轮询到了IO事件(selectedKey != 0)

b)oldWakenUp参数为true

c)任务队列里面有待处理任务(hasTasks())

d)第一个定时任务即将要被执行(hasScheduledTasks())

e)用户主动唤醒(wakenUp.get()==true)

2、处理任务队列

1)处理用户产生的普通任务

NioEventLoop中的Queue<Runnable> taskQueue被用来承载用户产生的普通Task。

taskQueue被实现为netty的mpscQueue,即多生产者单消费者队列。netty使用该队列将外部用户线程产生的Task聚集,并在reactor线程内部用单线程的方式串行执行队列中的Task。

当用户在非IO线程调用Channel的各种方法执行Channel相关的操作时,比如channel.write()、channel.flush()等,netty会将相关操作封装成一个Task并放入taskQueue中,保证相关操作在IO线程中串行执行。

2)处理用户产生的定时任务

NioEventLoop中的Queue<ScheduledFutureTask<?>>delayedTaskQueue = new PriorityQueue被用来承载用户产生的定时Task。

当用户在非IO线程需要产生定时操作时,netty将用户的定时操作封装成ScheduledFutureTask,即一个netty内部的定时Task,并将定时Task放入delayedTaskQueue中等待对应Channel的IO线程串行执行。

为了解决多线程并发写入delayedTaskQueue的问题,netty将添加ScheduledFutureTask到delayedTaskQueue中的操作封装成普通Task,放入taskQueue中,通过NioEventLoop的IO线程对delayedTaskQueue进行单线程写操作。

3)处理任务队列的逻辑

a)将已到期的定时Task从delayedTaskQueue中转移到taskQueue中

b)计算本次循环执行的截止时间

c)循环执行taskQueue中的任务,每隔64个任务检查一下是否已过截止时间,直到taskQueue中任务全部执行完或者超过执行截止时间。

五、Netty中Reactor线程和worker线程所处理的事件

1、Server端NioEventLoop处理的事件

2、Client端NioEventLoop处理的事件

 

节点管理 Kafka消息队列 UKafkaRocketMQ 消息失败重试 解析——图解无论如何如何重新分配内存
什么是 EventLoop,eventloop是什么 tar解压指定文件名,tar的解压命令
相关内容