[03] Netty 架构设计
1. 功能特性
- 传输服务:支持 BIO 和 NIO;
- 容器集成:支持 OSGI、JBossMC、Spring、Guice 容器;
- 协议支持:HTTP、Protobuf、二进制、文本、WebSocket 等一系列常见协议都支持,还支持通过实行编码解码逻辑来实现自定义协议;
- Core 核心:可扩展事件模型、通用通信 API、支持零拷贝的 ByteBuf 缓冲对象。
2. Netty 模型
2.1 模型说明
a. 简略版
Netty 线程模型采用“服务端监听线程”和“IO 线程”分离的方式,与多线程 Reactor 模型类似。
抽象出 NioEventLoop 来表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 Selector,用于监听绑定在其上的 Socket 连接。
(1)串行化设计避免线程竞争
Netty 采用串行化设计理念,从消息的读取 → 解码 → 处理 → 编码 → 发送,始终由 IO 线程 NioEventLoop 负责。整个流程不会进行线程上下文切换,数据无并发修改风险。
一个 NioEventLoop 聚合一个多路复用器 selector,因此可以处理多个客户端连接。
Netty 只负责提供和管理“IO 线程”,其他的业务线程模型由用户自己集成。
时间可控的简单业务建议直接在“IO 线程”上处理,复杂和时间不可控的业务建议投递到后端业务线程池中处理。
(2)定时任务与时间轮
NioEventLoop 中的 Thread 按照时间轮中的步骤不断循环执行:
- 在时间片 Tirck 内执行
selector.select()
轮询监听 IO 事件; - 处理监听到的就绪 IO 事件;
- 执行任务队列 taskQueue/delayTaskQueue 中的非 IO 任务。
b. 详细版
Netty 抽象出两组线程池:BossGroup 专门负责客户端的连接,WorkerGroup 专门负责网络的读写。
- BossGroup 和 WorkerGroup 的类型都是 NioEventLoopGroup;
- NioEventLoopGroup 相当于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop;
- NioEventLoop 表示一个不断循环的执行处理任务的线程,每个 NioEventLoop 包含 1 个 Selector,用于监听绑定在其上的 SocketChannel 事件。
每个 Boss NioEventLoop 循环执行的任务包含 3 步:
- 轮询 accept 事件;
- 处理 accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将其注册到某个 Worker NioEventLoop 的 Selector 上;
- 处理任务队列中的任务(包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其它线程提交到该 eventloop 的任务),runAllTasks。
每个 Worker NioEventLoop 循环执行的任务包含 3 步:
- 轮询 read、write 事件;
- 处理每一个连接的 I/O 事件,即 read、write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理;
- 处理任务队列中的任务,runAllTasks。
2.2 示例代码
a. Server 端
(1)NettyServer
package org.example.netty.demo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author 6x7
* @Description QuickStart
* @createTime 2022年02月22日
*/
public class NettyServer {
public static void main(String[] args) {
// bossGroup 表示监听端口,接收新连接的线程组
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
// workerGroup 表示处理每一个连接的数据读写的线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
// 创建服务器端的启动对象,配置启动参数
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
// ServerBootstrap 将引导服务端的启动工作
ChannelFuture channelFuture = serverBootstrap
// 设置两个线程组
.group(bossGroup, workerGroup)
// 使用 NioSocketChannel 作为服务器的通道实现
.channel(NioServerSocketChannel.class)
// 设置线程队列等待连接的个数
.option(ChannelOption.SO_BACKLOG, 128)
// 设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
// childHandler 用于指定处理新连接数据的读写处理逻辑
// 给引导类创建一个通道初始化器对象,主要是定义后续每个连接的数据读写
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 给 workerGroup 的 EventLoop 对应的 Pipeline 设置处理器
ch.pipeline().addLast(new NettyServerHandler());
}
})
// 绑定 6677 端口(异步方法,调用之后是立即返回的,返回值是一个 ChannelFuture)
.bind(6677)
// 同步处理
.sync();
// 对 ‘关闭通道’ 进行监听
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 断开连接,关闭线程
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
(2)服务端启动的其他方法
handler()
方法可以和前面分析的childHandler()
方法对应起来:childHandler()
方法用于指定处理新连接数据的读写处理逻辑;handler()
方法用于指定在服务端启动过程中的一些逻辑,通常情况下用不到这个方法。serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() { protected void initChannel(NioServerSocketChannel ch) { System.out.println("服务端启动中..."); } })
attr()
方法可以给服务端 Channel,也就是 NioServerSocketChannel 指定一些自定义属性,然后通过channel.attr()
取出这个属性。比如上面的代码可以指定服务端 Channel 的 serverName 属性,属性值为 nettyServer,其实就是给 NioServerSocketChannel 维护一个 Map 而已,通常情况下也用不上这个方法。serverBootstrap.attr(AttributeKey.newInstance("serverName"), "nettyServer")
- 除了可以给服务端 Channel 即 NioServerSocketChannel 指定一些自定义属性,我们还可以给每一个连接都指定自定义属性,后续可以通过
channel.attr()
方法取出该属性。serverBootstrap.childAttr(AttributeKey.newInstance("clientKey"), "clientValue")
option()
方法可以给服务端 Channel 设置一些 TCP 参数,最常见的就是 so_backlog,这个设置表示系统用于临时存放已完成三次握手的请求队列的最大长度。因为服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户端来的时候,服务端将不能处理的客户端连接请求放置在队列中等待处理。故如果连接建立频繁,服务器处理创建新连接较慢,则可以适当调大这个参数。serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)
childOption()
方法可以给每个连接都设置一些 TCP 参数。serverBootstrap // 是否开启 TCP 底层心跳机制,true 表示开启 .childOption(ChannelOption.SO_KEEPALIVE, true) // 是否开启 Nagle 算法,true 表示关闭,false 表示开启 .childOption(ChannelOption.TCP_NODELAY, true)
(3)NettyServerHandler
package org.example.netty.demo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
/**
* @author 6x7
* @Description 自定义一个 Handler 继承规定好的某个 HandlerAdapter
* @createTime 2022年02月22日
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 这个方法在接收到客户端发来的数据之后被回调
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 当超过 nThreads 后将重新从 1 开始
// ===> Thread: nioEventLoopGroup-3-1
// ===> Thread: nioEventLoopGroup-3-2
// ===> Thread: nioEventLoopGroup-3-3
System.out.println("|===> Thread: " + Thread.currentThread().getName());
System.out.println("|===> ChannelHandlerContext: " + ctx);
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline();
System.out.println("|===> channel: " + channel);
// 本质是一个双向链表
System.out.println("|===> pipeline: " + pipeline);
// msg -> io.netty.buffer.ByteBuf
ByteBuf buf = (ByteBuf) msg;
System.out.println("clientMsg: " + buf.toString(CharsetUtil.UTF_8));
System.out.println("clientAddr: " + ctx.channel().remoteAddress());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 将数据写入缓冲(先对发送的数据进行编码)并刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello, Client! (。・∀・)ノ゙", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 处理异常,一般是要关闭通道
ctx.channel().close();
}
}
b. Client 端
(1)NettyClient
package org.example.netty.demo;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* @author 6x7
* @Description QuickStart
* @createTime 2022年02月22日
*/
public class NettyClient {
public static void main(String[] args) {
// 客户端需要一个事件循环组
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
// 创建客户端启动对象
Bootstrap bootstrap = new Bootstrap();
try {
// 设置相关参数
bootstrap
// 设置线程组
.group(eventExecutors)
// 设置客户端通道的实现类(反射)
.channel(NioSocketChannel.class)
// 设置连接超时时间,超过这个时间仍未连接到服务器则表示连接失败
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
// 指定客户端相关的数据读写逻辑
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 给客户端添加一个逻辑处理器,其作用是负责向服务端写数据
ch.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("Client ok!");
// 启动客户端去连接服务器端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6677).sync();
// 给 ‘关闭通道’ 进行监听
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 断开连接,关闭线程
eventExecutors.shutdownGracefully();
}
}
}
(2)NettyClientHandler
package org.example.netty.demo;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* @author 6x7
* @Description TODO
* @createTime 2022年02月22日
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当通道就绪就会触发该方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("ChannelHandlerContext:" + ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello, Server! ヾ(•ω•`)o", CharsetUtil.UTF_8));
}
/**
* 当通道有读取事件时,会触发
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
System.out.println("Server response: " + byteBuf.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
(3)简单说明
- 客户端和服务端的逻辑处理均在启动的时候,通过为逻辑处理器链 Pipeline 添加逻辑处理器,来编写数据的读写逻辑;
- 然后在客户端连接成功之后,会回调到逻辑处理器的
channelActive()
方法。不管服务端还是客户端,收到数据之后都会调用channelRead()
; ch.pipeline()
返回的是和这条记录相关的逻辑处理链,采用了责任链模式;- Netty 里的数据是以 ByteBuf 为单位的,所有需要写出的数据都必须放到一个 ByteBuf 中。数据的写出如此,读取亦是如此;
- Handler 中重写的
channelRead(ChannelHandlerContext ctx, Object msg)
方法的入参 msg 指的就是 Netty 里数据读写的载体; - 写数据调用
writeAndFlush()
方法,客户端与服务端交互的二进制数据载体为 ByteBuf,ByteBuf 通过连接的内存管理器创建,字节数据填充到 ByteBuf 之后才能写到对端。
c. 控制台打印
Server 端
Connected to the target VM, address: '127.0.0.1:54977', transport: 'socket'
|===> Thread: nioEventLoopGroup-3-1
|===> ChannelHandlerContext: ChannelHandlerContext(NettyServerHandler#0, [id: 0x35d167b5, L:/127.0.0.1:6677 - R:/127.0.0.1:55057])
|===> channel: [id: 0x35d167b5, L:/127.0.0.1:6677 - R:/127.0.0.1:55057]
|===> pipeline: DefaultChannelPipeline{(NettyServerHandler#0 = org.example.netty.demo.NettyServerHandler)}
clientMsg: Hello, Server! ヾ(•ω•`)o
clientAddr: /127.0.0.1:55057
Netty 如何使用 Reactor 模式?
服务器端,一般有设置两个线程组,监听连接的 Parent Channel 工作在一个独立的线程组,这里名称为 Boss 线程组(有点像负责招人的包工头)。连接成功后,负责客户端连接读写的 Child Channel 工作在另一个线程组,这里名称为 Worker 线程组,专门负责搬数据(有点儿像搬砖)。
3. 任务队列
- 用户程序自定义的普通任务
- 用户自定义定时任务
- 非当前 Reactor 线程调用 Channel 的各种方法
例如在推送系统的业务线程里面,根据用户的标识,找到对应的 Channel 引用,然后调用 write 类方法向该用户推送消息,就会进入到这种场景,最终的 write 会提交到任务队列中被异步消费。
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 若有一个非常耗时长的业务 -> 异步执行 -> 提交该 channel 对应的 NioEventLoop 的 taskQueue 中
ctx.channel().eventLoop().execute(() -> {
try {
Thread.sleep(6 * 1000);
ctx.writeAndFlush(Unpooled.copiedBuffer("["
+ Thread.currentThread().getName() + "-6s] wakeUp!", CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
ctx.channel().eventLoop().execute(() -> {
try {
Thread.sleep(7 * 1000);
// 13s 后才会发送到客户端
ctx.writeAndFlush(Unpooled.copiedBuffer("["
+ Thread.currentThread().getName() + "-7s] wakeUp!", CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
ctx.channel().eventLoop().schedule(() -> {
ctx.writeAndFlush(Unpooled.copiedBuffer("["
+ Thread.currentThread().getName() + "-2s] wakeUp!", CharsetUtil.UTF_8));
}, 2, TimeUnit.SECONDS);
System.out.println("channelRead over...");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 将数据写入缓冲(先对发送的数据进行编码)并刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello, Client! (。・∀・)ノ゙", CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 处理异常,一般是要关闭通道
ctx.channel().close();
}
}
启动 NettyServer 后,再启动一个 NettyClient 后,输出如上。可以看到虽然任务被加到任务队列中,但是运行时并不是异步执行的,任务运行时仍然是在原来的 nioEventLoopGroup-3-1 线程中运行的,且多个任务之间仍旧是串行执行的。
方案再说明:
- Netty 抽象出两组线程池,bossGroup 专门负责接收客户端连接,workerGroup 专门负责网络读写操作,而 NioEventLoopGroup 下又包含多个 NioEventLoop;
- NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个 Selector,用于监听绑定在其上的 Socket 网络通道;
- NioEventLoop 内部采用串行化设计,从消息的读取 → 解码 → 处理 → 编码 → 发送,始终由 IO 线程 NioEventLoop 负责;
- 每个 NioEventLoop 中包含一个 Selector、一个 taskQueue;
- 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel;
- 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上;
- 每个 NioChannel 都绑定一个自己的 ChannelPipeline。
4. Netty 异步编程模型
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
Netty 中的 I/O 操作是异步的,包括 bind、write、connect 等操作会简单的返回一个 ChannelFuture,调用者并不能立刻获得结果,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
Netty 的异步模型是建立在 Future 和 callback 之上的。Future 的核心思想是:假设一个方法 fun,计算过程可能非常耗时,等待 fun 返回显然不合适,那么可以在调用 fun 的时候,立马返回一个 Future,后续可以通过 Future 去监控 fun 的处理过程(即 Future-Listener 机制)。
在使用 Netty 进行编程时,拦截操作和转换出入站数据只需要您提供 callback 或利用 Future 即可。这使得链式操作简单、高效,并有利于编写可重用的、通用的代码。Netty 框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来、解脱出来。
4.1 ChannelFuture
Future 表示异步的执行结果,可以通过它提供的方法来检测执行是否完成。ChannelFuture 是一个接口,我们可以添加监听器,当监听的事件发生时,就会通知到监听器。
当 Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作。
接口结构如下:
public interface Future<V> extends java.util.concurrent.Future<V> { ... }
public interface ChannelFuture extends Future<Void> { ... }
- io.netty.util.concurrent.Future 表示异步的执行结果,可以通过它提供的方法来检测执行是否完成。
- io.netty.channel.ChannelFuture 是一个接口,我们可以添加监听器,当监听的事件发生时,就会通知到监听器。
常见操作如下:
- 通过 isDone 方法来判断当前操作是否完成;
- 通过 isSuccess 方法来判断已完成的当前操作是否成功;
- 通过 getCause 方法来获取已完成的当前操作失败的原因;
- 通过 isCancelled 方法来判断已完成的当前操作是否被取消;
- 通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则通知指定的监听器。
例如下面的的代码中绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑:
serverBootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
} else {
System.err.println("端口[" + port + "]绑定失败!");
}
});
相比传统阻塞 I/O,执行 I/O 操作后线程会被阻塞住,直到操作完成;异步处理的好处是不会造成线程阻塞,线程在 I/O 操作期间可以执行别的程序,在高并发情形下会更稳定和更高的吞吐量。
4.2 ChannelPromise
(1)Netty Promise
Netty 的 Future,只是增加了监听器。整个异步的状态,是不能进行设置和修改的,于是 Netty 的 Promise 接口扩展了 Netty 的 Future 接口,可以设置异步执行的结果。在 IO 操作过程,如果顺利完成、或者发生异常,都可以设置 Promise 的结果,并且通知 Promise 的 Listener 们。
(2)Netty ChannelPromise
ChannelPromise 接口,则继承扩展了 Promise 和 ChannelFuture。所以,ChannelPromise 既绑定了 Channel,又具备了设置监听回调的功能,还可以设置 IO 操作的结果,是 Netty 实际编程使用的最多的接口。
5. 模拟 HTTP 服务
TestServer
public class TestServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TestServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(6677).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
TestHttpServerHandler
/**
* 1. abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter
* 2. HttpObject 表示客户端和服务器端相互通讯的数据被封装成 HttpObject
*/
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
String rejectPath = "/favicon.ico";
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// 判断 msg 是不是 HttpRequest 请求
if (msg instanceof HttpRequest) {
System.out.println("msgClass: " + msg.getClass());
System.out.println("clientAddr: " + ctx.channel().remoteAddress());
// 获取到请求 uri
HttpRequest httpRequest = (HttpRequest) msg;
URI uri = new URI(httpRequest.uri());
if (rejectPath.equals(uri.getPath())) {
System.out.println("请求 favicon.ico 则不做响应");
return;
}
// 构造 HTTP 响应,即 HttpResponse
ByteBuf content = Unpooled.copiedBuffer("Hello, I'm Server!", CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
response.headers()
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain")
.set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
// 将构建好的 response 返回
ctx.writeAndFlush(response);
}
}
}
TestServerInitializer
public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 向管道加入处理器
ChannelPipeline pipeline = ch.pipeline();
// ----- 加入 Netty 提供的基于 HTTP 的编解码器(codec = code + decode)
pipeline.addLast("MyHttpServerCodec", new HttpServerCodec());
// ----- 加入自定义 Handler
pipeline.addLast("MyTestHttpServerHandler", new TestHttpServerHandler());
}
}
Debug HttpServerCodec()
:
6. IO模型&线程模型
[From GPT] IO 模型和线程模型是密切相关的。让我们一一来看。
Java BIO 模型:
- IO 模型定义了程序与外部设备之间数据交互的方式。Java 的传统 IO 模型,也称为 BIO(Blocking IO),采用同步阻塞方式进行 IO 操作。
- 在 BIO 模型中,当一个线程执行 IO 操作时,它会被阻塞,直到数据准备好或者操作完成。这意味着一个线程只能处理一个 IO 操作,如果有多个请求同时到达,其他线程必须等待。
- BIO 模型通常使用多线程来处理并发请求,每个请求分配一个线程,但这种方式在高并发场景下会导致线程资源消耗过大,性能较低。
Java NIO 模型:
- NIO 模型(Non-blocking IO)引入了选择器(Selector)和通道(Channel)的概念,提供非阻塞的 IO 操作。
- 在 NIO 模型中,一个线程可以处理多个 IO 操作,通过选择器监听多个通道的状态,当某个通道就绪时,线程可以处理该通道的 IO 操作,而不需要阻塞等待。
- NIO 模型使用了缓冲区(Buffer)来存储数据,并且可以使用通道之间的数据传输(Transfer)来提高效率。
- NIO 模型适合处理大量的连接和低延迟的场景,但编程模型相对复杂。
Netty Reactor 模型(既是一种 IO 模型,也是一种线程模型):
- 作为 IO 模型,Netty 的 Reactor 模型基于 Java NIO,使用了 Selector 来监听多个通道的事件。当某个通道就绪时,会触发相应的事件,然后通过回调机制进行处理,实现了非阻塞的 IO 操作。
- 作为线程模型,Netty 的 Reactor 模型采用了基于事件驱动的异步模型,通过回调机制来处理 IO 操作的结果,避免了线程阻塞,提高了并发处理能力和性能。其中包含以下组件:
- 主事件循环(Event Loop):负责监听事件和分发事件,是整个 Reactor 模型的核心。
- 多个工作线程池(Worker Thread Pool):负责具体的 IO 操作和业务处理。当事件被主事件循环分发到工作线程池时,工作线程池中的线程会异步处理事件,从而实现并发处理。
Java 的 IO 模型和线程模型密切联系。BIO 模型使用多线程来处理并发请求,而 NIO 模型利用选择器和缓冲区实现非阻塞 IO 操作,可以在单个线程中处理多个 IO 操作。Netty 作为一个高性能网络编程框架,基于 NIO 模型,提供了简化的 API 和事件驱动的异步模型,使网络编程更加高效和易用。