上篇讲了reactor的线程模型,我们说netty 是属于主从线程模型,下面我们一起来看看netty的线程模型。
本片文章分为两部分:
第一部分是分析 netty的线程模型,这部分我只会贴关键源码,我觉得贴太多源代码会很难理解,所以我一般会用贴关键源代码和伪代码表示。
第二部分是分析几道关于这部分的面试题。
本次分析用的netty版本是 4.1.65.Final
第一部分:
我们先从一个服务端demo开始:
public static void main(String[] args) throws Exception {
EventLoopGroup boosGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(3);
try{
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boosGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
ChannelFuture cf = bootstrap.bind(6668).sync();
cf.channel().closeFuture().sync();
}finally {
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
总览:
大家先看看这幅图:
先总结一下这幅图:
1 netty 是用了netty 主从模式, boosGroup为主线程池,用于接收ACCEPT事件,workerGroup用于处理耗时的IO事件和普通任务。
2 NioEventLoopGroup 和 EventLoop 是池和线程的关系,一个NioEventLoopGroup 里面有一个或多个EventLoop
下面我们分三部分来给分别说一下:
第一,NioEventLoopGroup 和 EventLoop 具体是做什么的
第二,boosGroup是如何被启动的
第三,boosGroup 是 如何驱动 workerGroup
这样大家就会对netty大的框架有一个认识,EventLoop 作为 reactor 线程是做什么的,
boosGroup 的 reactor 是怎样被启动的, workerGroup 的reactor 线程 是怎样被启动的。
第一 我们一起来看看 NioEventLoopGroup 的构造函数 做了什么:
第一步 new NioEventLoopGroup 时,调用父类(SingleThreadEventExecutor)的构造函数,初始化话了若干个 EventLoop
问题:多少个EventLoop
答:根据传进去的参数决定,如果不传默认 cup核心数*2
核心代码如下:
第一调用 NioEventLoopGroup 的父类构造函数MultithreadEventExecutorGroup,
此处 做了两件事:
1 初始化的线程池
2 初始化了EventLoop数组
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
children[i] = newChild(executor, args);
}
}
第二 初始化 EventLoop的时候,创建了 Selector,供后面监听事件
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");
final SelectorTuple selectorTuple = openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
第三调用 EventLoop 的父类构造函数,初始化工作入口启动类
protected SingleThreadEventExecutor() {
// 这里 eventLoop工作的启动入口
this.executor = ThreadExecutorMap.apply(executor, this);
}
public static Executor apply(final Executor executor, final EventExecutor eventExecutor) {
return new Executor() {
@Override
public void execute(final Runnable command) {
executor.execute(apply(command, eventExecutor));
}
};
}
我们用伪代码来看看 EventLoop是如何工作的:
EventLoop{
Exector exector;
Selector selecor;
for (;;) {
// 判断队列是否有任务,有则执行 selectNow() , 没有则返回 -1
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
// 返回 -1 则说明队列没有任务
if(strategy==-1){
strategy = select();
}
if(strategy>0){
processSelectedKeys();
ranTasks = runAllTasks();
}
}
}
第一:先判断 是否有本地任务需要执行
第二:如果有则调用selectNow 立即返回就绪事件,目的是防止延误非IO任务的执行
第三:如果没有本地任务,则调用select 监听IO事件
总结一下就是:
第一: NioEventLoopGroup主要完成对 EventLoop的初始化,但是还没有正式启动EventLoop。只是定义了一下,只有正在有任务的时候才会启动。
第二:NioEventLoopGroup 和 EventLoop 是池和线程的关系
第三:因为EventLoop是用于监听并处理事件的,所以EventLoop肯定持有一个Selector
第二是 我们来看看 boosGroup 的线程是如何被启动
我们先说结论,boosGroup是在 bootstrap.bind(6668) 被启动,bootstrap.bind 做主要做了初始化ServerSocket 并 注册到 bossGroup,注册的时候 启动了boosGroup 线程。
我们来直接看关键代码
我们先来大概的代码调用链路:
bootstrap.bind(6668) ----> AbstractBootstrap.dobind()---->AbstractBootstrap. initAndRegister()----->config().group().register(channel)---->next().register(channel)----->promise.channel().unsafe().register(this, promise)---->AbstractChannel.register------> eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
现在我们来看看,eventLoop.execute 做了什么事情:
1 将任务加入队列,后面线程会消费
private void execute(Runnable task, boolean immediate) {
addTask(task);
startThread();
}
private void startThread() {
doStartThread();
}
private void doStartThread() {
executor.execute(new Runnable() {
@Override
public void run() {
SingleThreadEventExecutor.this.run();
}
});
}
2 启动工作线程 监听IO事件
@Override
protected void run() {
for (;;) {
processSelectedKeys();
ranTasks = runAllTasks();
}
}
总结一下就是,bootstrap.bind 的时候,注册 fd 时启动bootstrap 的 eventLoop
第三是 boosGroup 是 如何驱动 workerGroup
前面因为已经将ServerSocket 注册到 boosGroup线程,所以boosGroup线程 是能监听到 accept事件的
boosGroup 驱动 workerGroup,是boosGroup线程接收到 accept 事件后,注册到 workerGroup 线程后启动的。
我们现在来看看关键源代码:
NioEventLoop.run---->NioEventLoop.processSelectedKey() --->NioEventLoop.unsafe.read()---->pipeline.fireChannelRead(readBuf.get(i))---->ServerBootstrap.channelRead()---->childGroup.register(child) ----> 后面就是走第二 的注册流程启动的
ServerBootstrap.channelRead()
public void channelRead(ChannelHandlerContext ctx, Object msg) {
childGroup.register(child)
}
这里先说明几个问题:
第一:NioEventLoop.run 的NioEventLoop 是 boosGroup 的EventLoop,接收事件后注册到workerGroup.
第二:接收到accept 事件后,传递fireChanelRead 到pipeline。pipeline 大家pipeline可以简单理解成保存channelHandler的双向链表,用于处理具体的任务的。
第三:因为 ServerBootstrap 也是一个handler,也是注册到 pipeline里面的,所以会调用到
第二部分:
问题一: netty 中 channel 和 jdk nio 包下的channel 是什么关系呢
答:netty 是基于 java nio 上实现,所以核心功能还是会依赖于 java jdk的核心组件,Netty 的 channel 是 封装了 java jdk 上的channel。Channel 接口的定义尽量大而全,为socketChannel 和 ServerSocketChannel 提供同一的视图,由不同子类实现不同的功能,公共功能在抽象父类中实现,最大程度地实现功能和接口的重用。
问题二:channel 注册到 nioEventLoop 的过程,详细的说下
答:nioEventLoop 可以说是 netty 的 reactor 线程,它除了处理I/O事件,还能处理普通的task。既然能处理 I/O事件,所以nioEventLoop 内部肯定封装了 java jdk 的 selector 对象,用于监听事件。所以channel 注册到 nioEventLoop 就是做了一件事,将netty channel对象内部维护的 jdk nio channel 注册到这个 NioEventLoop 的 selector 对象内