Netty源码解析-NioEventLoop(二)

Netty源码4.1.22
继前一篇,下面该分析processSelectedKeys()

processSelectedKeys()处理IO事件

    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }

这里涉及Netty对SelectedKeys的优化,NioEventLoop初始化过程中会调用openSelector()创建Selector,若你没有设置io.netty.noKeySetOptimization属性为true,则默认开启优化

......
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
......
//selectorImplClass为"sun.nio.ch.SelectorImpl"的class对象
// 获取该对象的两个字段:selectedKeys与publicSelectedKeys
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");

Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
if (cause != null) {
    return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
if (cause != null) {
    return cause;
}

selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);

目的就是将sun.nio.ch.SelectorImplselectedKeys与publicSelectedKeys与selectedKeySet绑定

SelectorImpl:

	protected Set<SelectionKey> selectedKeys = new HashSet();
	private Set<SelectionKey> publicSelectedKeys;
    protected SelectorImpl(SelectorProvider var1) {
        super(var1);
        if (Util.atBugLevel("1.4")) {
            this.publicKeys = this.keys;
            this.publicSelectedKeys = this.selectedKeys;
        } else {
            this.publicKeys = Collections.unmodifiableSet(this.keys);
            this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);
        }

    }

publicSelectedKeys与selectedKeys相同,或是其的不可增加版,调用selectedKeys()返回的就是return this.publicSelectedKeys;
这两个HashSet有何用?在执行Selector的select()方法时,如果与SelectionKey相关的事件发生了,这个SelectionKey就被加入到selected-keys集合中,就是selectedKeys与publicSelectedKeys。
来看看代替者SelectedSelectionKeySet

final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {

    SelectionKey[] keys;
    int size;

    SelectedSelectionKeySet() {
        keys = new SelectionKey[1024];
    }

    @Override
    public boolean add(SelectionKey o) {
        if (o == null) {
            return false;
        }

        keys[size++] = o;
        if (size == keys.length) {
            increaseCapacity(); // 扩容
        }

        return true;
    }
    ......

继承自AbstractSet,内部是一个数组,每次将元素加在尾部,复杂度为0(1),不支持remove,contains,iterator。HashSet是依赖HashMap实现的,添加操作的复杂度为O(lgn).

回到一开始进入到processSelectedKeysOptimized方法

    private void processSelectedKeysOptimized() {
        for (int i = 0; i < selectedKeys.size; ++i) {
            final SelectionKey k = selectedKeys.keys[i];
            // null out entry in the array to allow to have it GC'ed once the Channel close
            // See https://github.com/netty/netty/issues/2363
            selectedKeys.keys[i] = null;

            final Object a = k.attachment();

            if (a instanceof AbstractNioChannel) {
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            if (needsToSelectAgain) {
                // null out entries in the array to allow to have it GC'ed once the Channel close
                // See https://github.com/netty/netty/issues/2363
                selectedKeys.reset(i + 1);

                selectAgain();
                i = -1;
            }
        }
    }

由于前面的优化已经,selectedKeys就是存放SelectionKey的集合。从数组中取出进行处理。
selectedKeys.keys[i] = null,当channel关闭的时候,其selectionKey也需要回收,所以这里需要将数组对其的强引用释放。
接下来就要对SelectionKey进行处理,if (a instanceof AbstractNioChannel)attachment为什么会是AbstractNioChannel类型?回到注册阶段,在AbstractNioChannel的doRegister()方法调用了JDK底层,将nio的SelectableChannel注册到nio的Selector上,并将AbstractNioChannel作为附属对象javaChannel().register(eventLoop().unwrappedSelector(), 0, this);这样在某个SelectableChannel有IO事件发生时,便可以通过其selectionKey获得NioSocketChannel或NioServerSocketChannel。

关于processSelectedKey涉及到pipline,之后分析。

接下来

            if (needsToSelectAgain) {
                // null out entries in the array to allow to have it GC'ed once the Channel close
                // See https://github.com/netty/netty/issues/2363
                selectedKeys.reset(i + 1);

                selectAgain();
                i = -1;
            }

关于needsToSelectAgain:它在NioEventLoop的run方法,也就是本篇所分析的processSelectedKeys()方法调用之前被设为false,那么什么时候变为true?

    void cancel(SelectionKey key) {
        key.cancel();
       // 在NioEventLoop的run方法,每次IO事件处理(processSelectedKeys)
       // 开始前归为0
        cancelledKeys ++; 
        if (cancelledKeys >= CLEANUP_INTERVAL) { //256
            cancelledKeys = 0;
            needsToSelectAgain = true;
        }
    }

被cancel掉的selectionKey的数量大于等于256时,会将needsToSelectAgain设为true,回到上面,needsToSelectAgain为true,会首先调用selectedKeys.reset(i + 1);将selectionKey的数组清空,随后selectAgain();重新填充。

    private void selectAgain() {
        needsToSelectAgain = false;
        try {
// 填充SelectionKey数组,指SelectedSelectionKeySet中的SelectionKey[] keys;
            selector.selectNow(); 
        } catch (Throwable t) {
            logger.warn("Failed to update SelectionKeys.", t);
        }
    }

总结一下就是,当删除一定数量(256)的SelectionKey后,Netty采取的策略便是在处理SelectionKey时发现这一情况后,重新填充SelectionKey数组,保证现存SelectionKey的及时有效。
总结一下
netty的reactor线程第二步做的事情为处理IO事件,netty使用数组替换掉jdk原生的HashSet来保证IO事件的高效处理,每个SelectionKey上绑定了netty类AbstractChannel对象作为attachment,在处理每个SelectionKey的时候,就可以找到AbstractChannel,然后通过pipeline的方式将处理串行到ChannelHandler,回调到用户方法。

处理任务队列runAllTasks()

先来看看task的使用
一:用户定义的普通任务

ctx.channel().eventLoop().execute(new Runnable() {
    @Override
    public void run() {
        //...
    }
});

跟进到execute调用addTask方法,此时是在NioEventLoop的线程

    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);
    }

