Netty 核心原理之运行机制

文章目录

  • Netty 核心原理之运行机制
    • 一、包含的知识点
    • 二、Reactor线程模型
      • 2.1 Reactor单线程模型
      • 2.2 Reactor多线程模型
      • 2.3 Reactor主从多线程模型
    • 三、EventLoopGroup实例化流程
      • 3.1 EventLoopGroup类继承关系
      • 3.2 EventLoopGroup创建流程
      • 3.3 总结
    • 四、EventLoop任务执行者
      • 4.1 EventLoop类继承关系
      • 4.2 SingleThreadEventExecutor
      • 4.3 NioEventLoop作用
      • 4.4 线程启动流程
    • 五、EventLoop和Channel关系
      • 5.1 ServerBootStrap服务启动
      • 5.2 EventLoopGroup创建
      • 5.3 EventLoop启动流程补充
    • 六、ServerBoostrap EventLoop启动流程图

Netty 核心原理之运行机制

一、包含的知识点

  • Reactor线程模型
  • EventLoopGroup和Reactor之间的关联
  • EventLoop和Channel的关联
  • EventLoop启动流程

二、Reactor线程模型

​ 在文章 Netty基本原理 中已经大致介绍过Reactor线程模型, 为了和后面内容承上启下, 这里再做一个简单介绍。Reactor主要分为

  • Reactor单线程模型
  • Reactor多线程模型
  • Reactor主从多线程模型

2.1 Reactor单线程模型

​ 单线程模型是指Acceptor和Handler在同一个线程中处理; 这种线程模型存在下面问题

  • 如果某个Handler阻塞, 会导致其它Client的Handler被阻塞, 不能进行业务处理
  • Acceptor也会被阻塞, 不能接受新的Client请求
//单线程模型使用示例代码
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup);

Netty 核心原理之运行机制_第1张图片

2.2 Reactor多线程模型

​ 多线程模型和单线程模型区别是, 各个Client连接的IO操作由一组NIO线程提供, Acceptor还是由一个线程来处理, 其特点如下:

  • Acceptor有一个专门的线程用于处理客户端的TCP连接请求
  • 客户端连接的IO操作由一个特定的NIO线程池负责
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(100);
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup, workerGroup);

Netty 核心原理之运行机制_第2张图片

Note: 一个Client的IO操作只能由同一个线程执行, 但是一个线程可以处理多个Client的IO操作

2.3 Reactor主从多线程模型

​ Reactor多线程模型的acceptor只有一个线程处理客户端的TCP请求, 如果高并发情况下有大量客户端发起连接请求, 可能造成大量客户端无法连接到服务端。为了解决这个问题, Reactor主从多线程模型, 对接收客户端的连接请求不在是一个线程, 而是一个独立的线程池,即acceptor使用了线程池来处理客户端的请求。

EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup,workerGroup);

Netty 核心原理之运行机制_第3张图片

三、EventLoopGroup实例化流程

3.1 EventLoopGroup类继承关系

​ 首先我们看下EventLoopGroup的类继承关系, 然后基于下面的代码分析EventLoopGroup初始化过程。

EventLoopGroup bossGroup = new NioEventLoopGroup();

Netty 核心原理之运行机制_第4张图片

3.2 EventLoopGroup创建流程

​ 创建EventLoopGroup时, 通过重载方式创建, 看下下面的代码

//NioEventLoopGroup
public NioEventLoopGroup() {
  this(0);
}

//... 省略部分其它构造函数

public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                         final SelectStrategyFactory selectStrategyFactory) {
  //调用的是父类 MultithreadEventLoopGroup 创建NioEventLoopGroup对象
  super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
}

//MultithreadEventLoopGroup
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
  // 继续调用父类 MultithreadEventExecutorGroup 进行重建
  super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

//MultithreadEventExecutorGroup
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
  // 创建NioEventLoopGroup的实际入口
  this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}

