前言
- 本篇分析之前,先对自己提几个问题
1.1 默认情况下,Netty服务端起多少线程?何时启动?
1.2 Netty是如何解决jdk空轮询bug的?
1.3 Netty如何保证异步串行无锁化? - 本篇按照以下步骤进行解析
2.1 NioEventLoop创建
2.2 NioEventLoop启动
2.3 NioEventLoop执行逻辑
NioEventLoop创建
- 默认nThreads=0,所以默认线程数为CPU核数*2
线程执行器ThreadPerTaskExecutor
- 创建线程执行器ThreadPerTaskExecutor(见下图红色标记部分1)
1.1 ThreadPerTaskExecutor的execute方法会根据线程工厂创建线程并立马启动(见下方第二个截图)
1.2 跟进threadFactory.newThread会创建FastThreadLocalThread线程
1.3 线程命名规则: prefix = poolName + ‘-’ + poolId.incrementAndGet() + ‘-’ + nextId.incrementAndGet()
–>nioEventLoopGroup + '- ’ + 2 + xx
创建并初始化children
- 伪代码展示
1.1 executor为上面解析的线程执行器EventExecutor[] children = = new EventExecutor[nThreads]; for (int i = 0; i < nThreads; i ++) { children[i] = newChild(executor, args); }
- 由下图可知由newChild()方法创建NioEventLoop
- NioEventLoop初始化
3.1 保存线程执行器ThreadPerTaskExecutor
3.2 创建一个MpscQueue
3.3 创建一个selector
(1) taskQueue:外部线程执行netty任务时,如果判断不是在NioEventLoop线程里执行,则塞到任务队列,然后由
NioEventLoop线程去执行
创建新连接选择器chooser
- 伪代码展示
EventExecutor[] children //数组里元素为上面创建的NioEventLoop EventExecutorChooserFactory.EventExecutorChooser chooser = chooserFactory.newChooser(children);
2. 上图展示了新连接循环获取NioEventLoop的过程
3. netty对上述实现做了优化
3.1 在计算机底层&操作(二进制运算)比取模操作(底层库实现)高效
NioEventLoop启动
-
下图是启动入口
1.1 在上图执行execute()方法之前,此时EventLoop中的thread属性为空 -
下图NioEventLoop启动的详细逻辑
2.1 将任务放入队列taskQueue
2.2 如果当前线程不是eventLoop的线程,则启动线程:startThread
(1) 将当前线程赋值给eventLoop的thread属性
(2)最终触发线程执行器的execute()方法:ThreadPerTaskExecutor#execute
NioEventLoop执行
- 下图是NioEventLoop执行的入口
1.1 由红色标记部分进入NioEventLoop#run()方法
- NioEventLoop#run()方法解析
for (;;) { if (!hasTasks()) {//队列中没有任务 strategy = select(curDeadlineNanos); //轮询注册到select的IO事件 } if (strategy > 0) { final long ioStartTime = System.nanoTime(); try{ processSelectedKeys(); //处理IO相关的逻辑 }finally{ final long ioTime = System.nanoTime() - ioStartTime; ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio); //处理外部线程扔到taskQueue里的任务 } } }
轮询注册到select的IO事件select()
- deadlineNanos
1.1 如果没有设置deadlineNanos,则selector会一直阻塞,除非轮询到一个事件
1.2 如果设置deadlineNanos<=0,则立即返回
1.3 如果设置deadlineNanos>0,则设置超时时间
- 通过调用栈发现,轮询到key会添加到SelectedSelectionKeySet
2.1 SelectedSelectionKeySet底层是由数组SelectionKey[]存放key (netty的优化部分)
(1)有set变为数组,降低查询时间复杂度 - 怎样将自定义的SelectedSelectionKeySet融入到Selector中
3.1 入口在NioEventLoop#openSelector(),通过反射替换(见下图)
处理IO相关的逻辑 processSelectedKeys()
- 遍历轮询到的key:selectedKeys
1.1 取出key的attachment为NioChannel
1.2 根据不同事件进行相应的处理(见下图)
处理异步任务队列 runAllTasks
- 任务的聚合
1.1 任务分为:普通任务和定时任务 - 任务的执行
避免jdk空轮询的bug:unexpectedSelectorWakeup
- 通过上图发现,当轮询次数超过阈值(默认512)后,重新构建Selector
- 构建方式
2.1 创建新的selector
2.2 将旧的selector的key转移到新的selector上
2.3 删掉旧的selector上的key
final Selector oldSelector = selector;
final SelectorTuple newSelectorTuple = openSelector();
for (SelectionKey key: oldSelector.keys()) {
Object a = key.attachment();
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;