Netty4 服务端启动解析一

855 阅读12分钟

简要

在上篇中主要简洁介绍了Netty服务端的启动流程以及相应的入口。在接下来的文章里会针对每一部分来学习,化整为零,攻克每一小部分。一点点积累并且享受学习的过程。

每次学习的过程中都有一种发现新大陆的感觉,建议在学习时,自己也要跟着去看源码,不仅加深印象也可以提高读源码的技巧

Netty是基于Java NIO 实现的,所以离不开Selector,ServerSocketChannel,SocketChannel和selectKey等,Netty 将这些封装到自己的底层。先来看下Netty的服务端启动。

还没有写demo感受Netty框架? 建议动手去写写,毕竟好记性胜不过烂笔头

Netty服务端代码示例

public class TimeServer {

  private int port;

  public TimeServer(int port) {
      this.port = port;
  }

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

      try {
          ServerBootstrap b = new ServerBootstrap();  
          b.group(bossGroup, workGroup)
                  .channel(NioServerSocketChannel.class)  
                  .childHandler(new ChannelInitializer<SocketChannel>() {
                      @Override
                      protected void initChannel(SocketChannel socketChannel) throws Exception {
                          socketChannel.pipeline().addLast(new TimeServerHandler());
                      }
                  })
                  .option(ChannelOption.SO_BACKLOG, 128)  
                  .childOption(ChannelOption.SO_KEEPALIVE, true);  

          //绑定端口,开始接收进来的连接
          ChannelFuture f = b.bind(port).sync();  
          
          f.channel().closeFuture().sync();
      } catch (InterruptedException e) {
          e.printStackTrace();
      } finally {
          bossGroup.shutdownGracefully();
          workGroup.shutdownGracefully();
      }
  }

  public static void main(String[] args) {
      int port = 8080;
      if (args != null && args.length > 0) {
          try {
              port = Integer.valueOf(args[0]);
          } catch (NumberFormatException e) {
              e.printStackTrace();
          }
      }
      new TimeServer(port).run();
  }
}

ServerBootStrap-Netty的服务端启动类

一切从 ServerBootStrap开始
ServerBootStrap实例中需要两个NioEventLoopGroup实例,实现了接口EventLoopGroup,也就是Netty线程模型中的Reactor线程,一般按照职责划分成boss 和work,有着不同的分工:

boss负责请求的accept,并将连接丢给work来处理
work负责请求的read,write

NioEventLoopGroup做了些啥?

NioEventLoopGroup是个具体的子类,先去看下它的父类也就是EventLoopGroup,其实质是一个EventLoop的数组。

eventLoop是什么?内部的一个处理线程,数量默认是处理器核个数的两倍。关系如下:

EventLoopGroup和EventLoop的关系图

来看下 该类的继承关系图:

EventLoopGroup类的继承关系图

心里有点崩溃? 大佬写的果然晦涩难懂

个人理解:
EventLoopGroup 事件循环组:既然是组,应该包含多个事件循环了。也有提到过其实质就是数组;
EventLoop 事件循环: 事件应该是网络事件,循环则是不断轮询来处理事件;
EventExecutorGroup 事件执行组: 负责执行处理事件,顶层继承了JDK的Executor,看到这个应该有所熟悉了。 对于执行处理事件的类也都标有 Executor,后续也会介绍到一个EventLoop会绑定一个线程,其类的命名也标有Thread。这样简单区分是不是很容易理解,大佬果然厉害啊!!!
但是对这些类之间的继承关系,为什么这么设计?目前还不是很懂

下面讲到的线程实例:一个EventLoop+绑定到该EventLoop上的一个线程,这可不是单纯的Thread。 来看下类的命名:
public final class NioEventLoop extends SingleThreadEventLoop {} 后面讲到的Reactor线程池其实是EventLoopGroup ,可以看成是包含多个EventLoop的数组

继续来看下源码

/** 
 * Netty的服务端开发时,先new了两个Reactor线程池,在练习时一般用无参构造函数就够了
 * 或者设置线程数量的,其余参数默认
 * 来看下这个构造函数做了啥
 */
public NioEventLoopGroup() {
    this(0);
}
...
//NioEventLoopGroup的完整构造函数
public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
                         final SelectorProvider selectorProvider,
                         final SelectStrategyFactory selectStrategyFactory,
                         final RejectedExecutionHandler rejectedExecutionHandler,
                         final EventLoopTaskQueueFactory taskQueueFactory) {
    //调用父类MultiThreadEventLoopGroup的构造函数
    super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory,
            rejectedExecutionHandler, taskQueueFactory); 
}

看下构造函数的各个参数:

1.nThreads:线程数,用来指定Reactor线程池的数量,也就是EventLoopGroup的数量。

