简要
在上篇中主要简洁介绍了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 事件循环: 事件应该是网络事件,循环则是不断轮询来处理事件;
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 权威指南》第二版
附录
事件驱动模型
通常,设计一个事件处理模型的程序主要有两种设计:
1.轮询方式:线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑。
2.事件驱动方式:发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也称为消息通知方式,其实是设计模式中观察者模式的设计思路。
事件驱动模型示例图:
主要包括四个组件:
事件队列(event queue) : 接收事件的入口,存储待处理的事件;
分发器(event mediator) : 将不同的事件分发到相应的业务逻辑单元;
事件通道(event channel) : 分发器与处理器之间联系的渠道;
事件处理器(event processor) : 实现业务逻辑,处理完成后会发出事件,触发下一步操作。
优点:
可扩展性好,分布式的异步架构,事件处理器之间高度解耦,可以方便扩展事件处理逻辑
高性能,基于队列暂存事件,方便并行处理事件