零拷贝机制是Netty高性能的一个原因,之前都是说netty的线程模型,责任链,说说netty底层的优化,优化就是netty自己的一个缓冲区。
(一)Netty自己的ByteBuf
介绍
ByteBuf 是为解决 ByteBuffer的问题和满足网络应用程序开发人员的日常需求而设计的。
对比JDK byteBuffer的缺点
无法动态扩容
长度是固定的,不能动态扩展和收缩,当数据大于ByteBuffer容量时,会发生索引越界异常。
API 使用复杂
读写的时候需要手工调用flip()和rewind()等方法,使用时需要非常谨慎的考虑这些API,否则容出现错误。
Netty的ByteBuf 操作
ByteBuf三个重要属性:capacity容量,readerIndex读取位置,writerIndex 写入位置。提供了两个指针变量来支持顺序和写操作,分别是读操作readerIndex 和写操作writeIndex。
常见的方法定义
随机访问索引 getByte
顺序读 read*
顺序写 write*
清除已读内容discardReadBytes
清除缓冲区 clear
搜索操作
标记和重置
引用计数和释放
缓冲区是如何被两个指针分割成三个区域的
discardable bytes 已读可丢弃区域
readable bytes 可读区域
writable bytes 待写区域
实例
ByteBuf 动态扩容
capacity 默认值:256字节,最大值:Integer.MAX_VALUE(2GB)
write 方法调用时,通过AbstractByteBuf.ensureWritable进行检查。
容量计算方法:AbstractByteBufAllocator.calculateNewCapacity(新capacity的最小要求,capacity最大值)
根据新的capacity的最小值要求,对应有两套计算方法
没超过4兆:从64字节开发,每次增加一倍,直至计算出来的newCapacity满足新容量最小要求。示例:当前大小256,已写250,继续写10字节数据,需要的容量最小要求是261,则新容量是6422*2=512
超过4兆:新容量 = 新容量最小要求/4兆 * 4兆 +4兆
示例:当前大小3兆,已写3兆,继续写2兆数据,需要的容量最小要求是5兆, 则新容量是9兆(不能超过最大值)
选择合适的 ByteBuf 实现
在实际使用中都是通过 ByteBufAllocator 分配器进行申请,同时分配器具有内存管理的功能。
unsafe 用到了 Unsafe 工具类,Unsafe 是 Java 保留的一个底层工具包,safe 则没有用到 unsafe 工具类。
unsafe 意味着不安全的操作,但是更底层的操作会带来性能提升和特殊功能,Netty 中会尽力使用 unsafe。
Java 语言很重要的特性是“一次编写导出运行”,所以它针对底层的内存或其他操作,做了很多封装。而 unsafe 提供了一系列操作底层的方法,可能会导致不兼容或者不可知的异常。
unpool 每次申请缓冲区时会新建一个,并不会复用,使用 Unpooled 工具类可以创建 unpool 的缓冲区。
Netty 没有给出很便捷的 pool 类型的缓冲区的创建方法。使用 ChannelConfig.getAllocator() 时,获取到的分配器是默认支持内存复用的。
pooledByteBuf对象、内存
PoolThreadCache: PooledByteBufAllocator 实例维护了一个线程变量。
多种分类的MemoryRegionCache数组用作内存缓存,MemoryRegionCache内部是链表,队列里面存Chunk。
Pool Chunk里面维护了内存引用,内存复用的做法就是把buf的memory指向Chunck的memory。
(二)零拷贝
介绍
Netty 的零拷贝机制,是一种应用层的实现。
拷贝方式
一般的数组合并,会创建一个大的数组,然后将需要合并的数组放进去。
Netty 的 CompositeButyBuf 将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免了各个 ByteBuf 之间的拷贝。
wrappedBuffer 方法将 byte[] 数组包装成 ByteBuf 对象
slice 方法将一个 ByteBuf 对象切分成多个 ByteBuf 对象
实例
PS:API操作便捷性,动态扩容,多种ByteBuf实现,高效的零拷贝机制(逻辑上边的设计)上边的所有就是nettyByteBuf所做的工作,性能提升,操作性增强。有了理论下节开始实战netty。
领取专属 10元无门槛券
私享最新 技术干货