介绍一下 Reactor 线程模型?
- 标签:Netty、后端
🏛 业务架构师
Reactor 是基于 IO 多路复用的事件驱动线程模型,三种形态对应业务的三个规模阶段:
- 单 Reactor 单线程:一个线程既监听事件又处理读写与业务。简单无锁,但业务逻辑稍慢就拖垮所有连接——只适合小流量内部组件(如配置代理);
- 单 Reactor 多线程:Reactor 只管事件分发,业务交给线程池。计算密集逻辑不再阻塞 IO,多数中等规模服务的答案;
- 主从 Reactor 多线程(Netty 默认形态):主 Reactor(boss)只管 accept 新连接,注册给**从 Reactor(worker 组)**各自负责一批连接的读写,N 个 worker 均摊海量连接。这就是"网关型业务"的标准架构。
业务价值我算过账:传统一连接一线程(BIO),10 万连接要 10 万线程,内存和上下文切换直接压垮机器;主从 Reactor 用几个线程服务数十万连接——推送系统、IM、API 网关从"百台机器"变"十几台",单机连接密度就是机器成本。
两点业务侧的落地经验:
- worker 数量按业务画像定:纯 IO 转发型(网关)少量 worker 即可(CPU 核数级别);handler 里有计算,再拆业务线程池——不要把慢业务写在 IO 线程里,一个慢 handler 会饿死同 worker 上的所有连接,这是 Netty 应用最高频的线上事故;
- 串行化红利:同一连接的事件在同一 worker 内串行执行,连接内无需加锁——业务建模时把"同一用户的请求路由到同一线程"(类似一致性哈希到队列),可以把锁成本降到零。
Reactor 的本质:用事件分发替代线程等待,用少量线程承载海量并发。
🏗 系统架构师
Reactor 是基于 I/O 多路复用的事件驱动模型:由一个或多个"等待事件 → 分发事件"的循环(Reactor)代替"每连接一线程"的阻塞模型,用少量线程支撑海量连接。三种形态:
- 单 Reactor 单线程(Redis 6.0 前):acceptor + IO + 业务全在一个线程,无锁简单,但一个慢操作阻塞全部,且吃不满多核;
- 单 Reactor 多线程(多 worker):Reactor 线程只管事件监听与分发,读到的数据交给 worker 线程池处理业务,计算密集与 IO 解耦;
- 主从 Reactor 多线程(Netty 的形态):mainReactor(BossGroup)只管 accept,把新连接注册给 subReactor(WorkerGroup,默认 CPU 核数个 NioEventLoop),每个 EventLoop 绑定一个 Selector 独立处理一批连接的读写——连接分担到多个 epoll 实例,彻底消除单点瓶颈;业务再复杂时加业务线程池(EventLoop 不做耗时操作)。
Netty 映射:Boss → NioServerSocketChannel 的 accept;Worker → NioEventLoop 循环(select → processIO → runAllTasks);同一 Channel 的所有事件固定在同一 EventLoop 串行执行——串行无锁化,这是 Netty 高性能的骨架设计。我做网关、IM 长连接选型时,都是这套模型的直接受益者。
💻 全栈开发专家
Reactor = 基于 I/O 多路复用的"事件分发"模型,三种形态:
1. 单 Reactor 单线程:一个线程搞定 accept/读写/业务。代表 Redis。简单无锁,但业务慢会卡全局、多核用不满。
2. 单 Reactor 多线程:Reactor 线程只管事件分发,业务丢线程池。但单个 Reactor 仍是瓶颈(百万连接时 accept/轮询顶不住)。
3. 主从 Reactor 多线程(主流):
text
mainReactor(boss):只管 OP_ACCEPT,accept 后把连接分给 subReactor
subReactor(worker 池):每个 EventLoop 绑定一个 Selector + 一个线程,
负责这条连接后续所有读写事件 —— 连接和线程固定绑定Netty 的标准落地:
java
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup(); // 默认 2*CPU
ServerBootstrap b = new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() { ... });
b.bind(8080);为什么快(引申加分):连接绑定固定线程 → 同一条连接的读写天然串行无锁(handler 里不用 synchronized);耗时业务再丢业务线程池(自定义 EventExecutorGroup),EventLoop 绝不阻塞——这是 Netty 开发的第一军规,handler 里写 JDBC 调用就是事故。