主从Reactor多Selector模型通过Boss线程专责Accept、Worker线程独占Selector处理IO,实现负载均衡与多核利用;连接分发需线程安全注册,资源隔离与异常防护必不可少。

在多核服务器上构建主从 Reactor 多 Selector 线程模型,核心是让 一个 Boss 线程(主 Reactor)负责 Accept 连接,再将新连接 负载均衡地分发给多个 Worker 线程(从 Reactor),每个 Worker 独占一个 NIO Selector 并处理读写事件。这样既避免了单个 Selector 成为瓶颈,又充分利用多核 CPU 资源。
主 Reactor:仅做 Accept,不处理 IO
主 Reactor 绑定到 ServerSocketChannel,只注册 OP_ACCEPT 事件。它不参与任何数据读写、编解码或业务逻辑。收到新连接后,通过线程安全的方式(如轮询、原子计数器、或一致性哈希)选择一个 Worker 线程,把 Channel 注册到该 Worker 的 Selector 上。
- 使用 非阻塞模式 创建 ServerSocketChannel,并配置 SO_REUSEADDR
- 主 Reactor 自己运行在一个独立线程中,调用 select() + processSelectedKeys() 循环
- 不要在主 Reactor 中执行耗时操作(如 TLS 握手初始化、权限校验),这些应移交到 Worker 或后续阶段
从 Reactor:每个 Worker 独占一个 Selector
每个 Worker 线程初始化自己的 Selector 和 EventLoop,负责其下所有 Channel 的 OP_READ / OP_WRITE 事件处理。关键在于:Channel 注册必须发生在对应 Worker 线程的 Selector 上,且不能跨线程调用 register() —— 否则会抛出 IllegalSelectorException。
- Worker 启动时创建 Selector,并启动自己的事件循环(select → dispatch → handle)
- 主 Reactor 通过 任务队列 + wakeup() 方式通知目标 Worker:向其内部队列提交一个“注册 Channel”的 Runnable,Worker 在下一次 select 前执行它
- 推荐使用 NioEventLoopGroup(Netty)或自定义 EventLoop(JDK NIO) 封装 Worker 行为,避免裸写线程和 Selector 管理
连接分发与线程绑定策略
新连接如何分配到哪个 Worker,直接影响负载均衡效果。简单轮询(Round-Robin)适用于连接生命周期相近的场景;若连接流量差异大(如长连接 vs 短连接),可考虑基于当前 Selector 的 selectedKeys 数量或 pending task 数做加权调度。
- 避免使用随机分发——可能导致热点 Worker
- 连接一旦分配给某 Worker,其整个生命周期(读、写、关闭)都由该 Worker 全权处理,不跨线程迁移
- 如果需跨 Worker 协作(如广播消息),应通过线程安全的共享结构(如 ConcurrentMap + RingBuffer)通信,而非直接调用对方 Selector
注意资源隔离与异常防护
每个 Selector 是线程局部资源,但 Channel、ByteBuffer、业务处理器等可能被多线程间接访问。务必确保:
- Channel 的 read()/write() 操作始终在其归属 Worker 线程中执行
- 业务 Handler(如解码器、业务逻辑)不应持有跨线程共享的可变状态,或加锁保护
- Worker 线程内发生未捕获异常时,需记录日志并重启该 EventLoop(防止 Selector 失效),但不要终止整个服务
- 定期调用 Selector 的 selectNow() + cleanUp() 防止 cancelledKey 泄漏(尤其高频断连场景)

