任务会被加入到taskQueue队列,taskQueue在NioEventLoop初始化过程中在SingleThreadEventExecutor构造函数被初始化,taskQueue = newTaskQueue(this.maxPendingTasks);

    protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
        return new LinkedBlockingQueue<Runnable>(maxPendingTasks);
    }

taskQueue是个LinkedBlockingQueue。

二:外部线程调用channel各种方法

channel.write(...)

这里涉及pipline之后剖析,总之最后会调用到AbstractChannelHandlerContext

    private void write(Object msg, boolean flush, ChannelPromise promise) {
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }

这里进入到else逻辑,将其封装成一个writeTask,之后调用safeExecute,里面会调用executor.execute(runnable);放入队列,LinkedBlockingQueue保证了操作的线程安全。

三:用户定义的定时任务

ctx.channel().eventLoop().schedule(new Runnable() {
    @Override
    public void run() {

    }
}, 60, TimeUnit.SECONDS);

先将任务封装成ScheduledFutureTask之后

    <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
        if (inEventLoop()) {
            scheduledTaskQueue().add(task);
        } else {
            execute(new Runnable() {
                @Override
                public void run() {
                    scheduledTaskQueue().add(task);
                }
            });
        }

        return task;
    }

    PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
        if (scheduledTaskQueue == null) {
            scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
                    SCHEDULED_FUTURE_TASK_COMPARATOR,
                    // Use same initial capacity as java.util.PriorityQueue
                    11);
        }
        return scheduledTaskQueue;
    }

    private static final Comparator<ScheduledFutureTask<?>> SCHEDULED_FUTURE_TASK_COMPARATOR =
            new Comparator<ScheduledFutureTask<?>>() {
                @Override
                public int compare(ScheduledFutureTask<?> o1, ScheduledFutureTask<?> o2) {
                    return o1.compareTo(o2);
                }
            };

关于scheduledTaskQueue 它是实体是

public final class DefaultPriorityQueue<T extends PriorityQueueNode>
			 extends AbstractQueue<T>implements PriorityQueue<T> {

它是Netty实现的优先级队列,它是非线程安全的,那么在外部线程并发向其添加任务时如何保证线程安全?Netty的做法是外部线程的添加操作会被封装成一个Runnable交给NioEventLoop的线程来实际执行,以此来保证安全性。

之前的文章中说scheduledTaskQueue里的定时任务按照延迟时间从小到大排列,首先来了解下这里时间的变化,如例子中任务会在60s后执行

return schedule(new ScheduledFutureTask<Void>(this, command, 
	null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay))));