​ 从代码实现和类继承关系, 创建NioEventLoopGroup的入口是MultithreadEventExecutorGroup,下面是具体实现逻辑代码

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
	//省略参数校验逻辑
  children = new EventExecutor[nThreads];

  for (int i = 0; i < nThreads; i ++) {
    boolean success = false;
    try {
      /**
      * 调用抽象方法进行 children 数组创建, NioEventLoopGroup实现创建逻辑, 返回NioEventLoop
      */
      children[i] = newChild(executor, args);
      success = true;
    } catch (Exception e) {
      // TODO: Think about if this is a good exception type
      throw new IllegalStateException("failed to create a child event loop", e);
    } finally {
      //省略服务停止逻辑
    }
  }

  chooser = chooserFactory.newChooser(children);
	
  //创建服务终止监听器Listener
  final FutureListener<Object> terminationListener = new FutureListener<Object>() {
    @Override
    public void operationComplete(Future<Object> future) throws Exception {
      if (terminatedChildren.incrementAndGet() == children.length) {
        terminationFuture.setSuccess(null);
      }
    }
  };
	
  // 给每个children添加刚刚创建的监听器
  for (EventExecutor e: children) {
    e.terminationFuture().addListener(terminationListener);
  }
	
  //创建children的只读副本 childrenSet
  Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
  Collections.addAll(childrenSet, children);
  readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
  return new NioEventLoop(this, executor, (SelectorProvider) args[0],
                          ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) 		args[2]);
}

3.3 总结

  1. EventLoopGroup的创建逻辑是在MultithreadEventExecutorGroup实现的, 内部维护了一个EventExecutor childrean数组

  2. EventExecutor数组的长度由nThreads指定, 如果给定初始值则由给定值决定, 如果没有给定初始值, 则是CPU核数 * 2

  3. EventExecutor数组是由newChild抽象方法创建, 这个方法的逻辑在NioEventLoopGroup类中实现, 返回NioEventLoop对象

  4. 初始化NioEventLoop主要属性

    4.1 通过args[0] 获得SelectorPrivider对象, 再通过provider对象的openSelector()方法获得Selector对象

    4.2 通过args[1] 获得SelectorStrategyFactory对象, 再通过newSelectStrategy获得对应的具体策略

    4.3 通过args[2] 获得拒绝策略Handler

四、EventLoop任务执行者

4.1 EventLoop类继承关系

​ 在第三节中我们分析知道, MultithreadEventExecutorGroup内部维护了EventExcutor children数组, 数组元素通过newChild创建, 这个方法是一个抽象方法, 是在NioEventLoopGroup实现, 返回NioEventLoop对象, 下面是类即成关系图

Netty 核心原理之运行机制_第5张图片

​ 从类继承关系可以看出, NioEventLoopGroup创建的NioEventLoop对象是EventExecutor的子类, 而NioEventLoop继承自SingleThreadEventExecutor类, SingleThreadEventExecutor是Netty的本地线程对象

4.2 SingleThreadEventExecutor

​ SingleThreadEventExecutor是Netty的本地线程对象, 内部维护了一个volatile修饰的Thread对象, 下面是关键的部分代码, thread存储了一个本地java线程, 当创建NioEventLoop对象时, 实际是和一个特定的线程进行绑定, 在生命周期内, 绑定的线程不会改变(从Thread用volatile修饰也可以知道 thread基本是只读的)

private final Queue<Runnable> taskQueue; // 任务队列taskQueue

private volatile Thread thread; // 执行业务逻辑线程thread
@SuppressWarnings("unused")
private volatile ThreadProperties threadProperties; //线程属性
private final Executor executor; // 线程池对象root
private volatile boolean interrupted; // 中断标志

private final Semaphore threadLock = new Semaphore(0);
private final Set<Runnable> shutdownHooks = new LinkedHashSet<Runnable>(); // 钩子, 停止线程
private final boolean addTaskWakesUp;
private final int maxPendingTasks; // 最大pending任务数量
private final RejectedExecutionHandler rejectedExecutionHandler; // 线程拒绝策略handler

private long lastExecutionTime; // 上次执行时间

@SuppressWarnings({ "FieldMayBeFinal", "unused" })
private volatile int state = ST_NOT_STARTED;

private volatile long gracefulShutdownQuietPeriod; //平滑停止服务优先级
private volatile long gracefulShutdownTimeout; //平滑停止服务超时时间
private long gracefulShutdownStartTime; // 平滑停止服务开始时间

4.3 NioEventLoop作用

​ NioEventLoop有两个作用

  • 第一个任务是作为IO线程, 执行Channel相关的IO操作, 实际调用Selector上等待就绪的事件
  • 执行taskQueue队列中的任务, 或eventLoop.schedule提交的任务队列

​ 从之前讲解Channel和Selector知识, 对上面第一点比较容易理解, 对第二点提交的定时任务处理怎么理解呢?

  1. NioEventLoop实例的execute()方法向任务队列taskQueue添加任务, 并进行调度执行
  2. NioEventLoop的类继承关系, NioEventLoop -> SingleThreadEventLoop -> SingleThreadEventExecutor -> AbstractScheduledEventExecutor, 在AbstractScheduledEventExecutor抽象类中有个任务队列变量, 如下,在SingleThreadEventLoop类又实现了任务队列功能
