Netty源码分析之EventLoop

一、EventLoop

前文可知,EventLoop对应着一个线程实体,且通过EventLoopGroup来分配,像端口绑定、channel注册等工作都也是EventLoop来完成,并且一些任务的提交和调度都是通过EventLoop来实现。所以EventLoop在netty中扮演着非常重要的角色,本文就重点分析一下EventLoop每个功能。

public interface EventLoop extends EventExecutor, EventLoopGroup {
    @Override
    EventLoopGroup parent();//返回EventLoopGroup实例
}

从接口来看,EventLoop继承了EventLoopGroup,说明EventLoop具备和EventLoopGroup一样的功能,同时还继承了EventExecutor(顾名思义,就是事件执行器,执行具体的某个事件),也就是说EventLoop可执行某个具体的事件,下边我们以其具体实现NioEventLoop详细分析。

二、NioEventLoop

前文中,NioEventLoopGroup的具体实现有这么个方法。

    protected EventLoop newChild(Executor executor, Object... args) 
    throws Exception {
        EventLoopTaskQueueFactory queueFactory 
        = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(),
            (RejectedExecutionHandler) args[2], queueFactory);
    }

EventLoopGroup会循环调用这个方法来初始化EventExecutor数组(也就是EventLoop数组)。threadFactory(DefaultThreadFactory实例)是线程工厂,EventLoop中持有的线程对象就是通过这个工厂提供,跟踪其newThread方法,发现最后生成的是自定义的FastThreadLocalThread线程。
同样的我们看NioEventLoop的构造方法。

    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                 EventLoopTaskQueueFactory queueFactory) {
        super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
                rejectedExecutionHandler);
        //每一个NioEventLoop都会持有同一个SelectorProvider
        this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
        //初始选择策略
        this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
        //这是一个Selector二元组,里边保存的是unwrappedSelector和selector对象
        final SelectorTuple selectorTuple = openSelector();
        this.selector = selectorTuple.selector;
        this.unwrappedSelector = selectorTuple.unwrappedSelector;
    }

接下来挨个分析NioEventLoop的所有方法。

  • 静态代码块:配置SELECTOR_AUTO_REBUILD_THRESHOLD,意思是说选择次数超过了这个值就会触发重建selector(具体的使用在代码中详解),根据系统参数io.netty.selectorAutoRebuildThreshold配置,默认是512.
  • newTaskQueue(EventLoopTaskQueueFactory queueFactory):返回一个任务队列,会根据传入的队列实例化工厂来生成对应的任务队列。
  • SelectorTuple:私有的静态内部类,是对Selector的包装
  • openSelector():返回SelectorTuple实例,先根据provider返回Selector对象,再根据是否配置了系统参数(io.netty.noKeySetOptimization,默认是false),如果配置了直接返回SelectorTuple。否则把SelectorImpl中的selectedKeys和publicSelectedKeys参数值设置成自定义的集合,这样的好处就是可以直接从自定义的集合中取数即可。
  • selectorProvider():返回SelectorProvider实例,这是通过NioEventLoop的构成方法传递而来。
  • newTaskQueue(int maxPendingTasks):返回任务队列
  • newTaskQueue0(int maxPendingTasks):返回任务队列
  • register(final SelectableChannel ch, final int interestOps, final NioTask<?> task):channel注册
  • register0(SelectableChannel ch, int interestOps, NioTask<?> task):具体的注册实现
  • getIoRatio():返回ioRatio属性值
  • setIoRatio(int ioRatio):设置ioRatio属性值,值的大小只能是大于0小于等于100,默认是50
  • rebuildSelector():重建Selector对象
  • rebuildSelector0():具体的重建函数,思想就是先获取旧的Selector对象,然后调用openSelector()对象生成一个新的Selector实例,然后把旧的SelectionKey集合注册到新的Selector即可,然后关闭旧的Selector。
  • run():这是NioEventLoop的重点方法,具体的业务逻辑实现就是在这个方法中进行