可以看到60s会先变换成纳秒之后被ScheduledFutureTask.deadlineNanos处理,

    static long deadlineNanos(long delay) {
        return nanoTime() + delay;
    }

返回该任务的定时终止时间,该值会被赋给ScheduledFutureTask的deadlineNanos字段

来看看ScheduledFutureTask的比较策略

    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;
        }
    }

终止时间在前的优先级高,在终止时间相同时按照id来排列,而id是按照加入队列的顺序递增的,即先加入的优先级高。

Netty中定时任务分为三类:

    /* 0 - no repeat, >0 - repeat at fixed rate, 
    <0 - repeat with fixed delay */
    private final long periodNanos;

periodNanos不同值代表不同类型,0代表只执行一次的定时任务;>0代表以固定频率执行的任务,无关任务的执行时间;<0代表以固定频率执行的任务,不过是在每次任务执行完后开始计时。
ScheduledFutureTask构造器分为两类

    ScheduledFutureTask(
            AbstractScheduledEventExecutor executor,
            Callable<V> callable, long nanoTime, long period) {

        super(executor, callable);
        if (period == 0) {
            throw new IllegalArgumentException("period: 0 (expected: != 0)");
        }
        deadlineNanos = nanoTime;
        periodNanos = period;
    }

    ScheduledFutureTask(
            AbstractScheduledEventExecutor executor,
            Callable<V> callable, long nanoTime) {

        super(executor, callable);
        deadlineNanos = nanoTime;
        periodNanos = 0;
    }

你传入的period参数值代表执行间隔。
来看看ScheduledFutureTask对它们的不同处理

    public void run() {
        assert executor().inEventLoop();
        try {
            if (periodNanos == 0) {
                if (setUncancellableInternal()) {
                    V result = task.call();
                    setSuccessInternal(result);
                }
            } else {
                // check if is done as it may was cancelled
                if (!isCancelled()) {
                    task.call();
                    if (!executor().isShutdown()) {
                        long p = periodNanos;
                        if (p > 0) {
                            deadlineNanos += p;
                        } else {
                            deadlineNanos = nanoTime() - p;
                        }
                        if (!isCancelled()) {
                            // scheduledTaskQueue can never be null as we lazy init it before submit the task!
                            Queue<ScheduledFutureTask<?>> scheduledTaskQueue =
                                    ((AbstractScheduledEventExecutor) executor()).scheduledTaskQueue;
                            assert scheduledTaskQueue != null;
                            scheduledTaskQueue.add(this);
                        }
                    }
                }
            }
        } catch (Throwable cause) {
            setFailureInternal(cause);
        }
    }

periodNanos == 0,执行任务然后退出;
periodNanos > 0代表每periodNanos时间执行一次,不考虑任务的耗时,所以下次任务的执行时间为deadlineNanos += p;
periodNanos < 0代表每periodNanos时间执行一次,每次以当此任务结束时间为开始计时,所以下次任务的执行时间为deadlineNanos = nanoTime() - p;
计算完下次任务的开始执行时间后,就将其重新加入队列,scheduledTaskQueue.add(this);

进入正题runAllTasks(long timeoutNanos)
由于final int ioRatio = this.ioRatio;默认为50,NioEventLoop的run方法进入如下代码

final long ioStartTime = System.nanoTime();
......
try {
	processSelectedKeys();
} finally {
	// Ensure we always run tasks.
	final long ioTime = System.nanoTime() - ioStartTime;
	runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}

在IO事件处理前记录开始时间ioStartTime,之后processSelectedKeys()处理IO事件,如上面分析的,最后计算出本次处理所耗时间ioTime,调用runAllTasks(ioTime * (100 - ioRatio) / ioRatio);

runAllTasks(long timeoutNanos)
该方法会取出所有taskQueue中的任务,执行其run,但执行总时长不能超过timeoutNanos

    protected boolean runAllTasks(long timeoutNanos) {
        fetchFromScheduledTaskQueue();
        Runnable task = pollTask();
        if (task == null) {
            afterRunningAllTasks();
            return false;
        }

        final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
        long runTasks = 0;
        long lastExecutionTime;
        for (;;) {
            safeExecute(task);

            runTasks ++;

            // Check timeout every 64 tasks because nanoTime() is relatively expensive.
            // XXX: Hard-coded value - will make it configurable if it is really a problem.
            if ((runTasks & 0x3F) == 0) {
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                if (lastExecutionTime >= deadline) {
                    break;
                }
            }

            task = pollTask();
            if (task == null) {
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                break;
            }
        }

        afterRunningAllTasks();
        this.lastExecutionTime = lastExecutionTime;
        return true;
    }