默认为MultiThreadEventLoopGroup中DEFAULT_EVENT_LOOP_THREADS

2.executor:线程池, 之前有提到每一个EventLoop会绑定一个线程,Selector的轮询操作是由绑定线程的run方法驱动,可以暂且理解为该executor就是用来启动绑定到EventLoop上的线程

if (executor == null) {
    //默认的初始化线程池
    executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
...
public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        this.threadFactory = threadFactory;
    }

    @Override
    public void execute(Runnable command) {
        //创建一个线程
        threadFactory.newThread(command).start();
    }
}

3.chooserFactory:选择事件执行器的工厂,由MultithreadEventExecutorGroup默认指定DefaultEventExecutorChooserFactory.INSTANCE,当有事件需要处理时, 按照默认策略选择一个事件执行器(也就是Reactor线程中的一个线程)来执行

//chooserFactory返回的事件执行器选择者
public EventExecutorChooser newChooser(EventExecutor[] executors) {
  if (isPowerOfTwo(executors.length)) {
      //Reactor线程池的大小若为2的次幂
      return new PowerOfTwoEventExecutorChooser(executors);
  } else {
      //Reactor线程池的大小不为2的次幂
      return new GenericEventExecutorChooser(executors);
  }
}
//Reactor线程池的大小若为2的次幂,其调用的next()方法
@Override
public EventExecutor next() {
  return executors[idx.getAndIncrement() & executors.length - 1];
}
//Reactor线程池的大小不为2的次幂,其调用的next()方法,采用取模的方式
@Override
public EventExecutor next() {
  return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}

4.selectorProvider:用来初始化Selector,每个Reactor线程池都有一个该实例

//默认使用JDK提供的方法
selectorProvider = SelectorProvider.provider()

5.selectStrategyFactory:这个工作在EventLoop中run方法体内,根据不同的选择执行相应操作

selectStrategyFactory = DefaultSelectStrategyFactory.INSTANCE

6.rejectedExecutionHandler:当线程池中没有可用的线程执行任务时的拒绝策略

//默认的拒绝策略抛出RejectedExecutionException
rejectedExecutionHandler = RejectedExecutionHandlers.reject()

private static final RejectedExecutionHandler REJECT = new RejectedExecutionHandler() {
  @Override
  public void rejected(Runnable task, SingleThreadEventExecutor executor) {
      throw new RejectedExecutionException();
  }
};

7.taskQueueFactory:生成一个任务队列

//返回生成的任务队列
private static Queue<Runnable> newTaskQueue(
      EventLoopTaskQueueFactory queueFactory) {
  if (queueFactory == null) {
      return newTaskQueue0(DEFAULT_MAX_PENDING_TASKS);
  }
  return queueFactory.newTaskQueue(DEFAULT_MAX_PENDING_TASKS);
}

NioEventLoopGroup类中另一个重要的方法:

/**
* NioEventLoopGroup中重载了父类MultithreadEventExecutorGroup的newChild()方法
* 其作用就是初始化EventLoop
*/
@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);
}  

MultiThreadEventLoopGroup是NioEventLoopGroup的父类,构造方法:

//同样该类调用其父类MultithreadEventExecutorGroup的构造函数
protected MultithreadEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
                               Object... args) {
  super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, chooserFactory, args);
}

DEFAULT_EVENT_LOOP_THREADS 默认的线程数16,通过静态块方式初始化如下,SystemPropertyUtil.getInt()该方法先去获取指定系统配置"io.netty.eventLoopThreads"获取默认的线程数;若为空则执行NettyRuntime.availableProcessors(),该方法先去获取配置"io.netty.availableProcessors", 值若为空,则返回处理器核数

static {
    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);
    }

该类MultiThreadEventLoopGroup中另一个重要的方法:默认生成的线程工厂

//重载了父类MultiThreadEventExecutorGroup的方法,初始化默认的线程工厂供父类使用
@Override
protected ThreadFactory newDefaultThreadFactory() {
    //获取当前类,和线程最高优先级为参数
    return new DefaultThreadFactory(getClass(), Thread.MAX_PRIORITY);
} 

最终MultiThreadEventExecutorGroup类,真正干实事的构造函数。

