NioEventLoop源码分析
来源:互联网 发布:淘宝订单编号查询系统 编辑:程序博客网 时间:2024/06/08 13:48
NioEventLoop源码分析
- NioEventLoop源码分析
- 1 轮询IO事件
- 2 处理IO事件
- 3 处理任务队列
- 31 转移队列任务
- 32 定期检查任务截至时间
在Netty开发中,一般异步IO事件处理采用NioEventLoopGroup作为线程池,而NioEventLoop为线程池中单个线程,作为系统任务执行单元。接下来将从源码分析Netty是如何处理任务的。
NioEventLoop线程执行入口为其run()方法,其核心业务流程为:
1. select(wakenUp.getAndSet(false));轮询IO事件
2. processSelectedKeys();处理IO事件
3. runAllTasks();处理任务队列
1.1 轮询IO事件
首先来看看轮询IO事件,其调用NioEventLoop中void select(boolean)方法;在void select(boolean),首先计算定时截至时间,在定时超时截至时间之后50ms,依旧没有select()操作,则selectNow()之后跳出循环。
int selectCnt = 0;//统计for循环次数long currentTimeNanos = System.nanoTime();//当前系统时间long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);//任务截止时间for (;;) { long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L; if (timeoutMillis <= 0) {//超出截至时间50ms if (selectCnt == 0) {;//循环中还没有发生任何select操作 selector.selectNow(); selectCnt = 1; } break;//select之后立即返回}…}
接着判断是否有任务到来,或任务被唤醒,即刻执行selectNow()并返回。
if (hasTasks() && wakenUp.compareAndSet(false, true)) { selector.selectNow(); selectCnt = 1; break; }
在既没有达到定时任务截至时间,没有任务,且没有唤醒情形下执行selector.select(timeoutMillis);并将计数selectCnt+1;
执行select(timeoutMillis)再次判断当前状态,检查是否需要退出。
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) { // - Selected something,//产生IO事件 // - waken up by user, or//用户唤醒 // - the task queue has a pending task.//taskqueue添加任务 // - a scheduled task is ready for processing//定时任务就绪 break;} if (Thread.interrupted()) { … selectCnt = 1; break; }
接下来解决NIO epoll模型下 selctor状态异常导致cpu负载100%的bug。
在Netty中,死循环select()执行操作时,系统给定一个默认select()有效时间,在反复循环过程中,每次循环时间小于系统给定的有效时间且循环次数到达512时,系统任务进入到epool bug中,接着就进行selector重建。
long time = System.nanoTime(); if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) { // TimeUnit.MILLISECONDS.toNanos(timeoutMillis)被认为是一次有效的select()操作 selectCnt = 1;//操作有效将selectCnt重置 } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {//当无效循环次数达到阈值512时; rebuildSelector();//重建selector selector = this.selector; selector.selectNow(); selectCnt = 1; break; }// SELECTOR_AUTO_REBUILD_THRESHOLD为设置上限无效循环次数
重建selector核心代码,其将原selector有效key及key.attachment()转移只新建的selector上。
private void rebuildSelector0() { final Selector oldSelector = selector; final SelectorTuple newSelectorTuple; … newSelectorTuple = openSelector(); … for (SelectionKey key: oldSelector.keys()) { Object a = key.attachment(); try { if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) { continue; } int interestOps = key.interestOps(); key.cancel(); SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a); if (a instanceof AbstractNioChannel) { // Update SelectionKey ((AbstractNioChannel) a).selectionKey = newKey; } … selector = newSelectorTuple.selector
if (ioRatio == 100) { try { processSelectedKeys(); } finally { // Ensure we always run tasks. runAllTasks(); } } else { final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); } finally { // Ensure we always run tasks. final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } }
NioEventLoop主要执行IO时间及定时任务,但是Netty作为高并发IO框架,遵循以优先处理IO任务的原则,ioRatio负载去设置定时任务的执行时间,默认iRatio= 50(及IO时间与任务时间相等)
小结
在轮询IO事件过程中,先判断是否需要退出IO执行流程,或执行轮询IO事件按,并且解决了Epoll bug。
1.2 处理IO事件
在分析处理IO事件之前,先来说说netty在处理IO事件之前所做的优化。
private SelectorTuple openSelector() { final Selector unwrappedSelector; … unwrappedSelector = provider.openSelector(); … if (DISABLE_KEYSET_OPTIMIZATION) { return new SelectorTuple(unwrappedSelector); }//默认开启优化… final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet(); Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { return Class.forName( "sun.nio.ch.SelectorImpl",//通过反射获取SelectorImpl实例 false, PlatformDependent.getSystemClassLoader()); } } }); … final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass; Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys"); Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys"); … //将SelectorImpl实例中成员selectedKeysField和publicSelectedKeysField与selectedKeySet替换 selectedKeysField.set(unwrappedSelector, selectedKeySet); publicSelectedKeysField.set(unwrappedSelector, selectedKeySet); return null; } }); … selectedKeys = selectedKeySet;//IO事件对应的selectkey logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector); return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet)); }
上面代码的优化点在NioEventLoop 创建selector时,将其内部成员selectedKeysField和publicSelectedKeysField与selectedKeySet替换,对于selectedKeysField和publicSelectedKeysField而言,它们的数据结构为HashSet,添加的时间复杂读为o(log(n));而selectedKeySet为netty自定义类,它实现的set接口,然而底层却是基于数组的实现,它的添加时间复杂度为o(1);
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; }
在IO事件触发时,Selector会往底层的selectedKeysField和publicSelectedKeysField添加数据,这种方案可以降低系统运行时间复杂度。
再来看处理IO事件的逻辑。
private void processSelectedKeys() { if (selectedKeys != null) {//IO事件对应的的selectedkeys,该set已被优化 processSelectedKeysOptimized();//基于系统优化处理IO事件 } else { processSelectedKeysPlain(selector.selectedKeys());//非优化时间处理 } }
主要分析基于set优化处理IO事件的函数(没有优化场景下逻辑大致一致)。
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) { … Iterator<SelectionKey> i = selectedKeys.iterator(); for (;;) { final SelectionKey k = i.next(); final Object a = k.attachment(); … processSelectedKey(k, (AbstractNioChannel) a);//处理selectorkey … if (needsToSelectAgain) {//是否再次执行select()操作 selectAgain(); selectedKeys = selector.selectedKeys();} … i = selectedKeys.iterator();//进入下次循环处理selectedKey相应IO事件 } } } }
从上图截取源代码可以看到,IO事件处理由:执行IO事件和是否更新selectedkeys两大业务处理组成;
展开processSelectedKey(k, (AbstractNioChannel) a)代码可以看到,该代码主要进行读,写,连接等IO事件。
每次执行完IO时间之后,都会检查selectedKeys,其判断条件为needsToSelectAgain;跟着这个变量在elcipse用Ctrl+F搜索,找到该变量的操作如下段所示
void cancel(SelectionKey key) { key.cancel(); cancelledKeys ++; if (cancelledKeys >= CLEANUP_INTERVAL) {// CLEANUP_INTERVAL=256 cancelledKeys = 0; needsToSelectAgain = true; } }
可以看到在每次有256次key的取消,needsToSelectAgain = true;便去更新selectedKeys
小结:整个IO事件执行大体逻辑便是,循环去遍历selectedKeys,依次处理相应的IO任务并将selectedKey移除;然后判断是否需要更新selectedKeys(每当有256个key取消任务时,更新)
小结
在处理IO时间时,先对selectedKeys进行优化,然后处理相应IO事件;同时为了避免无效的轮询,在每发生256次selectedKey取消时,重新更新selectedKeys。
1.3 处理任务队列
处理任务队列的代码位于SingleThreadEventExecutor。
protected boolean runAllTasks(long timeoutNanos) { fetchFromScheduledTaskQueue(); Runnable task = pollTask(); … 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. if ((runTasks & 0x3F) == 0) {//每执行64次任务检查是否超时 lastExecutionTime = ScheduledFutureTask.nanoTime(); if (lastExecutionTime >= deadline) { break; } } task = pollTask(); … } … return true; }
1.3.1 转移队列任务
在处理任务过程中执行fetchFromScheduledTaskQueue();将scheduledTask队列取出并存入taskQueue;为什么Netty开发者要这样做????
我们先来看看这两个列的声明及初始化。
首先看scheduledTask = pollScheduledTask(nanoTime);该方法来自AbstractScheduledEventExecutor,方法返回值为该类成员变量scheduledTaskQueue,初始化过程如下代码:
Queue<ScheduledFutureTask<?>> scheduledTaskQueue() { if (scheduledTaskQueue == null) { scheduledTaskQueue = new PriorityQueue<ScheduledFutureTask<?>>(); } return scheduledTaskQueue; }
综上可知,scheduledTaskQueue为优先队列,接着来看看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"); taskQueue = newTaskQueue(this.maxPendingTasks); rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler"); }
taskQueue为SingleThreadEventExecutor成员变量,在构造器中调用newTaskQueue(this.maxPendingTasks)初始化;
在SingleThreadEventExecutor类成员方法中找到newTaskQueue()实现。
protected Queue<Runnable> newTaskQueue(int maxPendingTasks) { return new LinkedBlockingQueue<Runnable>(maxPendingTasks); }
初一看,哦原来taskQueue是一个阻塞队列,似乎感觉有点不妙(NioEventLoop异步执行)!!!!
接着把代码重新定位回到NioEventLoop。
@Override protected Queue<Runnable> newTaskQueue(int maxPendingTasks) { // This event loop never calls takeTask() return maxPendingTasks == Integer.MAX_VALUE ? PlatformDependent.<Runnable>newMpscQueue(): PlatformDependent.<Runnable>newMpscQueue(maxPendingTasks); }
在NioEventLoop重写了父类中方法,实际在NioEventLoop执行是taskQueue为newMpscQueue队列;这里才是重点:mpscQueue队列(多生产者单消费者队列)。分析到这里fetchFromScheduledTaskQueue()的工作就是将所有优先队列中数据转移至mpsc队列;Netty要这么做的理由是:为了避免在多线程执行任务队列时,在开发过程考虑同步;这违反了Netty设计核心理念,在线程执行任务时,多个线程同时将任务移交给mscp,而由当前NioEventLoop线程去执行。
1.3.2 定期检查任务截至时间
(runTasks & 0x3F) == 0,netty设计原则以优先处理IO任务,故在每从任务队列中执行64个任务,将会去检查定时截至时间是否已到,并退出执行任务队列
小结
任务队列的处理主要将任务一直mscp队列中去处理,并在每执行64个任务时,判断是否需要退出循环。
- NioEventLoop源码分析
- netty4源码分析——NioEventLoop
- NioEventLoop源码
- Netty4源码分析-NioEventLoop实现的线程运行逻辑
- 【Netty4.X】Netty源码分析之NioEventLoop(六)
- 【Netty源码解析】NioEventLoop
- 【Netty源码】NioEventLoop源码剖析
- NioEventLoop
- Netty源码分析:NioEventLoop启动以及其IO操作和Task任务的处理
- netty5.0之SingleThreadEventLoop & NioEventLoop
- Netty4学习笔记-- NioEventLoopGroup NioEventLoop
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- Leetcode 657 Judge Route Circle回到原点
- 别人学韦东山嵌入式的笔记
- 关于高阻态和OOC(out of context)综合方式
- java.nio.BufferOverflowException
- 使用Python开发windows桌面程序
- NioEventLoop源码分析
- 轻钢别墅安装过程
- Andorid 3.0 更新的一些折腾
- learning R with swirl-vapply and tapply(不懂)
- git基本命令
- 安装tensorflow之后conda连不上网的问题解决
- 初访逻辑门电路的世界
- 上传下载
- oracle-java7-installer安装java失败之后的处理