Queue<ScheduledFutureTask<?>> scheduledTaskQueue;

4.4 线程启动流程

​ NioEventLoop启动时会执行run方法, 如果当前选择策略是SelectStrategy.SELECT, 会执行select方法

protected void run() {
        for (;;) {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT: // 如果KEY是 SELECT, 执行select操作
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                    default:
                        // fallthrough
                }

               // ... 省略其它代码
    }

​ 在进行select操作时,如果需要rebuild操作,会执行rebuildSelector()方法

private void select(boolean oldWakenUp) throws IOException {
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
            for (;;) {
                // ... 省略部分其它代码

                long time = System.nanoTime();
                if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                    // timeoutMillis elapsed without anything selected.
                    selectCnt = 1;
                } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                        selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) { // 自动rebuild阈值>0, 且selectCnt>阈值, 进行rebuild操作
                    // The selector returned prematurely many times in a row.
                    // Rebuild the selector to work around the problem.
                    logger.warn(
                            "Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
                            selectCnt, selector);

                    rebuildSelector();
                    selector = this.selector;

                    // Select again to populate selectedKeys.
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }

                currentTimeNanos = time;
            }

            // ...省略部分其它代码
    }

​ 因为启动时主线程和当前调用线程是不一样的,初始时刻 inEventLoop()方法为false 查看下面代码

public boolean inEventLoop(Thread thread) {
  return thread == this.thread;
}

​ 因为inEventLoop为false, 会执行execute方法,

public void rebuildSelector() {
  if (!inEventLoop()) {
    execute(new Runnable() {
      @Override
      public void run() {
        rebuildSelector();
      }
    });
    return;
  }
  // ... 省略其它代码
}
public void execute(Runnable task) {
  if (task == null) {
    throw new NullPointerException("task");
  }

  boolean inEventLoop = inEventLoop();
  if (inEventLoop) {
    addTask(task);
  } else {
    startThread(); // 线程启动
    addTask(task); // 将任务添加到taskQueue队列中
    if (isShutdown() && removeTask(task)) {
      reject();
    }
  }

  if (!addTaskWakesUp && wakesUpForTask(task)) {
    wakeup(inEventLoop);
  }
}

​ 因为inEventLoop为false, 会执行else对应的逻辑,执行startThread方法进行线程启动, 实际执行的是doStartThread方法, 启动线程逻辑是SingleThreadEventExecutor.this.run(), 实现类在NioEventLoop, 和本小节开始时分析相对应。

private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    SingleThreadEventExecutor.this.run(); // 启动线程, 抽象方法,NioEventLoop是对应的实现, 
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    // ... 省略部分代码
            }
        });
    }

五、EventLoop和Channel关系

5.1 ServerBootStrap服务启动

​ ServerBootStrap服务在启动后会进行服务启动和register操作, 查看下面的代码示例

ServerBootstrap server = new ServerBootstrap();
server.group(bossGroup,workerGroup);
server.bind(port) ;

​ 服务通过bind进行端口绑定和服务启动操作, 内部实际调用的是doBind()、bind0()操作

//AbstractBootstrap
public ChannelFuture bind(SocketAddress localAddress) {
  validate();
  if (localAddress == null) {
    throw new NullPointerException("localAddress");
  }
  return doBind(localAddress);
}


//
private ChannelFuture doBind(final SocketAddress localAddress) {
  final ChannelFuture regFuture = initAndRegister(); // 核心: 将Channel和EventLoop进行绑定操作
  final Channel channel = regFuture.channel();
  if (regFuture.cause() != null) {
    return regFuture;
  }

  // ... 省略其它bind0() 操作
}


//初始话Channel并和NioEventLoop绑定
final ChannelFuture initAndRegister() {
  Channel channel = null;
  try {
    channel = channelFactory.newChannel(); // ChannelFactory创建Channel
    init(channel);
  } catch (Throwable t) {
    if (channel != null) {
      // channel can be null if newChannel crashed (eg SocketException("too many open files"))
      channel.unsafe().closeForcibly();
    }
    // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
    return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
  }

  ChannelFuture regFuture = config().group().register(channel); // channel和NioEventLoop绑定核心逻辑
  return regFuture;
}

5.2 EventLoopGroup创建

