3、Netty那些事 - NioEventLoopGroup源码解析

1,215 阅读3分钟

空山新雨后,天气晚来秋。今年的秋天好像来的格外早,天气渐凉,大家注意保暖。上篇文章主要为大家介绍了下Netty服务端绑定端口以及注册OP_ACCEPT事件流程,今天就继续为大家分析下服务端启动流程中的关键类NioEventLoopGroup源码实现。

一、温故知新

     public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap server = new ServerBootstrap().group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
            .childHandler(new HeartBeatServerChannelInitializer());

        try {
            ChannelFuture future = server.bind(this.port).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    } 

前文讲过,ServerBootstrap启动的过程中,会为启动类ServerBootstrap设置两个线程组,分别为boss线程和worker线程,一般来说,boss线程为1个,worker线程数为cpu核数x2,下面就让我们来看看,NioEventLoopGroup在服务端初始化的流程中到底起什么作用吧。

二、NioEventLoopGroup解析

image.png 通过类图可以看到,NioEventLoopGroup父类为MultithreadEventLoopGroup,父类继承了抽象类MultithreadEventExcutorGroup实现了EventLoopGruop。初始化NioEventLoopGroup的时候,会调用父类的构造方法,决定生成多少个NioEventLoop线程,默认是CPU核数的两倍。

    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
    }
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

MultithreadEventLoopGroup最终是调用的父类MultithreadEventExecutorGroup构造函数进行初始化,同时,大部分功能也都是直接调用的父类方法,下面就让我们来看下MultithreadEventExecutorGroup的实现

三、MultithreadEventExecutorGroup解析

3.1. 初始化函数

    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        checkPositive(nThreads, "nThreads");

        if (executor == null) {
            // 获取线程执行器executor和线程工厂
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

        // 根据线程数构建EventExecutor数组
        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                // 初始化线程组中的线程,通过NioEventLoopGroup创建NioEventLoop实例
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
               ... ... 
            } finally {
                // 初始化失败,清理资源
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }
        // 根据线程数,创建选择器
        chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };
        // 为每个EventLoop添加终止监听器
        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        // 创建只读副本
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }

3.2. newChild实现

查看newChild方法,实际上是个抽象方法,查看其实现如下,可以看到对应多种实现。 image.png 以本文主要分析的NioEventLoopGroup实现为例,查看其实现如下:

    @Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
    }

创建NioEventLoop时,一共有个6个参数

  • 第一个为NioEventLoopGroup自身
  • 第二个为线程执行器,用于启动线程
  • 第三个参数为Nio的Selector选择器的提供者
  • 第四个参数主要在NioEventLoop的run方法中用于控制选择循环
  • 第五个参数为非I/O任务提交被拒绝时的处理Handler
  • 第六个参数为队列工厂,在NioEventLoop中,队列读是单线程操作,队列写则可能是多线程操作,默认为MpscChunkedArrayQueue

3.3. newChooser解析

newChooser方法,最终由DefaultEventExecutorChooserFactory实现,查看源码如下:

    @Override
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

根据线程是否是2的幂次来选择策略,是的话选择PowerOfTwoEventExecutorChooser,否则选择GenericEventExecutorChooser。二者的区别在于,前者通过与运算计算下一个选择的线程组index,后者是通过求余的方式计算下一个线程在线程组的index。其中与运算的性能会更好一点,因此我们在设置worker线程数的时候如果想要自定义,尽量设置为2的倍数。

3.4. next方法分析

    @Override
    public EventExecutor next() {
        return chooser.next();
    }

    private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }
    private static final class GenericEventExecutorChooser implements EventExecutorChooserFactory.EventExecutorChooser {
        public EventExecutor next() {
            return this.executors[(int)Math.abs(this.idx.getAndIncrement() % (long)this.executors.length)];
        }
    }

通过next方法获取NioEventLoop线程,最终根据不同的选择器,选择不同的实现进行选择,这里就不赘述了。

四、小结

本文主要分析了Netty启动时用到的组件NioEventLoopGroup实现原理,通过类图发现,除了NioEventLoopGroup线程组,Netty还提供了一套由epoll模型实现的EpollEventLoopGroup,以及其他I/O多路复用模型线程组,本文主要分析NioEventLoopGroup实现,其他线程组后面有机会在详细比对下。总结来说,NioEventLoopGroup其实主要完成以下三件事:

  • 创建一定数量的NioEventLoop线程
  • 创建线程选择器chooser,通过选择器来获取线程
  • 创建线程工厂并构建线程执行器