protected void run() {
        int selectCnt = 0;//定义轮询次数
        for (;;) {
            try {
                int strategy;//策略
                try {
                	//定位calculateStrategy可知,如果当前没有任务返回的是SelectStrategy.SELECT策略,反正会直接调用selectNow方法,也就是底层方法。
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.CONTINUE://==-2
                        continue;
                    case SelectStrategy.BUSY_WAIT://==-3
                    case SelectStrategy.SELECT://==-1
                    //当前队列中没有任务,走这里
                    //返回下一个任务执行所需的截止时间,没有则返回-1
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);//设置唤醒时间
                        try {
                            if (!hasTasks()) {//如果当前还是没有任务
                                strategy = select(curDeadlineNanos);//执行具体的选择方法
                                //这个方法会根据当前的curDeadlineNanos时间去选择需要阻塞多久,也就是当前没有连接请求,会在这里阻塞curDeadlineNanos时间,刚好可以等待下一个任务执行完成
                            }
                        } finally {
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                    default:
                    }
                } catch (IOException e) {
                    rebuildSelector0();//如果有异常就重建Selector
                    selectCnt = 0;//归0
                    handleLoopException(e);
                    continue;
                }

                selectCnt++;//+1
                cancelledKeys = 0;//无效的key数量
                needsToSelectAgain = false;//是否需要重新选择
                final int ioRatio = this.ioRatio;//io占比
                boolean ranTasks;
                if (ioRatio == 100) {//默认是50
                    try {
                        if (strategy > 0) {//大于0表示当前有连接需要处理
                            processSelectedKeys();//处理当前的所有连接
                        }
                    } finally {
                        ranTasks = runAllTasks();//处理完成后,执行任务队列中的任务
                    }
                } else if (strategy > 0) {//大于0表示当前有连接需要处理
                    final long ioStartTime = System.nanoTime();//io开始时间
                    try {
                        processSelectedKeys();
                    } finally {
                        // io消耗时间,也就是处理连接花了多久.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        //根据ioTime * (100 - ioRatio) / ioRatio公式计算出一个timeout时间
                        //如果设置为100那么传入的参数就是0,查看runAllTasks方法可知,每当
                        //任务执行了64个之后,就会判断当前总的执行时间是否超过了timeout时间,
                        //如果是,直接break返回,否则继续执行。也就是ioRatio越大,表示优先
                        //处理连接,越小表示优先处理任务
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    ranTasks = runAllTasks(0); // 这里会执行最小数量的任务(64)
                }

                if (ranTasks || strategy > 0) {//如果当前执行了任务,或者处理了连接
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;//归0
                } else if (unexpectedSelectorWakeup(selectCnt)) { 
                	//表示当前既没有任务队列,也没有连接请求,此时有两种可能,一种是线程被中断了
                	//一种是CPU发生了空轮询
                    selectCnt = 0;
                }
            } catch (CancelledKeyException e) {
                // Harmless exception - log anyway
                if (logger.isDebugEnabled()) {
                    logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                            selector, e);
                }
            } catch (Error e) {
                throw (Error) e;
            } catch (Throwable t) {
                handleLoopException(t);
            } finally {
                // Always handle shutdown even if the loop processing threw an exception.
                try {
                    if (isShuttingDown()) {//是否关闭
                        closeAll();//关闭
                        if (confirmShutdown()) {//再次确认
                            return;
                        }
                    }
                } catch (Error e) {
                    throw (Error) e;
                } catch (Throwable t) {
                    handleLoopException(t);
                }
            }
        }
    }