​ config()是一个抽象类, 分别根据ServerBoostrap、Bootstrap有不同的实现, 这里以ServerBootStrap为例进行说明, 方法执行后会创建ServerBootstrapConfig

public final ServerBootstrapConfig config() {
  return config; // 返回 ServerBootstrapConfig
} 

​ ServerBootstrapConfig继承自AbstractBootstrapConfig, 在AbstractBootstrapConfig类中, 通过下面的代码创建EventLoopGroup对象

//ServerBootstrap
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this); // this就是ServerBootStrap

//ServerBootstrapConfig
public final EventLoopGroup group() {
  return bootstrap.group();
}

//AbstractBootStrap <- ServerBootstrap
public final EventLoopGroup group() {
  return group; // group是bossGroup(EventLoopGroup)
}

​ EventLoopGroup是一个抽象类, 其核心子类是MultithreadEventLoopGroup, 内部通过register方法将Channel和EventLoopGroup进行绑定,查看接口调用

//MultithreadEventLoopGroup -> EventLoopGroup
public ChannelFuture register(Channel channel) {
  return next().register(channel);
}

//SingleThreadEventLoop
public ChannelFuture register(Channel channel) {
  return register(new DefaultChannelPromise(channel, this));
}

public ChannelFuture register(final ChannelPromise promise) {
  ObjectUtil.checkNotNull(promise, "promise");
  // 通过unsafe方法进行注册, 将EventLoop和Channel绑定
  // 这里unsafe是Netty自己定义的接口, 不是操作系统native操作Unsafe
  promise.channel().unsafe().register(this, promise); 
  return promise;
}

5.3 EventLoop启动流程补充

​ 在4.4节中, 我们初步分析了EventLoop的启动流程, 这里再做一个补充。 从4.2节我们知道NioEventLoop的父类SingleThreadEventExecutor内部维护了一个Thread, 因此NioEventLoop的启动实际是对这个thread的启动,我们知道thread的启动是通过thread.start()来启动的,现在从这个思路在SingleThreadEventExecutor类中进行查询, 发现下面的代码

private void startThread() {
  if (STATE_UPDATER.get(this) == ST_NOT_STARTED) {
    if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
      doStartThread();
    }
  }
}

​ STATE_UPDATER是一个原子类集合, 内部维护了线程状态, 初始时刻线程状态是ST_NOT_STARTED, 会执行if条件内部的逻辑, 在CAS成功后会执行doStartThread()方法, 进行线程启动

private static final AtomicIntegerFieldUpdater<SingleThreadEventExecutor> STATE_UPDATER;

​ startThread方法是私有方法, 它是在execute方法调用的,下面是源码, 因为服务启动是在主线程中运行的, 下面代码走else逻辑, 执行startThread操作, 并将task加入taskQueue队列中。

public void execute(Runnable task) {
  if (task == null) {
    throw new NullPointerException("task");
  }

  boolean inEventLoop = inEventLoop();
  if (inEventLoop) {
    addTask(task);
  } else {
    startThread();
    addTask(task);
    if (isShutdown() && removeTask(task)) {
      reject();
    }
  }

  if (!addTaskWakesUp && wakesUpForTask(task)) {
    wakeup(inEventLoop);
  }
}

​ 那么execute()什么时候会被调用呢 ?

在4.4节中我们分析知道,如果需要rebuildSelector, 会执行execute方法; 除此之外,Channel和EventLoop进行register绑定的时候, 也会执行execute方法, 查看下面代码, 服务启动是在主线程中执行的, 进行NIO操作的Thread和主线程不同, 会执行else逻辑, 最后执行EventLoop的execute方法

//AbstractChannel
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
  // 省略部分其它代码
  AbstractChannel.this.eventLoop = eventLoop;

  if (eventLoop.inEventLoop()) { // 主线程启动和当前线程不同, 返回false
    register0(promise);
  } else {
    try {
      eventLoop.execute(new Runnable() {
        @Override
        public void run() {
          register0(promise);
        }
      });
    } catch (Throwable t) {
      logger.warn(
        "Force-closing a channel whose registration task was not accepted by an event loop: {}",
        AbstractChannel.this, t);
      closeForcibly();
      closeFuture.setClosed();
      safeSetFailure(promise, t);
    }
  }
}

​ 总结: 当EventLoop执行execute方法时, 会触发startThread方法调用, 进而对Java本地线程启动。

六、ServerBoostrap EventLoop启动流程图

Netty 核心原理之运行机制_第6张图片

你可能感兴趣的:(NIO,netty)