当前位置: 首页 > 科技观察

NioEventLoop源码分析

时间:2023-03-16 11:16:34 科技观察

本文转载自微信公众号《源码学徒》,作者皇甫傲傲鸣。转载本文请联系出处学徒公众号。源码分析上节课我们深入分析了newNioEventLoopGroup();的初始化过程,后来发现NioEventLoopGroup在初始化过程中会构建一个actuator数组,数组里面存放的元素是NioEventLoop类型。但是NioEventLoop是什么?为什么它是Netty的精髓?我们直接去NioEventLoop看看它的构造方法:上节课我们在循环填充actuator数组的过程中创建了它。有关详细信息,请参见上一课,请参阅上一课。false,newTaskQueue(queueFactory),newTaskQueue(queueFactory),rejectedExecutionHandler);this.provider=ObjectUtil.checkNotNull(selectorProvider,"selectorProvider");this.selectStrategy=ObjectUtil.checkNotNull(strategy,"selectStrategy");finalSelectorTupleselectorTuple.Tupleselector=this;选择器;this.unwrappedSelector=selectorTuple.unwrappedSelector;}关于super,后面会跟进,保存selectorproducerthis.provider=ObjectUtil.checkNotNull(selectorProvider,"选择器提供者");绑定了类似于生产者的东西,它允许我们在初始化NioEventLoopGroup时进行初始化。使用此生产者,我们可以稍后获取选择器或套接字通道!2.保存选择器this.selectStrategy=ObjectUtil.checkNotNull(strategy,"selectStrategy");将默认选择策略保存到NioEventLoop对象3.打开一个选择器finalSelectorTupleselectorTuple=openSelector();打开一个selectorwrapper对象,里面包含了一个selector!Netty为了进一步优化Netty的性能,官方也疯狂优化了这个selector。下面我们跟进openSelector这个方法,看看它是如何优化的。内部代码比较复杂。下面逐行分析一下:1.获取原始选择UnwrappedSelector=provider.openSelector();使用原始的生产者对象得到一个原始的选择器,以后再用!2.判断是否启用selector优化//禁用优化选项defaultfalseif(DISABLE_KEY_SET_OPTIMIZATION){//如果不进行优化,则直接包裹原selectorreturnnewSelectorTuple(unwrappedSelector);}DISABLE_KEY_SET_OPTIMIZATION默认为false。当禁用优化时,选择器将直接包装并返回!默认会优化,所以一般不会进入这个逻辑分支!3.获取一个selector类的对象//如果需要优化//反射获取对应类的对象SelectorImplObjectmaybeSelectorImplClass=AccessController.doPrivileged(newPrivilegedAction(){@OverridepublicObjectrun(){try{returnClass.forName("太阳.nio.ch.SelectorImpl",false,PlatformDependent.getSystemmClassLoader());}catch(Throwablecause){returncause;}}});这段代码是返回一个SelectorImplClass对象,这里是返回SelectorImplClass对象!从上面的代码我们可以看出,如果获取失败,会返回一个异常。如果异常,肯定不行,所以需要对可能出现的异常进行处理://如果获取不成功f(!(maybeSelectorImplClassinstanceofClass)||///确保当前选择器实现是我们可以检测到的东西判断是子类还是同类unwrappedSelector!((Class)maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())){//如果(maybeSelectorImplClassinstanceofThrowable){Throwablet=(Throwable)maybeSelectorImplClass;logger.trace("failedtoinstrumentaspecialjava.util.Setinto:{}",unwrappedSelector,t);}//还是wrapped为未优化的selectorreturnnewSelectorTuple(unwrappedSelector);如果发生异常,或者获取的和原来的选择器不是对象,仍然使用原来的选择器包装器返回!4.创建一个优化的selectKeysfinalSelectedSelectionKeySetselectedKeySet=newSelectedSelectionKeySet();用过NIO的应该都知道,使用选择器可以获得事件的Set集合。这里Netty官方自己实现了一个Set集合,内部使用数组进行优化。!因为Hashset集合是使用HashMap方法实现的,如果在添加元素时(this->Object)发生hash碰撞,就会遍历hash槽上的链表。算法复杂度为O(n),但数组不同。数组是O(1)所以Netty官方使用数组来优化选择器事件集。默认是1024,装满后会扩容两倍!你可以简单的认为它是一个Set集合,但是它是以数组的形式实现的!内部重写了add、size、iterator方法,其他方法全部丢弃!这是Netty对selector进行优化的一个重要对象,这样在加入额外事件的时候,算法复杂度直接从O(N)变为O(1)!5.StartreflectionreplacementforselectedKeys//开始反射替换ObjectmaybeException=AccessController.doPrivileged(newPrivilegedAction(){@OverridepublicObjectrun(){try{//获取selector事件中事件对象selectedKeys的属性对象FieldselectedKeysField=selectorImplClass.getDeclaredField("selectedKeys");//获取公选keyFieldpublicSelectedKeysField=selectorImplClass.getDeclaredField("publicSelectedKeys");//java9和Unsafe的存在直接替换内存空间中的数据if(PlatformDependent.javaVersion()>=9&&PlatformDependent.hasUnsafe()){//我们尝试用sun.misc.Unsafe替换SelectionKeySet//这样使得我们也可以在没有任何额外标志的情况下在Java9+中执行此操作。longselectedKeysFieldOffset=PlatformDependent.objectFieldOffset(selectedKeysField);longpublicSelectedKeysFieldOffset=PlatformDependent.objectFieldOffset(publicSelectedKeysField);if(selectedKeysFieldOffset!=-1&&publicSelectedKeysFieldOffset!=-1){PlatformDependent.putObject(unwrappedSelector,selectedKeysFieldOffset,selectedKeySet);PlatformDependent.putObject(unwrappedSelector,publicSelectedKeysFieldOffset,selectedKeySet);returnnull;}//如果不能直接替换内存空间中的数据,尝试使用反射}//java8或java9+使用反射替换Throwablecause=ReflectionUtil.trySetAccessible(selectedKeysField,true);if(cause!=null){returncause;}cause=ReflectionUtil.trySetAccessible(publicSelectedKeysField,true);if(cause!=null){returncause;}//开始替换,将我们用反射创建的优化事件数组替换到选择器selectedKeysField中。设置(unwrappedSelector,selectedKeySet);publicSelectedKeysField.set(unwrappedSelector,selectedKeySet);returnull;}猫ch(NoSuchFieldExceptione){returne;}catch(IllegalAccessExceptione){returne;}}});逻辑比较简单!首先获取SelectorImpl类对象的selectedKeys属性和publicSelectedKeys属性!判断使用的JDK版本是不是9以上,如果是9,直接操作JAVA的Unsafe对象操作系统的内存空间!关于Unsafe的介绍,零拷贝章节有详细介绍,大家可以回顾一下零拷贝章节!我们这里使用的JDK8如果使用JDK8,使用反射将我们创建的SelectedKeys优化对象SelectedSelectionKeySet替换成原来的选择器unwrappedSelector!6.包装选择器returnnewSelectorTuple(unwrappedSelector,newSelectedSelectionKeySetSelector(unwrappedSelector,selectedKeySet));首先将unwrappedSelector选择器包装到SelectedSelectionKeySetSelector包装类中!然后匹配unwrappedSelector和SelectedSelectionKeySetSelector,包装Wie元组返回!4.保存优化后的选择器和原始选择器this.selector=selectorTuple.selector;this.unwrappedSelector=selectorTuple.unwrappedSelector;5.调用父类并创建队列super(parent,executor,false,newTaskQueue(queueFactory),newTaskQueue(queueFactory),rejectedExecutionHandler);首先他会通过newTaskQueue建立两个队列,这两个队列是什么类型的?上节课我们分析过,queueFactory==null,所以分支代码会走如图,如果不指定DEFAULT_MAX_PENDING_TASKS,默认为Integer.MAX,最小为16!我们进入分支代码,看看他创建了一个什么样的队列:publicstaticQueuenewMpscQueue(){returnMpsc.newMpscQueue();}可以看出他创建了一个Mpsc队列,这是一个多生产者单消费者队列,由jctools框架提供。如果可能的话,我会详细解释队列。到这里我们知道,在创建NIOEventLoop的时候,父类传递了两个Mpsc队列,继续回到主线:进入super(xxx)的源码:protectedSingleThreadEventLoop(EventLoopGroupparent,Executorexecutor,booleanaddTaskWakesUp,QueuetaskQueue,QueuetailTask??Queue,RejectedExecutionHandlerrejectedExecutionHandler){super(parent,executor,addTaskWakesUp,taskQueue,rejectedExecutionHandler);//保存一个tailTask??s尾队列tailTask??s=ObjectUtil.checkNotNull(这里是tailTask??Queue,"tail"Task)Queue,尾队列,这个tailqueue,官方的意思是对Netty的运行状态做一些统计,比如task循环的耗时,占用物理内存的大小等等,但实际上tailTask??s用到的场景很少,这里不解释太多!我们继续跟进super方法的源码://父线程执行器falsempsc队列拒绝策略protectedSingleThreadEventExecutor(EventExecutorGroupparent,Executorexecutor,booleanaddTaskWakesUp,QueuetaskQueue,RejectedExecutionHandlerrejectedHandler){super(parent);this.addTaskWakes=addmaxPendingTasks=DEFAULT_MAX_PENDING_EXECUTOR_TASKS;//保存线程执行器this.executor=ThreadExecutorMap.apply(executor,this);//创建一个队列Mpscq,在外部线程执行时使用(不是在EventLoop线程中执行时)newTaskQueue(queueFactory)this.taskQueue=ObjectUtil.checkNotNull(taskQueue,"taskQueue");//保存拒绝策略this.rejectedExecutionHandler=ObjectUtil.checkNotNull(rejectedHandler,"rejectedHandler");}这里进一步保存,NioEventLoop对应的线程执行器,MpscQuery保存的任务队列和相应的拒绝策略!后面看到使用对应变量的代码就不会觉得陌生了!总结创建并保存两个多生产者单消费者队列tailTask??s和taskQueue保存一个线程执行器executor保存一个拒绝策略,主要用于队列满时如何处理!保存一个选择器生产者!创建一个优化的选择器,并保存它!保存原始选择器和优化后的选择器!