Netty组件EventLoopGroup和EventLoop源码分析

1、初始化EventLoopGroup

2、NioEventLoop 的运行

3、Select方法

4、processSelectedKeys IO事件处理

5、Netty解决JAVA NiO空轮询BUG

6、EventLoop构造过程一图总结


一、UnSafe相关介绍

============

1、JAVA中Unsafe简介


为什么先介绍Unsafe这个东西呢?我们知道JDK中也有UnSafe,Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。封装这一系列的native方法。并且是禁止我们开发者自己使用的。当然你可以通过反射进行获取。

JAVA中的UnSafe提供以下的功能

可以看到,java中的unsafe提供的都是至关重要的一些功能。

2、Netty中Unsafe介绍


Netty中的unsafe同样也是非常重要的。因为在Netty源码中很多地方都是用到了这个相关工具,Unsafe接口中定义了socket相关操作,包括SocketAddress获取、selector注册、网卡端口绑定、socket建连与断连、socket写数据。这些操作都和jdk底层socket相关。他的继承关系如下。

Unsafe是Channel的内部类,一个Channel对应一个Unsafe。

Unsafe用于处理Channel对应网络IO的底层操作。ChannelHandler处理回调事件时产生的相关网络IO操作最终也会委托给Unsafe执行。

NioUnsafe在Unsafe基础上增加了几个操作,包括访问jdk的SelectableChannel、socket读数据等。

NioByteUnsafe实现了与socket连接的字节数据读取相关的操作。

NioMessageUnsafe实现了与新连接建立相关的操作。

二、EventLoopGroup和EventLoop源码分析

==============================

我们就从最开始的Demo开始了解Netty的源码吧。大家都知道Netty是一个网络Io框架,他继承NIO,BIO,AIO,并能够按照模板化的方式去实现相关功能。我们以前也单独讲过JAVA原生的NIO实现。那么Netty到底是怎么将他们产生联系的呢?接下来一步一步为大家解读Netty源码。了解Netty的技术内幕。

PS:以下源码使用的是Netty4.1.28版本

1、初始化EventLoopGroup


在服务器启动的常规代码里,首先是实例化NioEventLoopGroup和ServerBootstrap。

执行这行代码时会发生什么?由NioEventLoopGroup开始,一路调用,到达MultithreadEventLoopGroup,如果没有指定创建的线程数量,则默认创建的线程个数为DEFAULT_EVENT_LOOP_THREADS,该数值为:处理器数量x2。

protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object… args) {

super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);

}

private static final int DEFAULT_EVENT_LOOP_THREADS;

static {

DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(

“io.netty.eventLoopThreads”, NettyRuntime.availableProcessors() * 2));

if (logger.isDebugEnabled()) {

logger.debug(“-Dio.netty.eventLoopThreads: {}”, DEFAULT_EVENT_LOOP_THREADS);

}

}

最终由MultithreadEventExecutorGroup实例化

/**

  • Create a new instance.

  • @param nThreads the number of threads that will be used by this instance.

  • @param executor the Executor to use, or {@code null} if the default should be used.

  • @param chooserFactory the {@link EventExecutorChooserFactory} to use.

  • @param args arguments which will passed to each {@link #newChild(Executor, Object…)} call

*/

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,

EventExecutorChooserFactory chooserFactory, Object… args)

在这个构造方法中,实例化了每个EventLoop所需要的执行器Executor

if (executor == null) {

executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());

}

由此可见,每个NioEventLoop的执行器为ThreadPerTaskExecutor,ThreadPerTaskExecutor实现了Executor接口,并会在execute方法中启动真正的线程,但是要和NioEventLoop的线程挂钩则在SingleThreadEventExecutor的doStartThread方法里。

public final class ThreadPerTaskExecutor implements Executor {

private final ThreadFactory threadFactory;

public ThreadPerTaskExecutor(ThreadFactory threadFactory) {

if (threadFactory == null) {

throw new NullPointerException(“threadFactory”);

}

this.threadFactory = threadFactory;

}

@Override

public void execute(Runnable command) {

//使用真正的线程执行方法

threadFactory.newThread(command).start();

}

}

接下来,new出EventExecutor(实际是NioEventLoop)的实例数组,并在循环里new每个具体的EventLoop实例

那么在NioEventLoop实例的构造方法里又做了什么事情呢?

@Override