1,首先从scheduledTaskQueue转移定时任务到taskQueue,即fetchFromScheduledTaskQueue();

    private boolean fetchFromScheduledTaskQueue() {
    // 当前时间
        long nanoTime = AbstractScheduledEventExecutor.nanoTime();
        // 获取到期任务
        Runnable scheduledTask  = pollScheduledTask(nanoTime);
        while (scheduledTask != null) {
            if (!taskQueue.offer(scheduledTask)) { // 将其加入taskQueue
                // 若加入失败,由于该定时任务已被从scheduledTaskQueue中删除,
                // 这里重新加回
                scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
                return false;
            }
            // 继续获取超时发任务
            scheduledTask  = pollScheduledTask(nanoTime); 
        }
        return true;
    }

// 检查定时任务队列scheduledTaskQueue的第一个任务(最先到期的任务)是否到期,
// 到期就从scheduledTaskQueue中删除并返回它,否则返回null
    protected final Runnable pollScheduledTask(long nanoTime) {
        assert inEventLoop(); // 只能是NioEventLoop的线程才能做此操作

        Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
        ScheduledFutureTask<?> scheduledTask = scheduledTaskQueue == null ? null : 
        									scheduledTaskQueue.peek();
        if (scheduledTask == null) {
            return null;
        }

        if (scheduledTask.deadlineNanos() <= nanoTime) {
            scheduledTaskQueue.remove();
            return scheduledTask;
        }
        return null;
    }

2,获取一个taskQueue的任务,计算runAllTasks(long timeoutNanos)方法的终止时间,

        Runnable task = pollTask();
        final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;

3, 循环执行任务

        long runTasks = 0;
        long lastExecutionTime;
        for (;;) {
        //所谓safe就是直接调用task的run方法,让它在NioEventLoop线程中执行
            safeExecute(task); 

            runTasks ++; // 记录循环次数

            // Check timeout every 64 tasks because nanoTime() is relatively expensive.
            // 由于nanoTime() 的耗时,所以Nety采取每循环64次就重新校验下
            // 当前是否超过方法截至时间deadline
            if ((runTasks & 0x3F) == 0) {
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                if (lastExecutionTime >= deadline) {
                    break;
                }
            }

            task = pollTask(); // 获取任务
            if (task == null) { // 为null表明任务取完,记录下结束时间
                lastExecutionTime = ScheduledFutureTask.nanoTime();
                break;
            }
        }

4,taskQueue队列任务执行完后

        afterRunningAllTasks();
        // 记录本次runAllTasks方法结束时间
        this.lastExecutionTime = lastExecutionTime;
        return true;

关于this.lastExecutionTime在多处被赋值,只在一个地方被使用到,即confirmShutdown,它与Netty的优雅退出机制有关
Netty 优雅退出机制和原理

来看看 afterRunningAllTasks()

    protected void afterRunningAllTasks() {
        runAllTasksFrom(tailTasks);
    }
    
    // 取出队列中的任务,调用其run方法执行,直到队列为空
    protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
        Runnable task = pollTaskFrom(taskQueue);
        if (task == null) {
            return false;
        }
        for (;;) {
            safeExecute(task);
            task = pollTaskFrom(taskQueue);
            if (task == null) {
                return true;
            }
        }
    }

关于tailTasks同样是在NioEventLoop初始化过程中在SingleThreadEventLoop构造器中被创建,tailTasks = newTaskQueue(maxPendingTasks);,也是个LinkedBlockingQueue,该队列作用?
若你想在每次任务循环结束执行某些操作,可以调用NioEventLoop父类SingleThreadEventLoop的executeAfterEventLoopIteration,它会将任务添加到tailTasks,afterRunningAllTasks会对它进行调用。

    public final void executeAfterEventLoopIteration(Runnable task) {
        ObjectUtil.checkNotNull(task, "task");
        if (isShutdown()) {
            reject();
        }

        if (!tailTasks.offer(task)) {
            reject(task);
        }

        if (wakesUpForTask(task)) {
            wakeup(inEventLoop());
        }
    }

猜你喜欢

转载自blog.csdn.net/sinat_34976604/article/details/85043266