从run方法的源码来看,主要任务是执行任务队列中的任务,以及处理客户端的连接,然后根据ioRatio来配置,当前是需要更多的处理连接还是处理任务,以及根据当前的任务和连接处理情况判断是否需要重建selector。

  • unexpectedSelectorWakeup(int selectCnt):如果当前线程被中断返回true,否则根据selectCnt和系统配置的SELECTOR_AUTO_REBUILD_THRESHOLD比较,大于等于它则重建Selector对象,返回true,否则返回false。

  • handleLoopException(Throwable t):异常处理,日志输出

  • processSelectedKeys():根据是否配置了selectedKeys调用不同的而实现

  • cleanup():关闭Selector对象

  • cancel(SelectionKey key):取消当前的key

  • processSelectedKeysPlain(Set selectedKeys):处理连接

  • processSelectedKeysOptimized():处理连接,与上边方法不同的在于集合是通过反射设置到Selector中还是,直接通过Selector返回。

  • processSelectedKey(SelectionKey k, AbstractNioChannel ch):处理具体连接

  • processSelectedKey(SelectionKey k, NioTask task):处理具体的任务

  • closeAll():关闭当前所有的channel

  • invokeChannelUnregistered(NioTask task, SelectionKey k, Throwable cause):直接调用task的channelUnregistered方法

  • wakeup(boolean inEventLoop):调用selector的wakeup方法

  • beforeScheduledTaskSubmitted(long deadlineNanos):返回布尔值

  • afterScheduledTaskSubmitted(long deadlineNanos):返回布尔值

  • unwrappedSelector():返回包装的Selector

  • selectNow()调用底层的selectNow方法

  • select(long deadlineNanos):调用底层带有timeout参数的select方法

  • selectAgain():直接调用底层的selectNow()方法
    以上这些方法都是在NioEventLoop类中实现的,我们接着看看其父类的方法。
    SingleThreadEventLoop:
    父类中有DEFAULT_MAX_PENDING_TASKS这么个属性,他的取值是根据系统参数配置来实现的(io.netty.eventLoop.maxPendingTasks)默认是Integer.MAX_VALUE,还有一个属性就是tailTasks队列,从构造方法来看,队列的大小就是DEFAULT_MAX_PENDING_TASKS。

  • parent():返回EventLoopGroup实例

  • next():返回EventLoop实例

  • register(Channel channel):channel注册

  • register(final ChannelPromise promise):具体的注册实现

  • register(final Channel channel, final ChannelPromise promise):方法的重载

  • executeAfterEventLoopIteration(Runnable task):添加任务到tailTask队列中

  • removeAfterEventLoopIterationTask(Runnable task):从tailTask队列中移除

  • afterRunningAllTasks():执行tailTask所有的任务

  • hasTasks():判断是否有任务可执行

  • pendingTasks():判断当前pending状态的任务数量

  • registeredChannels():返回-1
    父类的SingleThreadEventLoop方法中重要的方法就是register方法的调用
    继续看父类方法SingleThreadEventExecutor
    SingleThreadEventExecutor
    查看其源码可知,它定义了五种线程状态:

  • ST_NOT_STARTED:线程未启动=1

  • ST_STARTED:线程启动=2

  • ST_SHUTTING_DOWN:线程关闭ing=3

  • ST_SHUTDOWN:线程关闭=4

  • ST_TERMINATED:线程终止=5
    EventLoop自身没有继承Runnable接口,线程的实现方式是持有Thread属性,然后定义自身的run方法,然后通过启动Thread线程来调用自身的run方法。当前类中还有一个属性就是taskQueue(任务队列)。
    SingleThreadEventExecutor的方法实现都很简单,就不进行一一介绍,大致就是往队列中添加任务、取出任务、执行任务。以及线程的启动、关闭等,其父类的方法也不再分析,方法都比较简单。

三、总结

EventLoop是由EventLoopGroup管理的一个线程,客户端的连接、数据的读写都是在EventLoop中去实现,从前几节的分析可知,netty启动时会传入EventLoopGroup参数,EventLoopGroup会根据系统的参数配置,生成相应个数的线程(也就是EventLoop实例),netty会根据具体算法取出线程,来执行对应的事件(比如channel注册、端口绑定、以及处理客户端连接、读写事件等)。

以上,有任何不对的地方,请指正,敬请谅解。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值