马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
大纲
5.NioEventLoop的实行总体框架
6.Reactor线程实行一次变乱轮询
7.Reactor线程处置惩罚产生IO变乱的Channel
8.Reactor线程处置惩罚使命队列之添加使命
9.Reactor线程处置惩罚使命队列之实行使命
10.NioEventLoop总结
8.Reactor线程处置惩罚使命队列之添加使命
(1)Reactor线程实行一次变乱轮询的过程
(2)使命的分类和添加分析
(3)平凡使命的添加
(4)定时使命的添加
(5)Netty的定时使命机制增补
(1)Reactor线程实行一次变乱轮询的过程
Reactor线程通过NioEventLoop的run()方法每举行一次变乱轮询,起首会调用select()方法实行检测出IO变乱,然后会调用processSelectedKeys()方法处置惩罚检测出的IO变乱。此中IO变乱重要包罗新毗连接入变乱和毗连的数据读写变乱,末了会调用runAllTasks()方法处置惩罚使命队列中的异步使命。
(2)使命的分类和添加分析
runAllTasks()方法中的Task包罗平凡使命和定时使命,分别存放于NioEventLoop差别的队列里。一个是平凡的使命队列MpscQueue,另一个是定时的使命队列PriorityQueue。
平凡的使命队列MpscQueue在创建NioEventLoop时创建的,然后在外部线程调用NioEventLoop的execute()方法时,会调用addTask()方法将Task生存到平凡的使命队列里。
定时的使命队列PriorityQueue则是在添加定时使命时创建的,然后在外部线程调用NioEventLoop的schedule()方法时,会调用scheduleTaskQueue().add()方法将Task生存到定时的使命队列里。
(3)平凡使命的添加
场景一:用户自界说平凡使命
场景二:外部线程调用Channel的方法
当通过ctx.channel().eventLoop().execute(...)自界说平凡使命,大概通过非Reactor线程(外部线程)调用Channel的各类方法时,末了都会实行到SingleThreadEventExecutor的execute()方法。
场景一:用户自界说平凡使命
不管是外部线程照旧Reactor线程实行NioEventLoop的execute()方法,都会调用NioEventLoop的addTask()方法,然后调用offerTask()方法。而offerTask()方法会利用一个taskQueue将Task生存起来。这个taskQueue实在就是一个MPSC队列,每一个NioEventLoop都会有一个MPSC队列。
Netty利用MPSC队列可以方便地将外部线程的异步使命举行聚集,然后在Reactor线程内部用单线程来批量实行以提拔性能。可以鉴戒Netty的这种使命实行模式来处置惩罚类似多线程数据聚合,定时上报应用。- //场景一:用户自定义普通任务
- ctx.channel().eventLoop().execute(new Runnable() {
- @Override
- public void run() {
- ...
- }
- });
- public final class NioEventLoop extends SingleThreadEventLoop {
- ...
- }
- public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {
- ...
- }
- public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
- //每一个NioEventLoop会有一个MPSC队列
- private final Queue<Runnable> taskQueue;
- protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
- boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
- super(parent);
- this.addTaskWakesUp = addTaskWakesUp;
- this.maxPendingTasks = Math.max(16, maxPendingTasks);
- this.executor = ObjectUtil.checkNotNull(executor, "executor");
- //创建普通的任务队列MpscQueue
- taskQueue = newTaskQueue(this.maxPendingTasks);
- rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
- }
- ...
-
- @Override
- public void execute(Runnable task) {
- if (task == null) throw new NullPointerException("task");
- boolean inEventLoop = inEventLoop();
- //不管是外部线程还是Reactor线程执行NioEventLoop的execute()方法,都会调用NioEventLoop的addTask()方法
- if (inEventLoop) {
- addTask(task);
- } else {
- startThread();
- addTask(task);
- if (isShutdown() && removeTask(task)) {
- reject();
- }
- }
- if (!addTaskWakesUp && wakesUpForTask(task)) wakeup(inEventLoop);
- }
-
- //Add a task to the task queue, or throws a RejectedExecutionException if this instance was shutdown before.
- protected void addTask(Runnable task) {
- if (task == null) throw new NullPointerException("task");
- if (!offerTask(task)) reject(task);
- }
- final boolean offerTask(Runnable task) {
- if (isShutdown()) reject();
- return taskQueue.offer(task);
- }
- ...
- }
复制代码 场景二:外部线程调用Channel的方法
这个场景是在业务线程里,根据用户标识找到对应的Channel,然后调用Channel的write()方法向该用户推送消息。
外部线程在调用Channel的write()方法时,executor.inEventLoop()会返回false。于是会将write操纵封装成一个WriteTask,然后调用safeExecute()方法来实行。默认情况下会获取Channel对应的NIO线程,然后作为参数传入safeExecute()方法中举行实行,从而确保使命会由Channel对应的NIO线程实行,通过单线程实行来实现线程安全。- //场景二:当前线程为业务线程
- channel.write(...)
- abstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext, ResourceLeakHint {
- final EventExecutor executor;//一般初始化时默认为null
- AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, Class<? extends ChannelHandler> handlerClass) {
- this.pipeline = pipeline;
- this.executor = executor;
- ...
- }
-
- ...
- private void write(Object msg, boolean flush, ChannelPromise promise) {
- AbstractChannelHandlerContext next = findContextOutbound();
- final Object m = pipeline.touch(msg, next);
- EventExecutor executor = next.executor();
- //外部线程在调用Channel的write()方法时,executor.inEventLoop()会返回false
- if (executor.inEventLoop()) {
- if (flush) {
- next.invokeWriteAndFlush(m, promise);
- } else {
- next.invokeWrite(m, promise);
- }
- } else {
- //将write操作封装成一个WriteTask
- AbstractWriteTask task;
- if (flush) {
- task = WriteAndFlushTask.newInstance(next, m, promise);
- } else {
- task = WriteTask.newInstance(next, m, promise);
- }
- //调用safeExecute()来执行
- safeExecute(executor, task, promise, m);
- }
- }
-
- @Override
- public EventExecutor executor() {
- if (executor == null) {
- return channel().eventLoop();
- } else {
- return executor;
- }
- }
-
- private AbstractChannelHandlerContext findContextOutbound(int mask) {
- AbstractChannelHandlerContext ctx = this;
- EventExecutor currentExecutor = executor();
- do {
- ctx = ctx.prev;
- } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
- return ctx;
- }
-
- private static void safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) {
- try {
- //调用SingleThreadEventExecutor.execute()方法
- executor.execute(runnable);
- } catch (Throwable cause) {
- try {
- promise.setFailure(cause);
- } finally {
- if (msg != null) {
- ReferenceCountUtil.release(msg);
- }
- }
- }
- }
- ...
- }
- public class DefaultChannelPipeline implements ChannelPipeline {
- ...
- final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {
- private final Unsafe unsafe;
- HeadContext(DefaultChannelPipeline pipeline) {
- //传入的executor为null
- super(pipeline, null, HEAD_NAME, HeadContext.class);
- unsafe = pipeline.channel().unsafe();
- setAddComplete();
- }
- ...
- }
-
- final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
- TailContext(DefaultChannelPipeline pipeline) {
- //传入的executor为null
- super(pipeline, null, TAIL_NAME, TailContext.class);
- setAddComplete();
- }
- ...
- }
- ...
- }
- public abstract class SingleThreadEventExecutor extends AbstractScheduledEventExecutor implements OrderedEventExecutor {
- //每一个NioEventLoop会有一个MPSC队列
- private final Queue<Runnable> taskQueue;
- protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
- boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
- super(parent);
- this.addTaskWakesUp = addTaskWakesUp;
- this.maxPendingTasks = Math.max(16, maxPendingTasks);
- this.executor = ObjectUtil.checkNotNull(executor, "executor");
- //创建普通的任务队列MpscQueue
- taskQueue = newTaskQueue(this.maxPendingTasks);
- rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
- }
-
- ...
- @Override
- public void execute(Runnable task) {
- if (task == null) throw new NullPointerException("task");
- boolean inEventLoop = inEventLoop();
- //不管是外部线程还是Reactor线程执行NioEventLoop的execute()方法,都会调用NioEventLoop的addTask()方法
- if (inEventLoop) {
- addTask(task);
- } else {
- startThread();
- addTask(task);
- if (isShutdown() && removeTask(task)) {
- reject();
- }
- }
- if (!addTaskWakesUp && wakesUpForTask(task)) wakeup(inEventLoop);
- }
-
- //Add a task to the task queue, or throws a RejectedExecutionException if this instance was shutdown before.
- protected void addTask(Runnable task) {
- if (task == null) throw new NullPointerException("task");
- if (!offerTask(task)) reject(task);
- }
- final boolean offerTask(Runnable task) {
- if (isShutdown()) reject();
- return taskQueue.offer(task);
- }
- ...
- }
复制代码 (4)定时使命的添加
通常利用ctx.channel().eventLoop().schedule(..)自界说定时使命,此中schedule()方法会通过scheduledTaskQueue().add(task)来添加定时使命。起首scheduledTaskQueue()方法会返回一个优先级队列,然后通过该优先级队列的add()方法将定时使命对象参加到队列中。
留意,这里可以直接利用优先级队列而不消思量多线程并发题目的缘故起因如下。假如是外部线程调用schedule()方法添加定时使命,那么Netty会将添加定时使命这个逻辑封装成一个平凡的Task。这个Task的使命是一个"添加某定时使命"的使命,而不是添加某定时使命。如许,对优先级队列的访问就酿成单线程了,也就是只有Reactor线程会访问,从而不存在多线程并发题目。- //场景三:用户自定义定时任务,这也是用得最多的方法
- ctx.channel().eventLoop().schedule(new Runnable() {
- @Override
- public void run() {
- ...
- }
- }, 60, TimeUnit.SECONDS);
- //Abstract base class for EventExecutors that want to support scheduling.
- public abstract class AbstractScheduledEventExecutor extends AbstractEventExecutor {
- Queue<ScheduledFutureTask<?>> scheduledTaskQueue;
- ...
- //添加定时任务
- <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
- if (inEventLoop()) {
- //如果当前线程是Reactor线程,则直接往PriorityQueue中添加任务
- scheduledTaskQueue().add(task);
- } else {
- //如果是外部线程,则调用SingleThreadEventExecutor.execute()方法
- //将添加定时任务这一动作也封装成一个普通任务
- execute(new Runnable() {
- @Override
- public void run() {
- scheduledTaskQueue().add(task);
- }
- });
- }
- return task;
- }
-
- Queue<ScheduledFutureTask<?>> scheduledTaskQueue() {
- if (scheduledTaskQueue == null) {
- //创建定时的任务队列PriorityQueue
- scheduledTaskQueue = new PriorityQueue<ScheduledFutureTask<?>>();
- }
- return scheduledTaskQueue;
- }
- ...
- }
复制代码 (5)Netty的定时使命机制增补
一.怎样包管克制时间近来的使命优先实行
为什么定时使命要生存在优先级队列中?优先级队列的特性是会按照肯定的次序来分列内部元素,内部元素是可以比力的。由于优先级队列中的每个元素都是定时使命,以是定时使命也是可以比力的。比力的逻辑就是:先比力定时使命的克制时间,在克制时间类似的情况下再比力定时使命的添加次序也就是ID。
二.Netty的定时使命有三种实行方式
方式一:定时使命不会被重复实行
ctx.channel().eventLoop().schedule(),通报的periodNanos为0。
方式二:每隔一段时间实行一次
ctx.channel().eventLoop().scheduleAtFixedRate(),通报的periodNanos为正数。
方式三:隔类似时间再实行一次
ctx.channel().eventLoop().scheduleWithFixedDelay(),通报的periodNanos为负数。
Netty的3种定时使命的实行逻辑是通过调解下一次使命的克制时间来运行的。起首修改完下一次实行的克制时间,然后把当前使命再次参加队列,如许就能确保使命在得当的时间实行。- final class ScheduledFutureTask<V> extends PromiseTask<V> implements ScheduledFuture<V> {
- //每个定时任务都有一个唯一的ID
- private static final AtomicLong nextTaskId = new AtomicLong();
- private final long id = nextTaskId.getAndIncrement();
- private long deadlineNanos;
- //标识一个任务是否重复执行,以及以何种方式执行
- private final long periodNanos;
- ...
-
- @Override
- public int compareTo(Delayed o) {
- if (this == o) {
- return 0;
- }
- ScheduledFutureTask<?> that = (ScheduledFutureTask<?>) o;
- long d = deadlineNanos() - that.deadlineNanos();
- if (d < 0) {
- return -1;
- } else if (d > 0) {
- return 1;
- } else if (id < that.id) {
- return -1;
- } else if (id == that.id) {
- throw new Error();
- } else {
- return 1;
- }
- }
- public long deadlineNanos() {
- return deadlineNanos;
- }
-
- @Override
- public void run() {
- ...
- if (periodNanos == 0) {
- //1.对应schedule()方法,表示一次性任务
- V result = task.call();
- setSuccessInternal(result);
- } else {
- task.call();
- long p = periodNanos;
- if (p > 0) {
- //2.对应scheduleAtFixedRate()方法,表示以固定速率执行任务
- deadlineNanos += p;
- } else {
- //3.对应scheduleWithFixedDelay()方法,表示以固定的延时执行任务
- deadlineNanos = nanoTime() - p;
- }
- scheduledTaskQueue.add(this);
- }
- ...
- }
- ...
- }
复制代码 9.Reactor线程处置惩罚使命队列之实行使命
(1)runAllTasks()方法须要传入超时时间
(2)Reactor线程实行使命的步调
(3)Netty性能优化之批量战略
(4)NioEventLoop.run()方法实行使命总结
(1)runAllTasks()方法须要传入超时时间
SingleThreadEventExecutor的runAllTasks()方法须要传入参数timeoutNanos,体现只管在timeoutNanos时间内将全部的使命都取出来实行一遍。由于假如Reactor线程在实行使命时停顿的时间过长,那么将会累积许多IO变乱无法及时处置惩罚,从而导致大量客户端哀求壅闭。因此Netty会精致控制内部使命队列的实行时间。
(2)Reactor线程实行使命的步调
一.使命聚合
转移定时使命到MPSC队列,这里只是将快到期的定时使命转移到MPSC队列里。
二.时间盘算
盘算本轮使命实行的克制时间,此时全部克制时间已到达的定时使命均被添补到平凡的使命队列(MPSC队列)里了。
三.使命实行
起首不抛非常地同步实行使命,然后累加当前已实行的使命数,接着每隔64次盘算一下当前时间是否已超克制时间,末了判断本轮使命是否已经实行完毕。(3)Netty性能优化之隔断战略
假设使命队列里有海量的小使命,假如每次实行完使命都须要判断是否到克制时间,那么服从是比力低的。以是Netty选择通过每隔64个使命才判断一下是否到克制时间,那么服从就会高许多。
(4)NioEventLoop.run()方法实行使命总结
Netty里的使命分两种:一种是平凡的使命,另一种是定时的使命。Netty在实行这些使命时起首会把定时使命聚合到平凡使命队列里,然后再从平凡使命队列里获取使命逐个实行,而且是每实行64个使命之后才判断一下当前时间是否高出最大答应实行时间。假如高出就直接停止,停止之后就会举行下一次NioEventLoop.run()方法的for循环。
10.NioEventLoop总结
(1)NioEventLoop的实行流程总结
(2)Reactor线程模子总结
(3)NioEventLoop创建启动实行的总结
(1)NioEventLoop的实行流程总结
一.NioEventLoop在实行过程中起首会不停检测是否有IO变乱发生,然后假如检测出有IO变乱就处置惩罚IO变乱,接着处置惩罚完IO变乱之后再处置惩罚外部线程提交过来的异步使命。
二.在检测是否有IO变乱发生时,为了包管异步使命的及时处置惩罚,只要有使命要处置惩罚,那么就立刻克制检测行止置惩罚使命。
三.外部线程异步实行的使命分为两种:平凡使命和定时使命。这两种使命分别生存到MPSC队列和优先级队列,而优先级队列中的使命终极都会转移到MPSC队列里举行处置惩罚。
四.Netty每处置惩罚完64个使命才会查抄一次是否超时而退出实行使命的循环。
(2)Reactor线程模子总结
一.NioEventLoopGroup在用户代码中被创建,默认情况下会创建两倍CPU核数个NioEventLoop。
二.NioEventLoop是懒启动的,bossNioEventLoop在服务端启动时启动,workerNioEventLoop在新毗连接入时启动。
三.当CPU核数为2的幂时,为每一个新毗连绑定NioEventLoop之后,都会做一个取模运算转位与运算的优化。
四.每个毗连都对应一个Channel,每个Channel都绑定唯逐一个NioEventLoop,一个NioEventLoop大概会被绑定多个Channel,每个NioEventLoop都对应一个FastThreadLocalThread线程实体和一个Selector。因此单个毗连的全部操纵都是在一个线程上实行的,以是是线程安全的。
五.每个NioEventLoop都对应一个Selector,这个Selector可以批量处置惩罚注册到它上面的Channel。
六.每个NioEventLoop的实行过程都包罗变乱检测、变乱处置惩罚以及异步使命的实行。
七.用户线程池在对Channel举行一些操纵时均为线程安全的。这是由于Netty会把外部线程的操纵都封装成一个Task放入这个Channel绑定的NioEventLoop中的MPSC队列,然后在该NioEventLoop的实行过程(变乱循环)的第三个过程中举行串行实行。
八.以是NioEventLoop的职责不光仅是处置惩罚网络IO变乱,用户自界说的平凡使命和定时使命也会同一由NioEventLoop处置惩罚,从而实现线程模子的同一。
九.从调治层看,也不存在从NioEventLoop线程中再启动其他范例的线程用于异步实行别的的使命,从而克制了多线程并发操纵和锁竞争,提拔了IO线程的处置惩罚性能和调治性能。
(3)NioEventLoop创建启动实行的总结
一.用户在创建bossGroup和workerGroup时,NioEventLoopGroup被创建,默认不传参时会创建两倍CPU核数个NioEventLoop。
二.每个NioEventLoopGroup都有一个线程实行器executor和一个线程选择器chooser。线程选择器chooser用于举行线程分配,它会针对NioEventLoop的个数举行优化。
三.NioEventLoop在创建时会创建一个Selector和一个MPSC使命队列,创建Selector时Netty会通过反射的方式用数组去更换Selector里的两个HashSet数据结构。
四.Netty的NioEventLoop在初次调用execute()方法时会启动线程,这个线程是一个FastThreadLocalThread对象。启动线程后,Netty会将创建完成的线程生存到成员变量,以便能判断实行NioEventLoop里的逻辑的线程是否是这个创建好的线程。
五.NioEventLoop的实行逻辑在run()方法里,重要包罗3部分:第一是检测IO变乱,第二是处置惩罚IO变乱,第三是实行异步使命。
|