• Netty组件 (NioEventLoopGroup、NioEventLoop) 关系分析


    Netty-组件 (NioEventLoopGroup、NioEventLoop)源码分析

    一、 必读前言

    学习该篇源码,请希望先看完 我之前一期 【Netty-Reactor线程模型(NIO)】, 从中可学习到 从基础线程模型 到 高级Netty的 多Reactor多线程 模型 优化的思想 ,这非常有利于学习本期源码 (如履平地)。
    该篇只会从 NioEventLoopGroup、NioEventLoop 的构造方法入手,分析这两个类中关键的 属性。

    Netty线程模型基于 主从Reactor 模型 (多线程多Reactor)
    每个Channel 会绑定一个 线程模型(NioEventLoop), 每个NioEventLoop中会管理一个Selector
    ServerSocketChannel 所绑定的 NioEventLoop 组成的 NioEventLoopGroup 是所谓的MainReactor(bossGroup),
    SocketChannel 所绑定的 NioEventLoop 组成的 NioEventLoopGroupSubReactor(workGroup)

    二、NioEventLoopGroup

    1.继承体系

    从上面的继承体系图可以看到,【NioEventLoopGroup】实现了 JUC 包下的 线程池相关接口,其中重要的方法有 submit() ,execute() 等。【NioEventLoopGroup】内部又包含了很多 【NioEventLoop】其同样也继承了JUC包下线程池的接口。 我们可以把 【NioEventLoopGroup】 理解成一个包含了很多小线程池的 大线程池, 只不过这些小线程池仅仅是 单线程的线程池。

    也就是 当向 【NioEventLoopGroup】 中submit(task) 时, 会从该大线程池中 选择一个 【NioEventLoop】小的线程池,将该 task提交到这个小的线程池中执行。

    2.构造方法

    #NioEventLoopGroup

        public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                                 final SelectStrategyFactory selectStrategyFactory) {
            //参数一: 内部线程数量 0
            //参数二: 执行器 null
            //参数三: 选择器提供器,通过这个可以获取到jdk层面的selector实例
            //参数四:选择器工作策略 DefaultSelectStrategy
            //参数五: 线程池拒绝策略
            super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
        }
    

    #MultithreadEventLoopGroup 检查并定义了nThreads

        //参数一: 内部线程数量 
        //参数二: 执行器 null
        //参数三: 选择器提供器,通过这个可以获取到jdk层面的selector实例  args[0] selectorProvider
        //参数四:选择器工作策略 DefaultSelectStrategy               args[1] selectStrategy
        //参数五: 线程池拒绝策略                                     args[2] 拒绝策略
        protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args){
            super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
       }
    
        static {
            // 默认的线程数 =   CPU * 2
            DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
        }
    

    #MultithreadEventLoopGroup 真正核心的构造方法

        //参数一: 内部线程数量 默认核心线程数*2
        //参数二: 执行器 null
        //参数三: ChooserFactory 用来生成Chooser实例
        //参数四: 选择器提供器,通过这个可以获取到jdk层面的selector实例  args[0] selectorProvider
        //参数五:选择器工作策略 DefaultSelectStrategy               args[1] selectStrategy
        //参数六: 线程池拒绝策略                                     args[2] 拒绝策略
        protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                                EventExecutorChooserFactory chooserFactory, Object... args) {
            checkPositive(nThreads, "nThreads");
    
    
            if (executor == null) {
                //真正生产出来执行任务的线程 executor
                // newDefaultThreadFactory() 构建出来一个线程工厂,线程工厂内具有prefix字段,命名规则 className + poolId
                // 通过线程工厂创建出来的线程实例,线程名称为className + poolId + 线程ID,并且线程实例类型为:FastThreadLocalThread
                executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
            }
    
            //children中存储的是多个NioEventLoop
            children = new EventExecutor[nThreads];
    
            for (int i = 0; i < nThreads; i ++) {
                boolean success = false;
                try {
                    // newChild(..) 都会返回一个 NioEventLoop实例
                    children[i] = newChild(executor, args);
                    success = true;
                } catch (Exception e) {
                    throw new IllegalStateException("failed to create a child event loop", e);
                } finally {
    				// ....
                }
            }
    
            // 通过ChooserFactory 根据当前children数量 返回一个合适的 chooser实例
            //PowerOfTwoEventExecutorChooser 或者  GenericEventExecutorChooser
            // 其中属性都有 EventExecutor[] executors  当前的线程组NioEventLoop
            // next()方法 就是轮询策略
            chooser = chooserFactory.newChooser(children); //chooser可以说是一个线程选择器
    
            // 结束监听器
            final FutureListener<Object> terminationListener = new FutureListener<Object>() {
                @Override
                public void operationComplete(Future<Object> future) throws Exception {
                    if (terminatedChildren.incrementAndGet() == children.length) {
                        terminationFuture.setSuccess(null);
                    }
                }
            };
    
            for (EventExecutor e: children) {
                e.terminationFuture().addListener(terminationListener);
            }
    
            Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
            Collections.addAll(childrenSet, children);
            readonlyChildren = Collections.unmodifiableSet(childrenSet);
        }
    

    会根据nThreads 线程数量,来创建与该线程数量相同的 【NioEventLoop】并保留在 EventExecutor[] children 数组字段中。

    其中有两个关键的组件 executorchooser

    executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); :真正的线程执行器

    chooser = chooserFactory.newChooser(children); : 小线程池选择器,就是在从多个 NioEventLoop中选择一个。

    下面会对这两个组件的源码进行解析。

    三、DefaultThreadFactory

    很简单,该类就继承了 JUC 包下的ThreadFactory 接口, 也就是说 他是个线程工厂,用来创建线程的。

    核心的创建线程的方法如下:

        public Thread newThread(Runnable r) {
            Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
           	//....
            return t;
        }
    
        protected Thread newThread(Runnable r, String name) {
            return new FastThreadLocalThread(threadGroup, r, name);
        }
    

    在Netty中,封装了自己的 线程 FastThreadLocalThread ,将其理解成普通的 Thread 就可以了。 实际上FastThreadLocalThread 也就是继承了 Thread, 目的就是为了方便在后面的 Netty内存池中 使用 增加的新功能InternalThreadLocalMap,这里不对其做过多介绍,后面我会做一期专门讲解FastThreadLocalThread 的文章。 我们只需要将其当作成普通的Thread 就可以了。

    四、ThreadPerTaskExecutor

    该类 同样非常简单

    public final class ThreadPerTaskExecutor implements Executor {
        private final ThreadFactory threadFactory;
    	
        // 传入 线程工厂
        public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
            this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
        }
    	
        // 继承 Executor接口 ,实现其 execute()方法, 从线程工厂中获取一个新的线程并开启线程并执行任务。
        @Override
        public void execute(Runnable command) {
            threadFactory.newThread(command).start();
        }
    }
    

    五、DefaultEventExecutorChooserFactory

        // 根据 NioEventLoop数量 选择合适的  选择器
    	public EventExecutorChooser newChooser(EventExecutor[] executors) {
    
            //executors 是NioEventLoop数组
            if (isPowerOfTwo(executors.length)) {
                //如果数量为2的幂次方, 那么就使用 2的幂次方选择器 
                return new PowerOfTwoEventExecutorChooser(executors);
            } else {
                //如果不是2的幂次方 , 那么就是用 通用选择器
                return new GenericEventExecutorChooser(executors);
            }
        }
    

    那么我么来看看,PowerOfTwoEventExecutorChooserGenericEventExecutorChooser 这两个选择器有什么区别。

    #PowerOfTwoEventExecutorChooser

        private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
            private final AtomicInteger idx = new AtomicInteger();
            private final EventExecutor[] executors;
    
            PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
                this.executors = executors;
            }
    
            //轮询策略
            @Override
            public EventExecutor next() {
                return executors[idx.getAndIncrement() & executors.length - 1];
            }
        }
    

    #GenericEventExecutorChooser

        private static final class GenericEventExecutorChooser implements EventExecutorChooser {
    
            private final AtomicLong idx = new AtomicLong();
            private final EventExecutor[] executors;
    
            GenericEventExecutorChooser(EventExecutor[] executors) {
                this.executors = executors;
            }
    
            //轮询策略
            @Override
            public EventExecutor next() {
                return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
            }
        }
    

    实际上并无太大区别, PowerOfTwoEventExecutorChooser使用 位运算的方式 轮询,而 GenericEventExecutorChooser使用的是 取余的方式轮询。 只是相比之下PowerOfTwoEventExecutorChooser的速度要快一些。

    六、 NioEventLoop

    1.继承体系

    从上面的继承体系图 可以看到, NioEventLoop 同样也实现了 JUC 的 线程池相关接口。 在NioEventLoopGroup中真正干活的 线程 也就是 NioEventLoop

    2.构造方法

        //参数一:NioEventLoopGroup
        //参数二:executor ThreadPerTaskExecutor实例
        //参数三:selectorProvider
        //参数四:选择器工作策略
        //参数五:拒绝策略
        //参数六:null
        //参数七:null
        NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                     SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                     EventLoopTaskQueueFactory taskQueueFactory, EventLoopTaskQueueFactory tailTaskQueueFactory) {
    
    		
            super(parent, executor, false, newTaskQueue(taskQueueFactory), newTaskQueue(tailTaskQueueFactory),
                    rejectedExecutionHandler);
            // 选择器提供器
            this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
    
            // 选择策略
            this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
    
            // this.provider.openSelector() == selectorProvider.openSelector()
            final SelectorTuple selectorTuple = openSelector();
    
            //拿到 selector
            this.selector = selectorTuple.selector;
            this.unwrappedSelector = selectorTuple.unwrappedSelector;
        }
    
    

    从中可知,NioEventLoop 有几个关键的属性:

    • selector : Netty对原 JDK的 selector做的一层封装
    • executor: 从所属的NioEventLoopGroup 中传过来的 executor,用来创建线程并执行用的
    • taskQueue: 存放本地任务的任务队列。
    • thread (属性变量中的): 当通过 executor 创建了一个线程后,就会将该线程 赋值给该全局变量,之后该NioEventLoop 中就只使用该线程来工作,这也是为什么说 NioEventLoop是单线程的线程池的原因所在。

    关于NioEventLoopGroupNioEventLoop 的其它具体方法源码分析会在后面 Netty启动流程 中分享。

  • 相关阅读:
    大话串口:我和它的恩恩怨怨
    分布式网游server的一些想法(一) 语言和平台的选择
    C++: C没有闭包真的很痛苦……
    C++不是C/C++
    最美树算法
    类魔兽世界 技能 天赋 成就 log 系统设计
    C++网游服务端开发(一):又无奈的重复造了个轮子,一个底层网络库
    C++ protobuf 不仅仅是序列化……
    深入WPFStyle
    Illusion = Caliburn.Micro + MEF
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15822872.html
Copyright © 2020-2023  润新知