Netty:Reactor 线程模型、EventLoop 与 Channel
一、Reactor 是什么
Reactor 是一种事件驱动网络处理模式:使用 IO 多路复用等待就绪事件,然后将事件分发给相应处理逻辑。它的核心不是“所有代码都异步”,而是 IO 等待与事件处理分离,少量线程可管理很多连接。
三种典型模型
Netty 服务端常见配置 bossGroup + workerGroup,可以按主从 Reactor 理解。
二、BossGroup 与 WorkerGroup
连接流程:
客户端 connect
↓
NioServerSocketChannel(服务端监听)
↓
BossGroup 中的 EventLoop 处理 OP_ACCEPT
↓
接受新连接,得到 NioSocketChannel
↓
注册到 WorkerGroup 选出的 EventLoop
↓
Worker EventLoop 处理这个 Channel 的读写事件
↓
ChannelPipeline → 相应 ChannelHandlerBossGroup:通常负责新连接的接受。
WorkerGroup:通常负责已建立连接的 IO 读写。
EventLoopGroup是 EventLoop 的集合及管理接口。new NioEventLoopGroup(4):创建具有 4 个 EventLoop 的组,不是最多只能连接 4 个客户端。
三、EventLoop 的核心原理
在常见的 NioEventLoop 实现里,一个 EventLoop 对应一个持续执行事件循环的线程。它负责:
监听 IO 事件;
执行已就绪 Channel 的 IO 操作;
执行已入队的普通任务;
执行到期的定时任务。
理解用伪代码(不是 Netty 源码):
while (!shutdown) {
select(); // 等待或轮询 IO 事件
processSelectedKeys(); // 处理连接读写
runAllTasks(); // 执行队列/到期任务
}真实实现还包括 wakeup、任务执行预算、异常处理和关闭等细节。
3.1 一个 EventLoop 管多少 Channel?
一个 EventLoop 可以关联多个 Channel。
一个 Channel 在正常注册期间通常关联一个 EventLoop。
一个 Channel 的 IO 事件因此通常由固定 EventLoop 线程处理。
分配 EventLoop 常见使用轮询选择,不保证按每个连接的实际 CPU 负载自动均衡。
例子:4 个 Worker EventLoop 维护 2,000 个设备连接,平均分配可以是约 500 个/线程;这只是解释,不代表每个连接带来的工作量相同,也不是 Netty 的硬性上限。
3.2 EventLoop 和普通线程池区别
纠错:EventLoopGroup 不能简单等同于 ThreadPoolExecutor。
四、为什么不能在 IO Handler 里写慢 SQL?
假设 EventLoop-1 同时管理设备 A、B、C。A 的 channelRead 里同步查询数据库耗时 5 秒,那么该 EventLoop 线程在此期间通常无法及时处理 B、C 的 IO 事件。
// 不推荐:在 IO EventLoop 上直接处理耗时同步业务
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
orderService.queryDatabase(msg); // 可能阻塞
}一种常见改进是让耗时业务 Handler 使用 共享的 DefaultEventExecutorGroup:
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
// 在服务启动时创建一次,并在关闭时 shutdownGracefully()
EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(16);
// ChannelInitializer 的 initChannel 中复用 businessGroup
ch.pipeline().addLast(businessGroup, "business", new BusinessHandler());该方式将这个 Handler 的事件执行从 IO EventLoop 转移到另外的执行器,但仍需设计线程数、队列积压、超时和背压。不要每次连接时 new 一个业务线程组。若直接使用 Spring 业务线程池,应做好结果写回、任务拒绝和消息内存生命周期管理。
五、Channel 是什么?
Channel 是 Netty 对网络 Socket/IO 通道的抽象,并提供读写、状态、地址及异步操作 API。
NioServerSocketChannel:服务端监听 Channel。NioSocketChannel:TCP 已连接 Channel。NioDatagramChannel:UDP Channel。
Channel channel = ctx.channel();
System.out.println(channel.id());
System.out.println(channel.remoteAddress());
System.out.println(channel.isActive());
ChannelFuture future = channel.writeAndFlush("hello\n");异步写入的返回值是 ChannelFuture。调用 writeAndFlush() 返回并不表示客户端已经收到或业务已处理。 通过监听 Future 可以判断 Netty 写操作是否成功完成,但仍不代表对端业务已确认。
六、Channel 生命周期
对客户端连接常见事件:
Channel 被注册 → channelRegistered
连接激活 → channelActive
接收数据 → channelRead
一次批量读取结束 → channelReadComplete
连接不再活动 → channelInactive
Channel 注销 → channelUnregistered事件的实际顺序与传输类型、注册/连接状态有关,不能机械假设所有异常流程都会完整走过全部回调。
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class ConnectionLogHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接建立:" + ctx.channel().id());
super.channelActive(ctx); // 继续传播入站事件
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接断开:" + ctx.channel().id());
super.channelInactive(ctx);
}
}七、物联网设备连接映射:推荐做法
设备认证后,可维护 deviceId → Channel 映射,例如:
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class DeviceConnections {
private final ConcurrentMap<String, Channel> channels = new ConcurrentHashMap<>();
public void online(String deviceId, Channel channel) {
channels.put(deviceId, channel); // 仅在鉴权成功后执行
}
public void offline(String deviceId, Channel oldChannel) {
// 只删除对应旧连接,避免旧连接离线时误删新连接
channels.remove(deviceId, oldChannel);
}
public Channel get(String deviceId) {
return channels.get(deviceId);
}
}生产必须考虑:鉴权前禁止任意覆盖映射、同一设备重连时替换连接、连接断开清理、心跳超时、服务器多实例下跨实例路由。内存 ConcurrentHashMap 只代表当前 JVM 的连接。
八、常见误区与踩坑
误区:连接数等于线程数。 一个 EventLoop 可以管理多个 Channel。
误区:4 个 EventLoop 最多服务 4 个连接。 参数是事件循环数量,不是连接上限。
误区:业务不耗时就无需担心。 小量阻塞在高消息量时也会累积延迟。
误区:Netty 自动隔离 SQL 阻塞。 默认 Handler 通常运行在 Channel 的 EventLoop 线程上。
误区:写 Future 成功等于设备执行成功。 设备业务需要自己定义 ACK 与超时重试。
九、常见问题与答案
Q1:什么是 Reactor?
A:基于事件驱动及 IO 多路复用的网络处理模型,线程监听多个连接的就绪事件,再分发事件处理。
Q2:BossGroup 和 WorkerGroup 分工?
A:Boss 通常接收新连接,Worker 负责注册后的客户端 Channel 的 IO 读写事件。
Q3:EventLoop 与 Channel 是一对一吗?
A:不是。一个 EventLoop 可以管理多个 Channel;一个已注册的 Channel 通常属于一个 EventLoop。
Q4:为何不能在 channelRead 中慢查数据库?
A:可能阻塞这个 EventLoop,使同一 EventLoop 上的其他 Channel 的 IO 处理延迟。可用共享业务执行器或异步业务调用隔离阻塞。
Q5:EventLoopGroup 和普通线程池的主要区别?
A:EventLoopGroup 组织的是用于 Channel IO 与事件循环的 EventLoop,不仅仅是普通任务执行器;普通线程池不提供 Channel 的注册和 IO 事件循环。
十、一句话复习
Boss 接新连接,Worker 管已连接的 IO;EventLoop 用固定线程管理多个 Channel,耗时业务要隔离,设备连接要做安全的注册与清理。
