Netty源码剖析之核心组件

1、NIOEventLoop

NioEventLoop有以下有个核心功能。

  • 开启Selector并初始化。
  • 把ServerSocketChannel注册到Selector上。
  • 处理各种I/O事件,如OP_ACCEPT、OP_CONNECT、OP_READ、
    OP_WRITE事件。
  • 执行定时调度任务。
  • 解决JDK空轮询bug。

NioEventLoop这些功能的具体实现大部分都是委托其他类来完成 的,其本身只完成数据流的接入工作

在这里插入图片描述

2、AbstractChannel

AbstractChannel抽象类包含以下几个重要属性。

  • EventLoop:每个Channel对应一条EventLoop线程。
  • DefaultChannelPipeline:一个Handler的容器,也可以将其理解为一个Handler链。Handler主要处理数据的编/解码和业务逻辑。
  • Unsafe:实现具体的连接与读/写数据,如网络的读/写、链路 关闭、发起连接等。命名为Unsafe表示不对外提供使用,并非不安全。

在这里插入图片描述

在这里插入图片描述

3、ByteBuf

网络传输中,字节是基本单位NIO使用ByteBuffer作为Byte字 节容器,但是其使用过于复杂。因此Netty写了一套Channel,代替了NIO 的 Channel 。 Netty 缓 冲 区 又 采 用 了 一 套 ByteBuf 代 替 了 NIO 的ByteBufferNetty的ByteBuf子类非常多,这里只对核心的ByteBuf进 行详细的剖析

在这里插入图片描述

NIO ByteBuffer只有一个位置指针position,在切换读/写状态 时,需要手动调用flip()方法或rewind()方法,以改变position的 值,而且ByteBuffer的长度是固定的,一旦分配完成就不能再进行扩 容和收缩,当需要放入或存储的对象大于ByteBuffer的容量时会发生异常。每次编码时都要进行可写空间校验。

Netty的AbstractByteBuf将读/写指针分离,同时在写操作时进行 了自动扩容。对其使用而言,无须关心底层实现,且操作简便、代码无冗余

3.1、AbstractByteBuf

AbstractByteBuf是ByteBuf的子类,它定义了一些公共属性,如 读索引、写索引、mark、最大容量等。AbstractByteBuf实现了一套 读/写操作的模板方法,其缓冲区真正的数据读/写由其子类完成

在这里插入图片描述

3.2、AbstractReferenceCountedByteBuf

Netty在进行I/O的读/写时使用了堆外直接内存,实现了零拷贝, 堆外直接内存Direct Buffer的分配与回收效率要远远低于JVM堆内存 上对象的创建与回收速率。Netty使用引用计数法来管理Buffer的引用 与释放。

Netty采用了内存池设计,先分配一块大内存,然后不断地重 复利用这块内存。例如,当从SocketChannel中读取数据时,先在大内 存块中切一小部分来使用,由于与大内存共享缓存区,所以需要增加 大内存的引用值,当用完小内存后,再将其放回大内存块中,同时减少其引用值

由于ByteBuf的操作可能存在多线程并发使用的情况,其refCnt属 性的操作必须是线程安全的,因此采用了volatile来修饰,以保证其 多线程可见。在Netty中,ByteBuf会被大量地创建,为了节省内存开 销,通过AtomicIntegerFieldUpdater来更新refCnt的值

没有采用AtomicInteger类型。因为AtomicInteger类型创建的对象比int类型多 占用16B的对象头,当有几十万或几百万ByteBuf对象时,节约的内存
可能就是几十MB或几百MB。

在这里插入图片描述

3.3、ReferenceCountUpdater

ReferenceCountUpdater 是 AbstractReferenceCountedByteBuf 的 辅助类,用于完成对引用计数值的具体操作

在这里插入图片描述

3.4、CompositeByteBuf

CompositeByteBuf的主要功能是组合多个ByteBuf,对外提供统一 readerIndex和writerIndex。由于它只是将多个ByteBuf的实例组装 到一起形成了一个统一的视图,并没有对ByteBuf中的数据进行拷贝,因此也属于Netty零拷贝的一种,主要应用于编码和解码,注意:组合并不一定真的要将数据全部放入一个集合,我可以仅仅保留指向单个的引用指针

应用场景

编码时,将消息头和消息体两个ByteBuf组合到一块进行编码,可能 会觉得Netty有写缓冲区,其本身就会存储多个ByteBuf,此时只需把 两个ByteBuf分别写入缓冲区ChannelOutboundBuffer即可,没必要使 用组合ByteBuf。但是在将ByteBuf写入缓冲区之前,需要对整个消息进行编码,如长度编码,此时需要把两个ByteBuf合并成一个,无须额 外处理就可以知道其整体长度。因此使CompositeByteBuf是非常适合的。

在解码时,由于Socket通信传输数据会产生粘包和半包问题,因 此需要一个读半包字节容器,这个容器采用CompositeByteBuf比较合 适,将每次从Socket中读到的数据直接放入此容器中,少了一次数据 的拷贝。Netty的解码类ByteToMessageDecoder默认的读半包字节容器Cumulator 未 采 CompositeByteBuf , 此 时 可 在 其 子 类 中 调 用setCumulator进行修改。但需要注意的是,CompositeByteBuf需要依 赖具体的使用场景。因为CompositeByteBuf使用了复杂的算法逻辑,所以其效率有可能比使用内存拷贝的低

在这里插入图片描述

3.5、PooledByteBuf

这 个类继承于AbstractReference CountedByteBuf,其对象主要由内存 池分配器PooledByteBufAllocator创建。

比较常用的实现类有两种: 一种是基于堆外直接内存池构建的PooledDirectByteBuf,是Netty在 进行I/O的读/写时的内存分配的默认方式,堆外直接内存可以减少内 存 数 据 拷 贝 的 次 数 ; 另 一 种 是 基 于 堆 内 内 存 池 构 建 的PooledHeapByteBuf。

除 了 上 述 两 种 实 现 类 , Netty 还 使 用 Java 的 后 门 类
sun.misc.Unsafe实现了两个缓冲区
即PooledUnsafeDirectByteBuf和PooledUnsafeHeapByteBuf。这个强大的后门类会暴露对象的底层地址,一般不建议使用,Netty为了优化性能引入了Unsafe。PooledUnsafeDirectByteBuf会暴露底层DirectByteBuffer的地址memoryAddress,而Unsafe则可通过memoryAddress+Index的方式取得对应的字节。

PooledUnsafeHeapByteBuf也会暴露字节数组在Java堆中的地址, 因此不再使用字节数组的索引,即array[index]。同样,Unsafe可通过BYTE_ARRAY_BASE_OFFSET+Index字节的地址获取字节。

由于创建PooledByteBuf对象的开销大,而且在高并发情况下,当 网络I/O进行读/写时会创建大量的实例。因此,为了降低系统开销,Netty对Buffer对象进行了池化,缓存了Buffer对象,使对此类型的Buffer可进行重复利用。PooledByteBuf是从内存池中分配出来的Buffer,因此它需要包含内存池的相关信息,如内存块Chunk、PooledByteBuf在内存块中的位置及其本身所占空间的大小等

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白鸽呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值