Netty 权威指南笔记(八):EventLoopGroup 和线程模型
来源:互联网 发布:学工控的该学vb还是vc 编辑:程序博客网 时间:2024/05/08 02:31
- Netty 权威指南笔记八EventLoopGroup 和线程模型
- 线程模型
- 概述
- Reactor 线程模型
- 单线程模型
- 多线程模型
- 主从多线程模型
- Proactor 线程模型
- 源码分析
- NioEventLoopGroup
- NioEventLoop
- ServerBootstrap
- 参考文献
- 线程模型
Netty 权威指南笔记(八):EventLoopGroup 和线程模型
源码版本 4.1。
线程模型
概述
一般情况下,I/O 复用机制需要事件分发器(event dispatcher)。 事件分发器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊: 谁谁谁的快递到了, 快来拿吧!开发人员在开始的时候需要在分发器那里注册感兴趣的事件,并提供相应的处理者(event handler),或者是回调函数;事件分发器在适当的时候,会将请求的事件分发给这些handler或者回调函数。
涉及到事件分发器的两种模式称为:Reactor和Proactor。Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的。
Netty 框架遵循的是 Reactor 线程模型,下面回顾一下经典的 Reactor 线程模型。
Reactor 线程模型
无论是C++还是Java编写的网络框架,大多数都是基于Reactor模式进行设计和开发,Reactor模式基于事件驱动,特别适合处理海量的I/O事件。
单线程模型
Reactor单线程模型,指的是所有的IO操作都在同一个NIO线程上面完成,NIO线程的职责如下:
1. 作为NIO服务端,接收客户端的TCP连接;
2. 作为NIO客户端,向服务端发起TCP连接;
3. 读取通信对端的请求或者应答消息;
4. 向通信对端发送消息请求或者应答消息。
单线程模型的缺点是无法应对高并发、大负载的场景。
多线程模型
Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理IO操作,它的原理图如下:
Reactor多线程模型的特点:
1. 有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP连接请求;
2. 网络IO操作-读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;
3. 1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。
在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是,在极个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。在这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型-主从Reactor多线程模型。
主从多线程模型
主从Reactor线程模型的特点是:服务端用于接收客户端连接的不再是个1个单独的NIO线程,而是一个独立的NIO线程池。
利用主从NIO线程模型,可以解决1个服务端监听线程无法有效处理所有客户端连接的性能不足问题。它的工作流程总结如下:
1. 从主线程池中随机选择一个Reactor线程作为Acceptor线程,用于绑定监听端口,接收客户端连接;
2. Acceptor线程接收客户端连接请求之后创建新的SocketChannel,将其注册到主线程池的其它Reactor线程上,由其负责接入认证、IP黑白名单过滤、握手等操作;
3. 步骤2完成之后,业务层的链路正式建立,将SocketChannel从主线程池的Reactor线程的多路复用器上摘除,重新注册到Sub线程池的线程上,用于处理I/O的读写操作。
Proactor 线程模型
在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。
而在Proactor模式中,事件处理者(或者代由事件分发器发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区、读的数据大小或用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分发器得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称为overlapped技术),事件分发器等IO Complete事件完成。这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。
源码分析
NioEventLoopGroup
NioEventLoopGroup 的类图如下所示:
NioEventLoopGroup 是一个线程池,继承了 MultithreadEventLoopGroup,根据下面的源码可知,默认的线程数目是 CPU 核数 × 2。
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup { private static final int DEFAULT_EVENT_LOOP_THREADS; static { DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); }}
真正负责创建线程池的是 MultithreadEventExecutorGroup 构造方法,创建了 nThreads 个子线程池。
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 ++) { boolean success = false; try { // 创建子线程池 children[i] = newChild(executor, args); success = true; } catch (Exception e) { throw new IllegalStateException("failed to create a child event loop", e); } finally { if (!success) { // 某一个线程池创建失败,则关闭之前创建成功的线程池 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; } } } } } // 线程池选择策略类,根据一定的策略选择子线程池使用 chooser = chooserFactory.newChooser(children); final FutureListener<Object> terminationListener = new FutureListener<Object>() { @Override public void operationComplete(Future<Object> future) throws Exception { if (terminatedChildren.incrementAndGet() == children.length) { terminationFuture.setSuccess(null); } } }; for (EventExecutor e: children) { e.terminationFuture().addListener(terminationListener); } Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length); Collections.addAll(childrenSet, children); readonlyChildren = Collections.unmodifiableSet(childrenSet); }
newChild 方法的实现在 NioEventLoopGroup 中,从源码可知,一个 NioEventLoopGroup 中包含若干 NioEventLoop。
protected EventLoop newChild(Executor executor, Object... args) throws Exception { return new NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]); }
NioEventLoop
NioEventLoop 的类图如下所示:
NioEventLoop 是只有单个线程的线程池,但并不是一个纯粹的线程池,还负责处理系统 Task 和一些定时任务。其成员变量如下所示,主要是多路复用器 Selector、
// 经过优化的 selector private Selector selector; // 未经优化的原生 selector private Selector unwrappedSelector; // 经过优化的 SelectionKeySet private SelectedSelectionKeySet selectedKeys; // 用来创建 Selector、Channel private final SelectorProvider provider;
Selector 的创建是在 openSelector 方法中,根据 DISABLE_KEYSET_OPTIMIZATION 来判断是否对 SelectionKey 进行优化。
private SelectorTuple openSelector() { final Selector unwrappedSelector; try { // 创建 Selector unwrappedSelector = provider.openSelector(); } catch (IOException e) { throw new ChannelException("failed to open a new selector", e); } // 如果不需要优化 SelectorKey,则立即返回,默认就是不需要优化。 if (DISABLE_KEYSET_OPTIMIZATION) { return new SelectorTuple(unwrappedSelector); } // 否则,进行优化 final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet(); 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; } } }); 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); } final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass; Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { try { Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys"); Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys"); Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField); if (cause != null) { return cause; } cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField); if (cause != null) { return cause; } // 优化,用 Netty 实现的 SelectedSelectionKeySet 替换 NIO 原生的 selectedKeysField.set(unwrappedSelector, selectedKeySet); publicSelectedKeysField.set(unwrappedSelector, selectedKeySet); return null; } catch (NoSuchFieldException e) { return e; } catch (IllegalAccessException e) { return e; } } }); if (maybeException instanceof Exception) { selectedKeys = null; Exception e = (Exception) maybeException; logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e); return new SelectorTuple(unwrappedSelector); } selectedKeys = selectedKeySet; logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector); return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet)); }
NioEventLoop 的核心方法是 run 方法:
1. 如果有 Task 要处理,则调用 selectNow; 否则调用 select。
2. 先处理 SelectedKeys。
3. 再处理 Task。
4. 如果状态变成关闭,则取消并关闭所有注册的 Channel,并退出。
@Override protected void run() { for (;;) { boolean oldWakenUp = wakenUp.getAndSet(false); try { if (hasTasks()) { selectNow(); } else { select(oldWakenUp); if (wakenUp.get()) { selector.wakeup(); } } cancelledKeys = 0; needsToSelectAgain = false; final int ioRatio = this.ioRatio; if (ioRatio == 100) { processSelectedKeys(); runAllTasks(); } else { final long ioStartTime = System.nanoTime(); processSelectedKeys(); final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } if (isShuttingDown()) { closeAll(); if (confirmShutdown()) { break; } } } catch (Throwable t) { logger.warn("Unexpected exception in the selector loop.", t); // 为避免连续异常导致 CPU 繁忙,Sleep 一会儿。 try { Thread.sleep(1000); } catch (InterruptedException e) { // Ignore. } } } }
ServerBootstrap
ServerBootstrap 是一个启动辅助类,通常用来组合 EventLoopGroup、SocketChannel、ChannelHandler:
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 2014) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(16)); socketChannel.pipeline().addLast(new TimeServerHandler()); } }); ChannelFuture future = bootstrap.bind(port).sync(); System.out.println("start listening ..."); future.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
ServerBootstrap 的 bind 方法负责绑定端口,并启动服务。
参考文献
- Java NIO 的 Selector wakeup 剖析
- Netty 权威指南笔记(八):EventLoopGroup 和线程模型
- Netty线程模型及EventLoop和EventLoopGroup源码解析
- Netty 权威指南笔记(一):网络 I/O 模型和 Java NIO 入门
- netty5笔记-线程模型2-EventLoopGroup
- netty(八)源码分析之eventLoop和eventLoopGroup
- Netty权威指南(笔记一)
- Netty权威指南(笔记二)
- Netty 权威指南笔记(二):Java NIO 和 Netty 对比
- [netty源码分析]--EventLoopGroup与EventLoop 分析netty的线程模型
- 《HTTP权威指南》阅读笔记(八)
- Netty 权威指南笔记(三):TCP 粘包和拆包
- Netty 权威指南笔记(七):ChannelPipeline 和 ChannelHandler 源码分析
- Netty 权威指南笔记(四):架构剖析
- Netty 权威指南笔记(五):ByteBuf 源码解读
- Netty 权威指南笔记(六):Channel 解读
- Netty:EventLoopGroup
- Netty权威指南学习笔记1
- 《netty权威指南》学习笔记1
- CCF-训练50题-NO.22-A除以B
- 11.15tf-cnn旁边有大佬
- Jmeter安装
- iOS,Android软件开发培训基础内容对比(一)
- C语言操作符总结
- Netty 权威指南笔记(八):EventLoopGroup 和线程模型
- CCF-训练50题-NO.23-锤子剪刀布
- Javascript知识点总结(六)
- 【BZOJ2049】洞穴勘测(Link-Cut Tree)
- c++实战应用-1
- vim的跨文件复制粘贴
- Spring Test 中重置自增字段
- 图片加载原理
- PHPExcel 读取Excel以xlsx格式的文件