Netty原理分析

Netty原理分析

文章目录

1. 简介

Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的协议服务器和客户端。它封装了 JDK 底层复杂的 NIO(Non-blocking IO)API,提供了一套更加易用、安全、高效的网络编程接口。

很多知名的开源项目(如 Dubbo、RocketMQ、Elasticsearch)的底层网络通信层都采用了 Netty,我自己做即时通讯、物联网平台、智能家居等相关的项目也频繁的用到了Netty。

2.Netty核心组件理解

  • Channel(通道)

    Channel 可理解为一条TCP的连接的抽象抽象,代表了一个到实体(如硬件设备、文件、网络套接字)的开放连接。你可以把它理解为传入(Inbound)或传出(Outbound)数据的载体。

  • EventLoopGroup

    用来管理EventLoop

  • EventLoop(事件循环)

    EventLoop 负责处理 Channel 的所有 I/O 操作。一个 EventLoop 内部维护了一个线程,在其生命周期内,它会处理注册在其上的 Channel 的所有事件(如连接建立、IO数据读写)。

    机制: 一个 EventLoop 可以管理多个 Channel,但一个 Channel 只会绑定到一个 EventLoop 上,它将在它的整个生命周期中都使用这个

    EventLoop(以及相关联的 Thread)。从而保证了线程安全,更详细的见下面的:无锁串行化设计。

  • ChannelFuture(异步结果)

由于 Netty 的操作都是异步的,操作会立即返回,但不保证此时已经完成。ChannelFuture 用于在稍后的时间点获取操作的结果(成功或失败),或者注册监听器(Listener)在操作完成时触发回调。

  • ChannelHandler(通道处理器)

    这是win开发者打交道最多的组件。它负责处理 I/O 事件或拦截 I/O 操作,比如:编码、解码操作、断包粘包处理、业务逻辑

    • InboundHandler: 处理入站数据(如读取数据)。
    • OutboundHandler: 处理出站数据(如发送数据)。
  • ChannelPipeline(流水线)

    其实是一个channelHandler的责任链,当数据流经 Channel 时,会按照 Pipeline 中 Handler 的顺序依次进行处理。这就好比工厂的流水线,数据是原材料,Handler 是工序。

    通过channelInitializer来吧channelHadler注册到channelPipleline

  • ByteBuf

双指针设计: ByteBuf 维护了 readerIndex(读指针)和 writerIndex(写指针)。写操作移动写指针,读操作移动读指针,无需像 JDK ByteBuffer 那样频繁调用 flip() 方法切换读写模式。

零拷贝(Zero Copy): Netty 提供了 CompositeByteBuf 等机制,允许将多个物理缓冲区在逻辑上组合成一个,或者直接进行文件传输,减少了数据在内存中的复制次数

核心组件关系图

ChannelPipeline和ChannelHandler

  • 每一个新创建的 Channel 都将会被分配一个新的 ChannelPipeline。这项关联是永久性的;
  • 从头入站从尾出站:从左到右进行编号,那么第一个被入站事件看到的 ChannelHandler 将是1,而第一个被出站事件看到的 ChannelHandler 将是 5

3.关键技术点源码分析

线程模型 (Reactor模式)

  • BossGroup(主线程池): 专门负责处理客户端的连接请求(Accept 事件)。一旦连接建立,它将连接注册到 WorkerGroup。
  • WorkerGroup(工作线程池): 负责处理已建立连接的 I/O 读写事件(Read/Write 事件)以及非阻塞的业务逻辑。

无锁串行化设计

  • EventLoop的执行逻辑

  • SingleThreadEventLoop源码

java 复制代码
    //io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, boolean, io.netty.channel.ChannelPromise)
private void write(Object msg, boolean flush, ChannelPromise promise) {
        final AbstractChannelHandlerContext next = findContextOutbound(flush ?
                (MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
        final Object m = pipeline.touch(msg, next);
  //1.将要在channel对应的EventLoop中执行任务,即从channel().eventLoop()获取
        EventExecutor executor = next.executor();
  //2.判断当前调用线程是否就是分配给到EventLLoop的那个线程
        if (executor.inEventLoop()) {
         	//3.如果线程是EventLoop对应的线程,则直接执行任务
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
         //4.如果线程不是EventLoop对应的线程,包装为任务放入队列以便下一次执行
            final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
            if (!safeExecute(executor, task, promise, m, !flush)) {
                // See https://github.com/netty/netty/issues/8343.
                task.cancel();
            }
        }
    }

    private static boolean safeExecute(EventExecutor executor, Runnable runnable,
            ChannelPromise promise, Object msg, boolean lazy) {
        try {
            executor.execute(runnable);
            return true;
        } catch (Throwable cause) {
            return false;
        }
    }

//io.netty.util.concurrent.SingleThreadEventExecutor#inEventLoop
    public boolean inEventLoop(Thread thread) {
        return thread == this.thread;
    }

//io.netty.util.concurrent.SingleThreadEventExecutor#execute(java.lang.Runnable)
    private void execute(Runnable task, boolean immediate) {
        boolean inEventLoop = inEventLoop();
      //添加到任务队列
        addTask(task);
        if (!inEventLoop) {
          //如果没有开线程则开启
            startThread();
        }
    }

4.最佳实践与注意事项

  • 避免阻塞 EventLoop:****严禁ChannelHandler 中执行耗时操作(如 DB 查询、HTTP 调用)

    • 正确做法:提交到业务线程池处理

    复制代码
      ctx.executor().submit(() -> {
          // 异步业务逻辑
          ctx.writeAndFlush(response);
      });
  • 合理释放资源: 对于入站消息(ByteBuf),如果已经被读取且不需要传递给下一个 Handler,务必调用 ReferenceCountUtil.release() 释放内存,防止内存泄漏。

  • 粘包与拆包: TCP 是流式协议,Netty 提供了多种解码器(如 LengthFieldBasedFrameDecoder、LineBasedFrameDecoder)来解决数据传输中的粘包和拆包问题,开发时应优先使用这些现成的组件

    java 复制代码
    public class TcpFrameDecoder extends LengthFieldBasedFrameDecoder {
    
        public TcpFrameDecoder() {
            super(
                    2048,      // 最大帧长
                    3,         // 长度字段偏移(跳过2字节帧头+1字节版本)
                    2,         // 长度字段长度
                    -1,        // 长度调整
                    0          // 初始跳过字节数
            );
        }
    }
相关推荐
小突突突21 小时前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
Mr.Entropy1 天前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
菜鸟233号1 天前
力扣96 不同的二叉搜索树 java实现
java·数据结构·算法·leetcode
sww_10261 天前
Spring-AI和LangChain4j区别
java·人工智能·spring
泡泡以安1 天前
【爬虫教程】第7章:现代浏览器渲染引擎原理(Chromium/V8)
java·开发语言·爬虫
月明长歌1 天前
Java进程与线程的区别以及线程状态总结
java·开发语言
汪不止1 天前
使用模板方法模式实现可扩展的动态查询过滤器
java·模板方法模式
Facechat1 天前
视频混剪-时间轴设计
java·数据库·缓存
蝎子莱莱爱打怪1 天前
我的2025年年终总结
java·后端·面试