/**
 * Create a new instance.
 *
 * @param nThreads          the number of threads that will be used by this instance.
 * @param executor          the Executor to use, or {@code null} if the default should be used.
 * @param chooserFactory    the {@link EventExecutorChooserFactory} to use.
 * @param args              arguments which will passed to each {@link #newChild(Executor, Object...)} call
 */
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                        EventExecutorChooserFactory chooserFactory, Object... args) {
    if (nThreads <= 0) {
        throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
    }

    if (executor == null) {
        // newDefaultThreadFactory 其实是调用子类中重载的方法
        //初始化线程池
        executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
    }

    // EventExecutor数组,保存EventLoop
    children = new EventExecutor[nThreads];

    //for循环内初始化每个children中的每一个元素也就是 EventLoop
    for (int i = 0; i < nThreads; i ++) {
        //标识实例化是否成功
        boolean success = false;
        try {
            // 初始化EventLoop,调用子类NioEventLoopGroup重载的该方法
            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 {
            //若有一个child event实例化失败
            if (!success) {
                //这个for循环将之前的实例优雅的关闭掉
                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;
                    }
                }
            }
        }
    }

    //传入Reactor线程池也就是EventLoopGroup事件循环组,返回一个chooser实例
    //用来选出一个执行任务的EventLoop
    chooser = chooserFactory.newChooser(children);
    
    //设置一个Listener,监听该线程池的terminate事件
    final FutureListener<Object> terminationListener = new FutureListener<Object>() {
        @Override
        public void operationComplete(Future<Object> future) throws Exception {
            if (terminatedChildren.incrementAndGet() == children.length) {
                terminationFuture.setSuccess(null);
            }
        }
    };
    //给线程池中的每个线程都配置该监听器,只有监听到每个线程都terminate后,这个线程池才terminate
    for (EventExecutor e: children) {
        e.terminationFuture().addListener(terminationListener);
    }
    //将children 设置为只读集合
    Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
    Collections.addAll(childrenSet, children);
    readonlyChildren = Collections.unmodifiableSet(childrenSet);
}

接下来看下newChild()方法: 上面有提到过子类NioEventLoopGroup重载了父类MultithreadEventLoopGroup中的newChild()方法,MultithreadEventLoopGroup其实也是重载了其父类MultithreadEventExecutorGroup的newChild()方法。

这里确实有点绕,而且Netty中这样类似的还有很多

看下源码:

//这个方法就是创建线程的方法,也就是EventLoop,而NioEventLoop是其一个子类(向上转型)
//传递的参数有点多,所以采用了可变参数
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    //判断args的长度,是否要将参数强转为EventLoopTaskQueueFactory 类型,一个任务队列的工厂
    EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
    //调用NioEventLoop的构造函数
    return new NioEventLoop(this, executor, (SelectorProvider) args[0],
        ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}

NioEventLoop的构造函数

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
             EventLoopTaskQueueFactory queueFactory) {
    //调用父类SingleThreadEventLoop的构造函数
    super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
            rejectedExecutionHandler);
    if (selectorProvider == null) {
        throw new NullPointerException("selectorProvider");
    }
    if (strategy == null) {
        throw new NullPointerException("selectStrategy");
    }
    provider = selectorProvider;
    //开启Selector
    final SelectorTuple selectorTuple = openSelector();
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}

看下NioEventLoop类的构造参数: 1.parent:将NioEventLoopGroup (线程池)作为NioEventLoop(线程)的parent 2.executor,selectorProvider,strategy,rejectedExecutionHandler直接从NioEventLoopGroup 传过来 3.queueFactory:队列工厂,在本类中初始化一个任务队列,在传给父类,这边传了两个任务队列,后续使用到时再说

private static Queue<Runnable> newTaskQueue(
        EventLoopTaskQueueFactory queueFactory) {
    if (queueFactory == null) {
        return newTaskQueue0(DEFAULT_MAX_PENDING_TASKS);
    }
    return queueFactory.newTaskQueue(DEFAULT_MAX_PENDING_TASKS);
}

DEFAULT_MAX_PENDING_TASKS:队列默认的大小为Integer.MAX_VALUE,该字段是在父类SingleThreadEventLoop中;
这种默认赋值的方式之前也有提高过。方式比较巧妙。
protected static final int DEFAULT_MAX_PENDING_TASKS = Math.max(16,
        SystemPropertyUtil.getInt("io.netty.eventLoop.maxPendingTasks", Integer.MAX_VALUE));

但最终其实初始化的Queue<Runnable>大小为1024,感兴趣的可以去看下newTaskQueue0()方法。如果理解有误,欢迎大佬指出。

看下NioEventLoop中几个重要的参数:

1.selector:由线程维护,每个线程都有自己的selector,channel会注册在该Selector上,关于Selector的创建,后续再看。

2.ioRatio:该线程IO任务与非IO任务的执行时间比例。这里的非IO任务可以是用户自定义的Task和定时任务Tas。Netty框架是用来通信的,线程主要是处理网络事件也就是IO任务为主,所以该字段也是为了保证IO任务有足够的执行时间

先来看下该类最重要的一个方法是run()方法:

