netty源码解析一之创建Reactor

一:创建Reactor

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Netty中的Reactor是以Group的形式出现的,主从Reactor在Netty中就是主从Reactor组,每个Reactor Group中会有多个Reactor用来执行具体的IO任务。当然在netty中Reactor不只用来执行IO任务,这个我们后面再说。

  • Main Reactor Group中的Reactor数量取决于服务端要监听的端口个数,通常我们的服务端程序只会监听一个端口,所以Main Reactor Group只会有一个Main Reactor线程来处理最重要的事情:绑定端口地址接收客户端连接为客户端创建对应的SocketChannel将客户端SocketChannel分配给一个固定的Sub Reactor。也就是上篇文章笔者为大家举的例子,饭店最重要的工作就是先把客人迎接进来。

  • Sub Reactor Group里有多个Reactor线程,Reactor线程的个数可以通过系统参数-D io.netty.eventLoopThreads指定。默认的Reactor的个数为CPU核数 * 2Sub Reactor线程主要用来轮询客户端SocketChannel上的IO就绪事件处理IO就绪事件执行异步任务Sub Reactor Group做的事情就是上篇饭店例子中服务员的工作,客人进来了要为客人分配座位,端茶送水,做菜上菜。

一个客户端SocketChannel只能分配给一个固定的Sub Reactor。一个Sub Reactor负责处理多个客户端SocketChannel,这样可以将服务端承载的全量客户端连接分摊到多个Sub Reactor中处理,同时也能保证客户端SocketChannel上的IO处理的线程安全性

netty有两种Channel类型:一种是服务端用于监听绑定端口地址的NioServerSocketChannel,一种是用于客户端通信的NioSocketChannel。每种Channel类型实例都会对应一个PipeLine用于编排对应channel实例上的IO事件处理逻辑。PipeLine中组织的就是ChannelHandler用于编写特定的IO处理逻辑。

ChannelInitializer是用于当SocketChannel成功注册到绑定的Reactor上后,用于初始化该SocketChannel的Pipeline。它的initChannel方法会在注册成功后执行。

EventLoop:就是Netty中的Reactor,可以说它就是Netty的引擎,负责Channel上IO就绪事件的监听,IO就绪事件的处理,异步任务的执行驱动着整个Netty的运转。

EventLoopGroup:Netty中的Reactor是以Group的形式出现的,EventLoopGroup正是Reactor组的接口定义,负责管理Reactor,Netty中的Channel就是通过EventLoopGroup注册到具体的Reactor上的。

Netty的IO线程模型是主从Reactor多线程模型,主从Reactor线程组在Netty源码中对应的其实就是两个EventLoopGroup实例。

ServerSocketChannel:用于Netty服务端使用的ServerSocketChannel,负责绑定监听端口地址,接收客户端连接并创建用于与客户端通信的SocketChannel。
		NioServerSocketChannel:表示传输协议,nio还是bio
SocketChannel	:用于与客户端通信的`SocketChannel`,对应于上篇文章提到的`客户端连接Socket`,当客户端完成三次握手后,由系统调用`accept`函数根据`监听Socket`创建。

创建主从Reactor线程组

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 一个是主Reactor线程组bossGroup用于监听客户端连接,创建客户端连接NioSocketChannel,并将创建好的客户端连接NioSocketChannel注册到从Reactor线程组中一个固定的Reactor上。

  • 一个是从Reactor线程组workerGroupworkerGroup中的Reactor负责监听绑定在其上的客户端连接NioSocketChannel上的IO就绪事件,并处理IO就绪事件执行异步任务

    EventLoopGroup boosGroup = new NioEventLoopGroup(1);
    
    public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                             final SelectStrategyFactory selectStrategyFactory) {
        //nThreads:线程数
        //executor:eventLoop具体执行器,负责启动Reactor线程进而Reactor才可以开始工作。
        //selector提供者:创建Selector
        //selectStrategyFactory:select策略工厂(用于判断是否执行select()方法)
        //RejectedExecutionHandler:任务拒绝策略
        super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
    }

