引言
我们一直都说Netty
是高性能服务器,那么它到底为什么是高性能应用呢?线程模型直接影响着网络应用的性能状况,本文将从Netty
的多线程模型出发揭开其高性能特性的神秘面纱。
多线程模型
(1)传统IO模型的问题
·如果我们自己是Netty
网络应用的设计者,想要设计出高性能的网络应用,首先要面对的问题就是如何解决网络编程的性能瓶颈。那么网络应用的性能瓶颈是什么呢?我们都知道传统的网络应用使用的是BIO
模型,也就是阻塞式IO
。网络程序处理当中的read()
以及write()
操作都会阻塞当前线程的。所以在传统的IO
模型当中每一个socket
连接都会有单独的线程来进行处理。
但是在当前的互联网时代,客户端的连接可能是百万、千万甚至上亿级别的,服务器不可能创建这么多的线程来处理客户端请求。这就是传统IO
网络连接的性能瓶颈所在。
(2)模型优化
如上一节内容所述,传统的IO
模型的性能瓶颈在于服务端需要为每一个socket
连接分配线程来满足业务处理的需要。那么有没有一种方法可以不需要建立那么多连接也可以处理客户端的请求呢?如果要用一个线程处理多个请求,那么BIO
是无法实现的。所以我们可采用java
中的NIO
来完成此部分的优化操作。Netty
的做法是采用Reactor
模式,所谓Reactor
模式就是是一个使用了同步非阻塞的I/O多路复用机制的模式。这里不再展开说明。
在Netty
的世界中,EventLoopGroup
是一个非常重要的核心概念。所谓EventLoopGroup
就是就是EventLoop
的集合,即为事件循环。其中NioEventLoop
负责轮询多路复用器,获取已经处于就绪状态的通道,执行网络的连接、客户端请求接入、读和写相关操作。bossGroup
就用来处理连接请求的,而 workerGroup
是用来处理读写请求的。bossGroup
处理完连接请求后,会将这个连接提交给 workerGroup
来处理, workerGroup
里面有多个 EventLoop
, workerGroup
就是用来处理实际读写操作的。那新的连接会交给哪个 EventLoop
来处理呢?这就需要一个负载均衡算法,Netty
中使用的就是轮询算法。Netty
支持多种Reactor
模式,如单线程模型、多线程模型以及主从多线程模型,用户可以根据实际场景进行启动参数中进行设置来切换对应的模式。
(3)代码分析
a、单线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup)
.channel(NioServerSocketChannel.class)
...
}
...
如上代码可知,创建了一个参数为1的bossGroup
,该EventLoopGroup 主要用于接收客户端的连接,即为单线程模型。
b、多线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
...
}
...
与单线程模式不同,多线程模式创建了workerGroup
来处理IO
操作,数量默认为CPU数量的两倍。
c、主从多线程模型
在该模型中,用于接收客户端连接的线程不再是单一的NIO
线程了,而是一个线程池。
//创建boss线程组,处理客户端连接
EventLoopGroup bossGroup = new NioEventLoopGroup(2);
// 创建worker线程组用于SocketChannel 的数据读写
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建 ServerBootstrap 对象
ServerBootstrap b = new ServerBootstrap();
// 设置EventLoopGroup
b.group(bossGroup, workerGroup)
// 设置要被实例化的为 NioServerSocketChannel 类
.channel(NioServerSocketChannel.class)
...
}
...
总结
本文主要介绍了Netty
的多线程模型,它采用的是Reactor
模型。处理连接请求与处理IO
操作的线程隔离。基于事件轮询监听,不断获取处于就绪状态的通道。其中Boss
线程池的线程负责处理连接请求,接收到accept事件之后,将对应的socket
进行封装生成NioSocketChannel
对象,并将其提交到workBoss
线程池中,处理IO的read
以及write
事件。