protected EventLoop newChild(Executor executor, Object… args) throws Exception {

return new NioEventLoop(this, executor, (SelectorProvider) args[0],

((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);

}

作为IO事件处理的主要组件,内部持有了Selector、SelectionKey的集合,所以构造方法中执行了关键方法openSelector(),最终通过JDK的api拿到selector的实例,作用和我们通过原生JDK的NIO编程中创建选择器是一样的。

另外,我们观察下NioEventLoop的类图如下

发现最终实现Exector,我们可以知道,EventLoop本质上是一个线程池,EventLoop内部维护着一个线程Thread和几个阻塞队列,所以EventLoop可以看成只有一个线程的线程池(SingleThreadPool)

每个EventLoop包含的线程Thread定义在父类SingleThreadEventExecutor中,每个EventLoop包含两个队列,taskQueue来自父类SingleThreadEventExecutor,保存各种任务,比如处理事件等等,tailTask来自父类SingleThreadEventLoop,用于每次事件循环后置任务处理

作为IO事件处理的主要组件,必然离不开对事件的处理机制,在NioEventLoop的run方法,就有selector上进行select和调用processSelectedKeys()处理各种事件集。

2、NioEventLoop 的运行


@Override

protected void run() {

for (;😉 {

try {

try {

// 1、通过 hasTasks() 判断当前消息队列中是否还有未处理的消息

switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {

case SelectStrategy.CONTINUE:

continue;

//hasTasks() 没有任务则执行 select() 处理网络IO

case SelectStrategy.SELECT:

//轮询事件,见第三小节

select(wakenUp.getAndSet(false));

if (wakenUp.get()) {

selector.wakeup();

}

// fall through

default:

}

} catch (IOException e) {

// If we receive an IOException here its because the Selector is messed up. Let’s rebuild

// the selector and retry. https://github.com/netty/netty/issues/8566

rebuildSelector0();

handleLoopException(e);

continue;

}

cancelledKeys = 0;

needsToSelectAgain = false;

// 处理IO事件所需的时间和花费在处理 task 时间的比例,默认为 50%

final int ioRatio = this.ioRatio;

if (ioRatio == 100) {

try {

// 如果 IO 的比例是100,表示每次都处理完IO事件后,才执行所有的task

processSelectedKeys();

} finally {

// 执行 task 任务

runAllTasks();

}

} else {

// 记录处理 IO 开始的执行时间

final long ioStartTime = System.nanoTime();

try {

//IO任务处理,见第四小节

processSelectedKeys();

} finally {

// 计算处理 IO 所花费的时间

final long ioTime = System.nanoTime() - ioStartTime;

// 执行 task 任务,判断执行 task 任务时间是否超过配置的比例,如果超过则停止执行 task 任务

runAllTasks(ioTime * (100 - ioRatio) / ioRatio);

}

}

} catch (Throwable t) {

handleLoopException(t);

}

// Always handle shutdown even if the loop processing threw an exception.

try {

if (isShuttingDown()) {

closeAll();

if (confirmShutdown()) {

return;

}

}

} catch (Throwable t) {

handleLoopException(t);

}

}

}

1、调用selectStrategy.calculateStrategy 判断是否有 Task任务,如果没有则调用 selectSupplier.get() 方法,该方法是非阻塞的,判断是否有需要处理的 Channel。如果没有则返回 SelectStrategy.SELECT,然后执行 select(wakenUp.getAndSet(false)) 方法,阻塞等待可处理的 IO 就绪事件。

2、如果有 Task 任务,则判断 ioRatio 的比率值,该值为 EventLoop 处理 IO 和 处理 Task 任务的时间的比率。默认比率为 50%。

  • 如果 ioRatio == 100,则说明优先处理所有的 IO 任务,处理完所有的IO事件后才会处理所有的 Task 任务。

  • 如果 ioRatio <> 100, 则优先处理所有的IO任务,处理完所有的IO事件后,才会处理所有的Task 任务,但处理所有的Task 任务的时候会判断执行 Task 任务的时间比率,如果超过配置的比率则中断处理 Task 队列中的任务。

从中可以发现,什么情况下都会优先处理 IO任务,但处理非 IO 任务时,会判断非 IO 任务执行的时间不能超过 ioRatio 的阈值。

3、Select方法


小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

总结:心得体会

既然选择这个行业,选择了做一个程序员,也就明白只有不断学习,积累实战经验才有资格往上走,拿高薪,为自己,为父母,为以后的家能有一定的经济保障。

学习时间都是自己挤出来的,短时间或许很难看到效果,一旦坚持下来了,必然会有所改变。不如好好想想自己为什么想进入这个行业,给自己内心一个答案。

面试大厂,最重要的就是夯实的基础,不然面试官随便一问你就凉了;其次会问一些技术原理,还会看你对知识掌握的广度,最重要的还是你的思路,这是面试官比较看重的。

最后,上面这些大厂面试真题都是非常好的学习资料,通过这些面试真题能够看看自己对技术知识掌握的大概情况,从而能够给自己定一个学习方向。包括上面分享到的学习指南,你都可以从学习指南里理顺学习路线,避免低效学习。

大厂Java架构核心笔记(适合中高级程序员阅读):

源码讲义、实战项目、讲解视频**

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-mwYeenW9-1710736747847)]

总结:心得体会

既然选择这个行业,选择了做一个程序员,也就明白只有不断学习,积累实战经验才有资格往上走,拿高薪,为自己,为父母,为以后的家能有一定的经济保障。

学习时间都是自己挤出来的,短时间或许很难看到效果,一旦坚持下来了,必然会有所改变。不如好好想想自己为什么想进入这个行业,给自己内心一个答案。

面试大厂,最重要的就是夯实的基础,不然面试官随便一问你就凉了;其次会问一些技术原理,还会看你对知识掌握的广度,最重要的还是你的思路,这是面试官比较看重的。

最后,上面这些大厂面试真题都是非常好的学习资料,通过这些面试真题能够看看自己对技术知识掌握的大概情况,从而能够给自己定一个学习方向。包括上面分享到的学习指南,你都可以从学习指南里理顺学习路线,避免低效学习。

大厂Java架构核心笔记(适合中高级程序员阅读):

[外链图片转存中…(img-RJpmlndH-1710736747847)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值