NioEventLoopGroup继承结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Mutithread前缀的类定义的行为自然是对Reactor线程组内多个Reactor线程`的创建和管理工作。

public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(MultithreadEventLoopGroup.class);

    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        //默认CPU核心数*2
        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);
        }
    }

    /**
     * @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
     */
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

MultithreadEventLoopGroup:确定Reactor线程组Reactor的个数。

MultithreadEventExecutorGroup这里就是本小节的核心,主要用来定义创建和管理Reactor的行为。

public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {//多线程处理任务

    //Reactor线程组中的Reactor集合
    private final EventExecutor[] children;
    private final Set<EventExecutor> readonlyChildren;
    //记录已经关闭的Reactor个数,用来判断NioEventLoopGroup中的Reactor是否已经全部关闭。
    private final AtomicInteger terminatedChildren = new AtomicInteger();
    //如果所有Reactor均已关闭,设置NioEventLoopGroup中的terminationFuture为success。表示Reactor线程组关闭成功。
    private final Promise<?> terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
    //从Reactor group中选择一个特定的Reactor的选择策略 用于channel注册绑定到一个固定的Reactor上
    private final EventExecutorChooserFactory.EventExecutorChooser chooser;
    
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
}

1.创建Reactor线程

public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
    }

    @Override
    public void execute(Runnable command) {
        //启动Reactor线程
        threadFactory.newThread(command).start();
    }
}

Reactor线程启动的时候,Netty会将Reactor线程要做的事情封装成Runnable,丢给exexutor启动。

Reactor线程的核心就是一个死循环不停的轮询IO就绪事件,处理IO事件,执行异步任务。一刻也不停歇,堪称996典范

Reactor线程组NioEventLoopGroup包含多个Reactor,存放于private final EventExecutor[] children数组中。

所以下面的事情就是创建nThreadReactor,并存放于EventExecutor[] children字段中

protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;

2. 创建Reactor

io.netty.channel.nio.NioEventLoopGroup#newChild

@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    //存储Reactor待执行的异步任务
    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);
}

io.netty.channel.nio.NioEventLoop#NioEventLoop

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
             EventLoopTaskQueueFactory queueFactory) {
    super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
            rejectedExecutionHandler);
    this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
    this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
    //打开一个选择器,返回选择器和SelectionKey数组关联对象
    final SelectorTuple selectorTuple = openSelector();
    //通过用SelectedSelectionKeySet装饰后的unwrappedSelector
    this.selector = selectorTuple.selector;
    //Netty优化过的JDK NIO远程Selector
    this.unwrappedSelector = selectorTuple.unwrappedSelector;
}

创建JDK NIO原生的Selector

private SelectorTuple openSelector() {
    final Selector unwrappedSelector;
    try {
        //通过JDK NIO SelectorProvider创建Selector
        unwrappedSelector = provider.openSelector();
    } catch (IOException e) {
        throw new ChannelException("failed to open a new selector", e);
    }

    if (DISABLE_KEY_SET_OPTIMIZATION) {
        return new SelectorTuple(unwrappedSelector);
    }
 
  ................省略................
}

SelectorProvider会根据操作系统的不同选择JDK在不同操作系统版本下的对应Selector的实现。Linux下会选择Epoll,Mac下会选择Kqueue

java.nio.channels.spi.SelectorProvider#provider

public static SelectorProvider provider() {
    synchronized (lock) {
        if (provider != null)
            return provider;
        return AccessController.doPrivileged(
            new PrivilegedAction<SelectorProvider>() {
                public SelectorProvider run() {
                        if (loadProviderFromProperty())
                            return provider;
                        if (loadProviderAsService())
                            return provider;
                        provider = sun.nio.ch.DefaultSelectorProvider.create();
                        return provider;
                    }
                });
    }
}

加载有三种方式

  • 通过系统变量-D java.nio.channels.spi.SelectorProvider指定SelectorProvider的自定义实现类全限定名。通过应用程序类加载器(Application Classloader)加载。
private static boolean loadProviderFromProperty() {
    String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
    if (cn == null)
        return false;
    try {
        Class<?> c = Class.forName(cn, true,
                                   ClassLoader.getSystemClassLoader());
        provider = (SelectorProvider)c.newInstance();
        return true;
    } catch (ClassNotFoundException x) {
        throw new ServiceConfigurationError(null, x);
    } catch (IllegalAccessException x) {
        throw new ServiceConfigurationError(null, x);
    } catch (InstantiationException x) {
        throw new ServiceConfigurationError(null, x);
    } catch (SecurityException x) {
        throw new ServiceConfigurationError(null, x);
    }
}
  • 通过SPI方式加载。在工程目录META-INF/services下定义名为java.nio.channels.spi.SelectorProviderSPI文件,文件中第一个定义的SelectorProvider实现类全限定名就会被加载。
private static boolean loadProviderAsService() {

    ServiceLoader<SelectorProvider> sl =
        ServiceLoader.load(SelectorProvider.class,
                           ClassLoader.getSystemClassLoader());
    Iterator<SelectorProvider> i = sl.iterator();
    for (;;) {
        try {
            if (!i.hasNext())
                return false;
            provider = i.next();
            return true;
        } catch (ServiceConfigurationError sce) {
            if (sce.getCause() instanceof SecurityException) {
                // Ignore the security exception, try the next provider
                continue;
            }
            throw sce;
        }
    }
}
  • 如果以上两种方式均未被定义,那么就采用SelectorProvider系统默认实现sun.nio.ch.DefaultSelectorProvider。笔者当前使用的操作系统是MacOS,从源码中我们可以看到自动适配了KQueue实现。
/**
 * Returns the default SelectorProvider.
 */
public class KQueueSelectorProvider extends SelectorProviderImpl {
    public KQueueSelectorProvider() {
    }

    public AbstractSelector openSelector() throws IOException {
        return new KQueueSelectorImpl(this);
    }
}

Netty对JDK NIO 原生Selector的优化

首先在NioEventLoop中有一个Selector优化开关DISABLE_KEY_SET_OPTIMIZATION,通过系统变量-D io.netty.noKeySetOptimization指定,默认是开启的,表示需要对JDK NIO原生Selector进行优化。

public final class NioEventLoop extends SingleThreadEventLoop {
//Selector优化开关 默认开启 为了遍历的效率 会对Selector中的SelectedKeys进行数据结构优化
private static final boolean DISABLE_KEY_SET_OPTIMIZATION =
        SystemPropertyUtil.getBoolean("io.netty.noKeySetOptimization", false);
}
private SelectorTuple openSelector() {
..........SelectorProvider创建JDK NIO  原生Selector..............

    //JDK NIO原生Selector ,Selector优化开关 默认开启需要对Selector进行优化
    if (DISABLE_KEY_SET_OPTIMIZATION) {
        //这里说明不进行优化
        return new SelectorTuple(unwrappedSelector);
    }
    ................省略................
}

下面为Netty对JDK NIO原生的Selector的优化过程:

  1. 获取JDK NIO原生Selector的抽象实现类sun.nio.ch.SelectorImplJDK NIO原生Selector的实现均继承于该抽象类。用于判断由SelectorProvider创建出来的Selector是否为JDK默认实现SelectorProvider第三种加载方式)。因为SelectorProvider可以是自定义加载,所以它创建出来的Selector并不一定是JDK NIO 原生的。
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
    @Override
    public Object run() {
        try {
            return Class.forName(
                    "sun.nio.ch.SelectorImpl",
                    false,
                    PlatformDependent.getSystemClassLoader());
        } catch (Throwable cause) {
            return cause;
        }
    }
});

//判断是否可以对Selector进行优化,这里主要针对JDK NIO原生Selector的实现类进行优化,因为SelectorProvider可以加载的是自定义Selector实现
//如果SelectorProvider创建的Selector不是JDK原生sun.nio.ch.SelectorImpl的实现类,那么无法进行优化,直接返回
if (!(maybeSelectorImplClass instanceof Class) ||
    // ensure the current selector implementation is what we can instrument.
    !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
    if (maybeSelectorImplClass instanceof Throwable) {
        Throwable t = (Throwable) maybeSelectorImplClass;
        logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
    }
    return new SelectorTuple(unwrappedSelector);
}
  1. 判断由SelectorProvider创建出来的Selector是否是JDK NIO原生的Selector实现。因为Netty优化针对的是JDK NIO 原生Selector。判断标准为sun.nio.ch.SelectorImpl类是否为SelectorProvider创建出Selector的父类。如果不是则直接返回。不在继续下面的优化过程。
//判断是否可以对Selector进行优化,这里主要针对JDK NIO原生Selector的实现类进行优化,因为SelectorProvider可以加载的是自定义Selector实现
//如果SelectorProvider创建的Selector不是JDK原生sun.nio.ch.SelectorImpl的实现类,那么无法进行优化,直接返回
if (!(maybeSelectorImplClass instanceof Class) ||
    // ensure the current selector implementation is what we can instrument.
    !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
    if (maybeSelectorImplClass instanceof Throwable) {
        Throwable t = (Throwable) maybeSelectorImplClass;
        logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
    }
    return new SelectorTuple(unwrappedSelector);
}
  1. 创建SelectedSelectionKeySet通过反射替换掉sun.nio.ch.SelectorImpl类selectedKeyspublicSelectedKeys的默认HashSet实现。

为什么要用SelectedSelectionKeySet替换掉原来的HashSet呢??

因为这里涉及到对HashSet类型sun.nio.ch.SelectorImpl#selectedKeys集合的两种操作:

  • 插入操作: 通过前边对sun.nio.ch.SelectorImpl类中字段的介绍我们知道,在Selector监听到IO就绪SelectionKey后,会将IO就绪SelectionKey插入sun.nio.ch.SelectorImpl#selectedKeys集合中,这时Reactor线程会从java.nio.channels.Selector#select(long)阻塞调用中返回(类似上篇文章提到的epoll_wait)。
  • 遍历操作:Reactor线程返回后,会从Selector中获取IO就绪SelectionKey集合(也就是sun.nio.ch.SelectorImpl#selectedKeys),Reactor线程遍历selectedKeys,获取IO就绪SocketChannel,并处理SocketChannel上的IO事件

我们都知道HashSet底层数据结构是一个哈希表,由于Hash冲突这种情况的存在,所以导致对哈希表进行插入遍历操作的性能不如对数组进行插入遍历操作的性能好。

还有一个重要原因是,数组可以利用CPU缓存的优势来提高遍历的效率。

所以Netty为了优化对sun.nio.ch.SelectorImpl#selectedKeys集合的插入,遍历性能,自己用数组这种数据结构实现了SelectedSelectionKeySet,用它来替换原来的HashSet实现。

final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
    //采用数组替换到JDK中的HashSet,这样add操作和遍历操作效率更高,不需要考虑hash冲突
    SelectionKey[] keys;
    //通过数组尾部指针size,在向数组插入元素的时候可以直接定位到插入位置keys[size++]。操作一步到位,不用像哈希表那样还需要解决Hash冲突。
    int size;

    SelectedSelectionKeySet() {
        //初始化数组容量1024,不够用扩容一倍
        keys = new SelectionKey[1024];
    }

    @Override
    public boolean add(SelectionKey o) {
        if (o == null) {
            return false;
        }
        //时间复杂度O(1)
        keys[size++] = o;
        if (size == keys.length) {
            //扩容为原来的两倍大小
            increaseCapacity();
        }

        return true;
    }
    
    }
  1. Netty通过反射的方式用SelectedSelectionKeySet替换掉sun.nio.ch.SelectorImpl#selectedKeyssun.nio.ch.SelectorImpl#publicSelectedKeys这两个集合中原来HashSet的实现。
    1. Java9版本以上通过sun.misc.Unsafe设置字段值的方式
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
    @Override
    public Object run() {
        try {
            //反射获取 selectedKeys 和 publicSelectedKeys 字段
            //IO就绪的SelectionKey(里面包裹着channel)
            Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
            //当有IO就绪的SelectionKey时,向调用线程返回。只可删除其中元素,不可增加
            Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");

            //Java9版本以上通过sun.misc.Unsafe设置字段值的方式
            if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
                // Let us try to use sun.misc.Unsafe to replace the SelectionKeySet.
                // This allows us to also do this in Java9+ without any extra flags.
                //获取字段偏移地址
                long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
                long publicSelectedKeysFieldOffset =
                        PlatformDependent.objectFieldOffset(publicSelectedKeysField);

                //将字段替换为selectedKeySet
                if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) {
                    PlatformDependent.putObject(
                            unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
                    PlatformDependent.putObject(
                            unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
                    return null;
                }
                // We could not retrieve the offset, lets try reflection as last-resort.
            }
  • 通过反射的方式用SelectedSelectionKeySet替换掉hashSet实现的sun.nio.ch.SelectorImpl#selectedKeys,sun.nio.ch.SelectorImpl#publicSelectedKeys
        Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
        if (cause != null) {
            return cause;
        }
        cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
        if (cause != null) {
            return cause;
        }
        //用反射替代默认实现
        selectedKeysField.set(unwrappedSelector, selectedKeySet);
        publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
        return null;
    } catch (NoSuchFieldException e) {
        return e;
    } catch (IllegalAccessException e) {
        return e;
    }
}
  1. 将与sun.nio.ch.SelectorImpl类中selectedKeys和publicSelectedKeys关联好的Netty优化实现SelectedSelectionKeySet,设置到io.netty.channel.nio.NioEventLoop#selectedKeys字段中保存。
//4:将与sun.nio.ch.SelectorImpl类中selectedKeys和publicSelectedKeys关联好的Netty优化实现SelectedSelectionKeySet,
// 设置到io.netty.channel.nio.NioEventLoop#selectedKeys字段中保存。
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector,
                         new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));

wrappedSelector会将所有对Selector的操作全部代理给unwrappedSelector,并在发起轮询IO事件的相关操作中,重置SelectedSelectionKeySet清空上一次的轮询结果。

final class SelectedSelectionKeySetSelector extends Selector {
    //Netty优化后的 SelectedKey就绪集合
    private final SelectedSelectionKeySet selectionKeys;
    //优化后的JDK NIO 原生Selector
    private final Selector delegate;
    
        @Override
    public int selectNow() throws IOException {
        //重置SelectedKeys集合
        selectionKeys.reset();
        return delegate.selectNow();
    }

    @Override
    public int select(long timeout) throws IOException {
        selectionKeys.reset();
        return delegate.select(timeout);
    }
    }

创建保存异步任务的队列

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
             EventLoopTaskQueueFactory queueFactory) {
    super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
            rejectedExecutionHandler);
    this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
    this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
    //打开一个选择器,返回选择器和SelectionKey数组关联对象
    final SelectorTuple selectorTuple = openSelector();
    //通过用SelectedSelectionKeySet装饰后的unwrappedSelector
    this.selector = selectorTuple.selector;
    //Netty优化过的JDK NIO远程Selector
    this.unwrappedSelector = selectorTuple.unwrappedSelector;
}

我们继续回到创建Reactor的主线上,到目前为止Reactor的核心Selector就创建好了,前边我们提到Reactor除了需要监听IO就绪事件以及处理IO就绪事件外,还需要执行一些异步任务,当外部线程向Reactor提交异步任务后,Reactor就需要一个队列来保存这些异步任务,等待Reactor线程执行。

下面我们来看下Reactor中任务队列的创建过程:

    //任务队列大小,默认是无界队列,最小16个。
    protected static final int DEFAULT_MAX_PENDING_TASKS = Math.max(16,
            SystemPropertyUtil.getInt("io.netty.eventLoop.maxPendingTasks", Integer.MAX_VALUE));
            
private static Queue<Runnable> newTaskQueue(
        EventLoopTaskQueueFactory queueFactory) {
    if (queueFactory == null) {
        return newTaskQueue0(DEFAULT_MAX_PENDING_TASKS);
    }
    return queueFactory.newTaskQueue(DEFAULT_MAX_PENDING_TASKS);
}
  • NioEventLoop的父类SingleThreadEventLoop中提供了一个静态变量DEFAULT_MAX_PENDING_TASKS用来指定Reactor任务队列的大小。可以通过系统变量-D io.netty.eventLoop.maxPendingTasks进行设置,默认为Integer.MAX_VALUE,表示任务队列默认为无界队列
  • 根据DEFAULT_MAX_PENDING_TASKS变量的设定,来决定创建无界任务队列还是有界任务队列。
private static Queue<Runnable> newTaskQueue0(int maxPendingTasks) {
    // This event loop never calls takeTask()
    return maxPendingTasks == Integer.MAX_VALUE ? PlatformDependent.<Runnable>newMpscQueue()//创建无界任务队列
            : PlatformDependent.<Runnable>newMpscQueue(maxPendingTasks);//创建有界任务队列
}
public static <T> Queue<T> newMpscQueue() {
    return Mpsc.newMpscQueue();
}

    public static <T> Queue<T> newMpscQueue(final int maxCapacity) {
        return Mpsc.newMpscQueue(maxCapacity);
    }

Reactor内的异步任务队列的类型为MpscQueue,它是由JCTools提供的一个高性能无锁队列,从命名前缀Mpsc可以看出,它适用于多生产者单消费者的场景,它支持多个生产者线程安全的访问队列,同一时刻只允许一个消费者线程读取队列中的元素。

我们知道Netty中的Reactor可以线程安全的处理注册其上的多个SocketChannel上的IO数据,保证Reactor线程安全的核心原因正是因为这个MpscQueue,它可以支持多个业务线程在处理完业务逻辑后,线程安全的向MpscQueue添加异步写任务,然后由单个Reactor线程来执行这些写任务。既然是单线程执行,那肯定是线程安全的了。

Reactor对应的NioEventLoop类型继承结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SingleThreadEventLoop

Reactor负责执行的异步任务分为三类:

  • 普通任务:这是Netty最主要执行的异步任务,存放在普通任务队列taskQueue中。在NioEventLoop构造函数中创建。
  • 定时任务: 存放在优先级队列中。后续我们介绍。
  • 尾部任务: 存放于尾部任务队列tailTasks中,尾部任务一般不常用,在普通任务执行完后 Reactor线程会执行尾部任务。**使用场景:**比如对Netty 的运行状态做一些统计数据,例如任务循环的耗时、占用物理内存的大小等等都可以向尾部队列添加一个收尾任务完成统计数据的实时更新。

SingleThreadEventLoop负责对尾部任务队列tailTasks进行管理。并且提供ChannelReactor注册的行为。

public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {
    //任务队列大小,默认是无界队列,最小16个。
    protected static final int DEFAULT_MAX_PENDING_TASKS = Math.max(16,
            SystemPropertyUtil.getInt("io.netty.eventLoop.maxPendingTasks", Integer.MAX_VALUE));
    //尾部任务队列,存放于尾部任务队列tailTasks中,尾部任务一般不常用,在普通任务执行完后 Reactor线程会执行尾部任务。
    // **使用场景:**比如对Netty 的运行状态做一些统计数据,例如任务循环的耗时、占用物理内存的大小等等都可以向尾部队列添加一个收尾任务完成统计数据的实时更新。
    private final Queue<Runnable> tailTasks;

protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
                                boolean addTaskWakesUp, Queue<Runnable> taskQueue, Queue<Runnable> tailTaskQueue,
                                RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, addTaskWakesUp, taskQueue, rejectedExecutionHandler);
  //尾部队列 执行一些统计任务 不常用
    tailTasks = ObjectUtil.checkNotNull(tailTaskQueue, "tailTaskQueue");
}

    @Override
    public ChannelFuture register(Channel channel) {
        //注册channel到绑定的Reactor上
        return register(new DefaultChannelPromise(channel, this));
    }
    
        @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        //将channel注册selector上,事件默认0,将accept和父handler注册到pipe,以及剔除初始化的handler
        promise.channel().unsafe().register(this, promise);
        return promise;
    }
    
    }

SingleThreadEventExecutor

SingleThreadEventExecutor主要负责对普通任务队列的管理,以及异步任务的执行Reactor线程的启停

protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                    boolean addTaskWakesUp, Queue<Runnable> taskQueue,
                                    RejectedExecutionHandler rejectedHandler) {
    //parent为Reactor所属的NioEventLoopGroup Reactor线程组
    super(parent);
    //向Reactor添加任务时,是否唤醒Selector停止轮询IO就绪事件,马上执行异步任务
    this.addTaskWakesUp = addTaskWakesUp;
    //Reactor异步任务队列的大小
    this.maxPendingTasks = DEFAULT_MAX_PENDING_EXECUTOR_TASKS;
    //用于启动Reactor线程的executor -> ThreadPerTaskExecutor
    this.executor = ThreadExecutorMap.apply(executor, this);
    //普通任务队列
    this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue");
    //任务队列满时的拒绝策略
    this.rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}

至此NioEventLoop里面存在如下元素

  • Executor:用于启动Reactor线程的executor
  • selector:选择器
  • NioEventLoopGroup:Reactor所属的NioEventLoopGroup Reactor线程组
  • taskQueue:普通任务队列
  • tailTasks:尾部队列

3. 创建Channel到Reactor的绑定策略

到这一步,Reactor线程组NioEventLoopGroup里边的所有Reactor就已经全部创建完毕。

无论是Netty服务端NioServerSocketChannel关注的OP_ACCEPT事件也好,还是Netty客户端NioSocketChannel关注的OP_READOP_WRITE事件也好,都需要先注册到Reactor上,Reactor才能监听Channel上关注的IO事件实现IO多路复用

NioEventLoopGroup(Reactor线程组)里边有众多的Reactor,那么以上提到的这些Channel究竟应该注册到哪个Reactor上呢?这就需要一个绑定的策略来平均分配。

还记得我们前边介绍MultithreadEventExecutorGroup类的时候提到的构造器参数EventExecutorChooserFactory吗?

这时候它就派上用场了,它主要用来创建ChannelReactor的绑定策略。默认为DefaultEventExecutorChooserFactory.INSTANCE

 
 public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {

    public static final DefaultEventExecutorChooserFactory INSTANCE = new DefaultEventExecutorChooserFactory();
 
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
        this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
    }
    
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, Object... args) {
                                        
                                                //创建channel到Reactor的绑定策略,用来分配获取下一个EventLoop的
        chooser = chooserFactory.newChooser(children);
        ...
        }
        
        }

Netty中的绑定策略就是采用round-robin轮询的方式来挨个选择Reactor进行绑定。

采用round-robin的方式进行负载均衡,我们一般会用round % reactor.length取余的方式来挨个平均的定位到对应的Reactor上。或者采用位运算。

@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
    //判断Reactor线程组中的Reactor个数是否为2的次幂
    if (isPowerOfTwo(executors.length)) {
        return new PowerOfTwoEventExecutorChooser(executors);
    } else {
        return new GenericEventExecutorChooser(executors);
    }
}

利用%运算的方式Math.abs(idx.getAndIncrement() % executors.length)来进行绑定。

private static final class GenericEventExecutorChooser implements EventExecutorChooser {
    // Use a 'long' counter to avoid non-round-robin behaviour at the 32-bit overflow boundary.
    // The 64-bit long solves this by placing the overflow so far into the future, that no system
    // will encounter this in practice.
    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)];
    }
}

利用&运算的方式idx.getAndIncrement() & executors.length - 1来进行绑定。

//reactor数量为2幂次方,就可以用位操作&运算round & reactor.length -1来代替%运算round % reactor.length,因为位运算的性能更高。
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];
    }
}

4. 向Reactor线程组中所有的Reactor注册terminated回调函数

当Reactor线程组NioEventLoopGroup中所有的Reactor已经创建完毕,ChannelReactor的绑定策略也创建完毕后,我们就来到了创建NioEventGroup的最后一步。

俗话说的好,有创建就有启动,有启动就有关闭,这里会创建Reactor关闭的回调函数terminationListener,在Reactor关闭时回调。

terminationListener回调的逻辑很简单:

  • 通过AtomicInteger terminatedChildren变量记录已经关闭的Reactor个数,用来判断NioEventLoopGroup中的Reactor是否已经全部关闭。
  • 如果所有Reactor均已关闭,设置NioEventLoopGroup中的terminationFuturesuccess。表示Reactor线程组关闭成功。
    private final AtomicInteger terminatedChildren = new AtomicInteger();

final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    //当所有的reactor关闭后,确认关闭成功。
                    terminationFuture.setSuccess(null);
                }
            }
        };
        //为所有Reactor添加terminationListener
        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }
   

5:建立一个只读不能修改的child的副本

Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);

回到服务端代码

//(1) 创建EventLoopGroup,来接受和处理新的连接,以进行事件的处理,如接受新连接以及读/写数据
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

现在Netty的主从Reactor线程组就已经创建完毕,此时Netty服务端的骨架已经搭建完毕,骨架如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值