EventLoop分组必须严格分离Boss和Worker:BossGroup仅处理OP_ACCEPT,WorkerGroup专责OP_READ/OP_WRITE,否则accept压力会拖慢所有I/O;单个EventLoopGroup误用于group()双参数将导致线程争抢Selector而卡死。

EventLoop 分组必须严格分离 Boss 和 Worker
网关不能把连接接入和 I/O 处理混在同一个 EventLoopGroup 里,否则 accept 压力大时会拖慢所有读写。BossGroup 只做 OP_ACCEPT,WorkerGroup 专责 OP_READ/OP_WRITE,这是主从 Reactor 模型的底线。
常见错误是误用单个 NioEventLoopGroup 同时传给 ServerBootstrap.group() 的两个参数,结果所有线程都去抢 accept,连接洪峰一来就卡住。
-
bossGroup = new NioEventLoopGroup(1):固定 1 个线程,避免多线程竞争 Selector -
workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2):根据 CPU 核心数扩展,但别盲目堆数量,过多线程反而增加上下文切换开销 - 务必调用
childOption(ChannelOption.SO_KEEPALIVE, true),防止长连接被中间设备静默断开
Channel 绑定后不可跨 EventLoop 执行耗时逻辑
每个 Channel 在注册时就被固定绑定到某个 EventLoop,后续所有 I/O 事件、channelRead()、writeAndFlush() 都在该线程串行执行。一旦你在 ChannelInboundHandler 里做数据库查询、远程 HTTP 调用或复杂 JSON 解析,整个 Channel 的读写就会阻塞。
正确做法是识别出真正耗时的操作,立刻移交出去:
- 用
eventLoop().execute()提交的仍是同一线程,没用;必须用独立业务线程池 - 推荐用
ctx.executor().submit(() -> { /* DB 查询 */ }),前提是这个Executor是你显式配置的线程池(比如new ThreadPoolExecutor(...)) - 注意:移交后要手动把结果写回 Channel,不能依赖原 handler 的 ctx,因为线程已切换
心跳与超时必须由 EventLoop 自身驱动,不能依赖外部定时器
网关维持海量长连接,心跳检测如果用 java.util.Timer 或 Spring 的 @Scheduled,会创建大量线程且无法与 Channel 生命周期对齐。EventLoop 内置的调度能力才是轻量可靠的。
示例中常见陷阱是把心跳逻辑写在 channelActive() 里直接调用 scheduleAtFixedRate(),但没检查 Channel 是否还活跃,导致任务泄漏:
- 用
ctx.channel().eventLoop().scheduleAtFixedRate(),确保调度器和 Channel 同属一个线程 - 每次心跳发送前先判断
ctx.channel().isActive(),失败则取消任务:future.cancel(true) - 超时检测也一样:收到消息后重置
ctx.channel().attr(ATTR_TIMEOUT).set(System.nanoTime()),再起一个延迟任务检查纳秒差值
内存泄漏和 ByteBuf 释放必须在 EventLoop 线程内完成
Netty 的 ByteBuf 引用计数机制要求 release() 必须在分配它的 EventLoop 线程中调用,否则可能引发 IllegalReferenceCountException 或静默泄漏。
尤其在转发场景下,比如网关把 A 客户端的数据解码后转给 B 客户端,很容易在业务线程里提前 release,或者忘记 release:
- 所有
ByteBuf的readBytes()、slice()、duplicate()都会增加引用计数,必须配对release() - 若用池化内存(
PooledByteBufAllocator),未 release 的缓冲区不会被回收,GC 也清不掉 - 最稳妥方式:在
channelReadComplete()或writeAndFlush()后统一msg.release(),且确保这个 handler 不在业务线程池里执行
真正难的不是配置几个 EventLoopGroup,而是守住「I/O 线程绝不做耗时事」这条线——哪怕一行日志打印、一次字符串拼接,在百万连接下都可能成为瓶颈。所有脱离 EventLoop 的操作,必须有明确的移交路径和生命周期管理。










