前言
在目前许多开源的框架中,避免不了服务与服务之间相互通信,相互查询数据等情况,而往往我们知道的grpc等等,底层也都是由Netty去实现,这里就简单的给大家扫下盲。
Netty简单介绍
什么是经典的三种I/O模型
模式名 | 版本 | 说明 |
---|---|---|
BIO(阻塞I/O) | JDK1.4之前 | 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善 |
NIO(非阻塞I/O) | JDK1.4(2002年,java.nio包) | 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。 |
AIO(异步I/O) | JDK1.7(2011年) | 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理 |
使用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
Netty对三种I/O模型的支持
BIO->OIO | NIO | AIO | ||
---|---|---|---|---|
COMMON | Linux | MacOs/BSD | ||
ThreadPerChannelEventLoopGroup | NioEventLoopGroup | EpollEventLoopGroup | KQueueEventLoopGroup | AioEventLoopGroup |
ThreadPerChannelEventLoop | NioEventLoop | EpollEventLoop | KQueueEventLoop | AioEventLoop |
OioServerSocketChannel | NioServerSocketChannel | EpollServerSocketChannel | KQueueServerSocketChannel | AioServerSocketChannel |
OioSocketChannel | NioSocketChannel | EpollSocketChannel | KQueueSocketChannel | AioSocketChannel |
为什么Netty仅支持NIO?
- 为什么不建议阻塞I/O(BIO/OIO)?
- 连接数高的情况下:阻塞->耗资源、效率低
- 为什么删除已经做好的AIO支持?
- Window实现成熟,但是很少用作服务器
- Linux常用来做服务器,但是AIO实现不够成熟
- Linux下AIO相比较NIO的性能提升不明显
为什么Netty有多种NIO实现?
实现的更好:复制代码
- Netty暴露了更多的可控参数
- JDK的NIO默认实现是水平触发
- Netty是边缘触发和水平触发可切换
- Netty实现的垃圾回收更少、性能更好
NIO一定优于BIO么?
- BIO代码简单
- 特定场景:连接数少,并发度低。
源码解读Netty怎么切换I/O模式?
工厂+反射
Netty如何支持三种Reactor
什么是Reactor
反应器设计模式(Reactor pattern)是一种为处理并发服务请求,并将请求提交到一个或者多个服务处理程序的事件设计模式。当客户端请求抵达后,服务处理程序使用多路分配策略,由一个非阻塞的线程来接收所有的请求,然后派发这些请求至相关的工作线程进行处理。
Reactor有几种模式
- Reactor单线程
- Reactor多线程
- Reactor主从多线程
BIO | NIO | AIO |
---|---|---|
Thread=Per-Connection | Reactor | Proactor |
注册感兴趣的事件 -->扫描是否有感兴趣的事件发生 -->时间发生后做出相应的处理
client/Server | SocketChannel/ServerSocketChannel | OP_ACCEPT | OP_CONNECT | OP_WRITE | OP_READE |
---|---|---|---|---|---|
client | SocketChannel | Y | Y | Y | |
server | SewrverSocketChannel | Y | |||
server | SocketChannel | Y | Y |
如何在Netty中使用Reactor模式
模式名称 | 使用方法 |
---|---|
Reactor单线程模式 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup) |
非主从Reactor多线程模式 | EventLoopGroup bossGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup) |
主从Reactor多线程模式 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup,workerGroup) |
Netty对Reactor模式的支持
通过上面的三种简单的介绍,我们知道Netty针对于Reactor模式是如何支持的,我们除了知道是如何支持的之外,还是需要了解其中是如何实现的,这里采用主从Reactor多线程模式简单的说明下。
主要代码部分:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {super.group(parentGroup);if (this.childGroup != null) {throw new IllegalStateException("childGroup set already"); }this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");return this; }复制代码
我们可以从代码启动开始寻找:
ChannelFuture regFuture = config().group().register(channel);if (regFuture.cause() != null) {if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } }复制代码
上面即是serverChannel的注册。
public void channelRead(ChannelHandlerContext ctx, Object msg) {final Channel child = (Channel) msg; child.pipeline().addLast(childHandler); setChannelOptions(child, childOptions, logger); setAttributes(child, childAttrs);try { childGroup.register(child).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (!future.isSuccess()) { forceClose(child, future.cause()); } } }); } catch (Throwable t) { forceClose(child, t); } }复制代码
上面即是Channel的注册。
通过上面的两步操作即可完成对应的Reactor模式支持。
总结
上文中简单的介绍了我们底层框架中Netty的一些介绍,当然强大的框架远远不止这些东西去支持,还有需要进行处理的(例如粘包、Metric等)。