/**
* 之前提到过的EventLoop会绑定一个线程,Selector的轮询操作在其绑定的线程run()方法驱动
* 看了这么久总算看到点面貌了
*/
@Override
protected void run() {
    //Selector的轮询操作...
}

接着往下看NioEventLoop的父类SingleThreadEventLoop:

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");
}

也调用了父类SingleThreadEventExecutor的构造函数:

protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                    boolean addTaskWakesUp, Queue<Runnable> taskQueue,
                                    RejectedExecutionHandler rejectedHandler) {
    super(parent);
    this.addTaskWakesUp = addTaskWakesUp;
    this.maxPendingTasks = DEFAULT_MAX_PENDING_EXECUTOR_TASKS;
    this.executor = ThreadExecutorMap.apply(executor, this);
    //提交给NioEventLoop的任务都会先offer到这个队列,等待被执行。
    //这里需要提到Netty的事件驱动模型,文末会附上一般的事件驱动模型先来了解下。
    this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue");
    rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}

继续看SingleThreadEventExecutor类的构造参数:
1.parent:设置了parent,也就是NioEventLoopGroup实例

2.executor:在MultithreadEventExecutorGroup构造函数中实例化的ThreadPerTaskExecutor对象,在NioEventLoopGroup的构造参数中有提到,该executor就是用来启动绑定到EventLoop上的线程

3.taskQueue:任务队列,是在子类NioEventLoop中初始化的。提交的任务都会先添加到这个队列,等待被执行。

//这个execute方法很重要
@Override
public void execute(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }

    boolean inEventLoop = inEventLoop();
    //将任务添加到taskQueue
    addTask(task);
    ...
}

4.rejectedExecutionHandler :任务队列满后出发执行rejectedExecutionHandler 的策略
看下这三个类:

NioEventLoop
-->SingleThreadEventLoop
-->SingleThreadEventExecutor

SingleThreadEventExecutor,这是一个Executor,按照概念来说,这是一个线程池,而且是单线程的(SingleThread)。也就是说EventLoopGroup线程池中的每一个线程EventLoop,也都可以看做一个线程池。

类似的看下这三个类:
NioEventLoopGroup
-->MultithreadEventLoopGroup
-->MultithreadEventExecutorGroup

可以这么理解:线程池,管理着每一个线程,具体任务由线程去执行。

回到NioEventLoop:

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
             EventLoopTaskQueueFactory queueFactory) {
    super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
            rejectedExecutionHandler);
    if (selectorProvider == null) {
        throw new NullPointerException("selectorProvider");
    }
    if (strategy == null) {
        throw new NullPointerException("selectStrategy");
    }
    provider = selectorProvider;
    //创建selector实例
    final SelectorTuple selectorTuple = openSelector();
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}

该类最重要的一个方法:openSelector()方法;创建了NIO中最重要的一个组件Selector多路复用器。channel会注册到该Selector,并在该类重载的run()方法内执行Selector的轮询操作。这里先不详细说明,有兴趣的大家先自己去看看。

看到这里,也就是完成了一些初始化操作,线程池EventLoopGroup的创建以及池内线程EventLoop的实例化。目前来说这些还是比较容易理解的,也建议大家跟着去看看源码,多去熟悉。 那么线程是如何启动并执行任务的?后面接着学习...

总结

这里主要提两个类:EventLoopGroup(NioEventLoopGroup 子类)、NioEventLoop(NioEventLoop子类)。也就是大家常说的在Netty中的线程池以及线程。EventLoopGroup其实质是包含EventLoop的一个数组;

线程池负责管理线程,有任务提交时,由选择线程执行器的工厂类EventExecutorChooserFactory负责指定某个线程来执行。

看源码学习,除了理解其工作原理,更重要的在于学习其设计思路和编码方式。在这个过程中对于自己也是一种提升吧。

参考

《Netty 权威指南》第二版

认真的 Netty 源码解析

附录

事件驱动模型

通常,设计一个事件处理模型的程序主要有两种设计:

1.轮询方式:线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑。

2.事件驱动方式:发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也称为消息通知方式,其实是设计模式中观察者模式的设计思路。

事件驱动模型示例图:


主要包括四个组件:
事件队列(event queue) : 接收事件的入口,存储待处理的事件;
分发器(event mediator) : 将不同的事件分发到相应的业务逻辑单元;
事件通道(event channel) : 分发器与处理器之间联系的渠道;
事件处理器(event processor) : 实现业务逻辑,处理完成后会发出事件,触发下一步操作。

优点:
可扩展性好,分布式的异步架构,事件处理器之间高度解耦,可以方便扩展事件处理逻辑 高性能,基于队列暂存事件,方便并行处理事件