netty源码解解析(4.0)-21 ByteBuf的设计原理

????io.netty.buffer包中是netty ByteBuf的实现。ByteBuf是一个二进制缓冲区的抽象接口,它的功能有:

  • 可以随机访问、顺序访问。
  • 支持基本数据类型(byte, short, int, long, float, double)的序列化和反序列化。
  • 不限制底层原始的数据类型,可以是byte[], NIO Buffer, String, IO Stream, 直接内(C语言中可以表示为指向一块内置起始地址的指针void*, 内存的长度), 等等。

为什么需要ByteBuf

????缓冲区的使用范围非常广泛:I/O操作,序列化/反序列化,编码转换,压缩/解压,加/解密等所有需要使用byte[]的场景。
????有些场景需要需要能够快速地创建和销毁缓冲区,如:高并发的服务器,处理请求、返回响应的时时候需要大量且高频地创建,销毁缓冲区。
????有些场景不能确定需要多大的缓冲区,如: 从数据流中分离出一条消息,消息的长度不确定,只知道最大长度。假如消息的最大长度是64KB,而消息的平均长度只有4KB, 如果每次创建64KB的缓冲区就太浪费了。如果缓冲区能够在需要的时候自动且高效地增减容量,就完美了。
????在所有的场景中都涉及到频繁的数据copy,这要求缓冲区数据copy的性能要尽量高。如果有可能,尽量减少数据copy。
????ByteBuf就是为解决以上问题而设计的。

核心概念

如下所示

|  discardable bytes | readable bytes |  writable bytes  |
0                readerIndex       writerIndex       capacity
  • capacity: 缓冲区的容量。
  • readerIndex: 当前读的位置。可以使用readerIndex()和readerIndex(int)方法获取、设置readerIndex值。每次调用readXXX方法都会导致readerIndex向writerIndex移动,直到等于writerIndex为止。
  • writerIndex: 写的当前位置。可以使用writerIndex()和writerIndex(int)方法获取、设置writeIndex的值。每次调用writeXXX方法都会导致writeIndex向capacity移动,直到等于capacity为止。
  • discardable bytes: 可丢弃的数据。0--readerIndex之间的数据, 长度是readerIndex - 0,调用discardReadBytes会丢弃这部分数据,把readerIndex--writerIndex之间的数据移动到ByteBuf的开始位置(0), ByteBuf会变成如下所示的样子:
       |   readable bytes  |  writable bytes  |
readerIndex(0)       writerIndex           capacity
  • readable bytes: 可读数据。 readerIndex--writerIndex之间的数据,长度是writerInex - readerIndex。可以调用readableBytes()方法得到它的长度。
  • writeable bytes: 可写的空间。长度是capacity - writerIndex。也可以认为它是ByteBuf的剩余空间。

核心能力

对二进制数据的读写是ByteBuf的核心能力。它提供两种读写方式:

  • 随机读写: getXXX(int), setXXX(int, ...)。不会对readerIndex和writerIndex产生影响,范围是(0,capacity]。
  • 顺序读写: readXXX, 增加readerIndex的值,范围是(readerIndex, writerIndex]。 writeXXX,增加writerIndex值,范围是(writerIndex, capacity]。

ByteBuf为了方便使用,提供了一些基本数据类型(unsigned表示无符号类型)的读写支持:

数据类型 长度(Byte)
byte, unsignedByte 1
short, unsignedShort 2
char 2
medium, unsignedMedium 3
int, unsignedInt 4
long 8
float 4
double 8

对这些基本数据类型的读写涉及到了字节须的问题,ByteBuf支持两种字节序,使用java.nio.ByteOrder中的定义,默认的字节序是BIG_ENDIAN, 这个也是网络字节序。

此外还提供了对byte[]类型及可以当成byte[]使用的数据类型的支持, 方法名都是:getBytes,setBytes, readBytes, writeBytes。

  • byte[]
  • ByteBuf
  • ByteBuffer
  • GatheringByteChannel
  • InputStread, OutputStream

内存管理

内存管理分为两个部分:内存分配,内存释放。
ByteBufAllocator是内存分配的接口,它有两个具体实现:UnpooledByteBufAllocator, PooledByteBufAllocator。
UnpooledByteBufAllocator是JVM内存分配接口的简单封装。
PooledByteBufAllocator在JVM内存分配的基础上实现了一套独立的内存分配算法。
内存释放的关键是如何判定对象死亡,ByteBuf继承了ReferenceCounted接口,使用引用计数的方式判定对象死亡。

PooledByteBufAllocator中高效的内存管理算法是ByteBuf的性能基础,理解了它的算法是理解ByteBuf的关键。

体系结构

graph TD;
B[ByteBuf]-->AB[AbstractByteBuf];
B-->SB[SwappedByteBuf];
B-->WB[WrappedByteBuf];
AB[AbstractByteBuf]-->ARCB[AbstractReferenceCountedByteBuf];
ARCB-->CB[CompositeByteBuf<br>FixedCompositeByteBuf];
ARCB-->PB[PooledByteBuf<T>];
ARCB-->UBB[UnpooledDirectByteBuf<br>UnpooledHeapByteBuf<br>UnpooledUnsafeDirectByteBuf<br>UnpooledUnsafeHeapByteBuf<br>UnpooledUnsafeNoCleanerDirectByteBuf];
ARCB-->ROBB[ReadOnlyByteBufferBuf];
PB[PooledByteBuf<T>]-->PDB[PooledDirectByteBuf];
PB-->PUDB[PooledUnsafeDirectByteBuf];
PB-->PHB[PooledHeapByteBuf];
PHB-->PUHB[PooledUnsafeHeapByteBuf];

上图是ByteBuf体系结构中主要的类和接口。主要分为三大类:

  • AbstractReferenceCountedByteBuf及其子类。这个类别是是ByteBuf中最重要的部分,它分为4个小类别:

CompositeByteBuf, FixedCompositeByteBuf: 把不同的ByteBuf组合成一个ByteBuf。
PooledByteBuf:实现了自定义内存管理算法的。
UnpooledXXXX: 直接使用JVM内存管理能力。
ReadOnlyByteBufferBuf: 只读的。

  • SwappedByteBuf: 用来转换ByteBuf的字节序。
  • WrappedByteBuf: 用来包装另一个ByteBuf。

工具

有两个工具类帮助开发者使用ByteBuf:

  • ByteBufUtil: 这个类中创建好了默认的ByteBufAllocator,可以直接拿来用。还有一些操作ByteBuf常用的方法。
  • Unpooled: 这个类是针对UnpooledXXXByteBuf的工具。

用法

创建ByteBufAllocator

????使用ByteBufUtil.DEFAULT_ALLOCATOR得到ByteBufAllocator实例。这个实例可能是UnpooledByteBufAllocator,也可能是PooledByteBufAllocator类型,这取决于io.netty.allocator.type属性的设置。默认是unpooled,UnpooledByteBufAllocator类型。如果想要PooledByteBufAllocator类型,把这个属性的值设置成pooled:
????java -Dio.netty.allocator.type=pooled,或者System.setProperty("io.netty.allocator.type", "pooled")
????

创建ByteBuf

????netty不建议直接创建创建ByteBuf实例,推荐使用ByteBufAllocator创建ByteBuf实例,或者使用Unpooled静态方法。
????ByteBufAllocator有7种方法用于创建ByteBuf实例:

方法名 特性
buffer 使用可能是JVM堆内存或直接内存,取决于具体的实现
ioBuffer 如果可以的话优先使用直接内存
heapBuffer 使用堆内存
directBuffer 使用直接内存
CompositeByteBuf 使用可能是JVM堆内存或直接内存,取决于具体的实现
compositeHeapBuffer 使用堆内存
compositeDirectBuffer 使用堆内存

Unpooled创建ByteBuf实例的方法有2两种:

方法名 特性
buffer 使用堆内存
directBuffer 使用直接内存

包装成ByteBuf

Unpooled提供了一系列的wrappedBuffer方法,把一些数据类型包装成一个ByteBuf, 这些数据类型有:

  • byte[]。
  • ByteBuffer。
  • 另一个的ByteBuf中的可读数据。

wrappedBuffer方法还可以把byte[].., ByteBuffer.., ByteBuf..包装成一个CompositeByteBuf。

数据读写

数据读写是ByteBuf的基本功能,前面已经讲过,相关方法是: getXXX, readXXX, setXXX, writeXXX。

原文地址:https://www.cnblogs.com/brandonli/p/11534491.html

时间: 2024-10-08 11:37:25

netty源码解解析(4.0)-21 ByteBuf的设计原理的相关文章

netty源码解解析(4.0)-8 ChannelPipeline的设计

io.netty.channel.ChannelPipeline 设计原理 上图中,为了更直观地展示事件处理顺序, 故意有规律地放置两种handler的顺序,实际上ChannelInboundHandler和ChanneOutboundHandler的顺序可以是任意,取决于用户调用add方法把handler方在哪里. ChannelPipeline的特性: 1. 它是一个双向链表 2. 每个节点持有一个ChannelHandler实例,这个实例可以是ChannelInboundHandler类型

netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放

ByteBuf内存分配和释放由具体实现负责,抽象类型只定义的内存分配和释放的时机. 内存分配分两个阶段: 第一阶段,初始化时分配内存.第二阶段: 内存不够用时分配新的内存.ByteBuf抽象层没有定义第一阶段的行为,但定义了第二阶段的方法: public abstract ByteBuf capacity(int newCapacity) 这个方法负责分配一个长度为newCapacity的新内存. 内存释放的抽象实现在AbstractReferenceCountedByteBuf中实现,这个类实

netty源码解解析(4.0)-24 ByteBuf基于内存池的内存管理

PooledByteBuf的初始化过程分为两个步骤:创建实例:初始化内存.这两个步骤的代码如下: 507383170 protected PooledByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) { super(maxCapacity); this.recyclerHandle = recyclerHandle; } void init(PoolChunk<T> chunk, long handle, int offset,

netty源码解解析(4.0)-14 Channel NIO实现:读取数据

 本章分析Nio Channel的数据读取功能的实现. Channel读取数据需要Channel和ChannelHandler配合使用,netty设计数据读取功能包括三个要素:Channel, EventLoop和ChannelHandler.Channel有个read方法,这个方法不会直接读取数据,它的作用是通知持有当前channel的eventLoop可以从这个这个channel读取数据了,这个方法被调用之后eventLoop会在channel有数据可读的时候从channel读出数据然后把数

netty源码解解析(4.0)-6 线程模型-IO线程EventLoopGroup和NIO实现(一)

接口定义 io.netty.channel.EventLoopGroup extends EventExecutorGroup 方法 说明 ChannelFuture register(Channel channel) 把一个channel注册到一个EventLoop ChannelFuture register(Channel channel, ChannelPromise promise); 同上 io.netty.channel.EventLoop extends OrderedEvent

netty源码解解析(4.0)-1 核心架构

netty是java开源社区的一个优秀的网络框架.使用netty,我们可以迅速地开发出稳定,高性能,安全的,扩展性良好的服务器应用程序.netty封装简化了在服务器开发领域的一些有挑战性的问题:jdk nio的使用:多线程并发:扩展性.它还提供了多种应用层协议的支持:http/https/websock, protobuf, 自定义协议, 简化了服务器协议的开发. netty是一个基于事件驱动的框架,它把事件分成两种类型:输入事件(inbound)和输出事件(outbound), 整个框架都是围

netty源码解解析(4.0)-16 ChannelHandler概览

本章开始分析ChannelHandler实现代码.ChannelHandler是netty为开发者提供的实现定制业务的主要接口,开发者在使用netty时,最主要的工作就是实现自己的ChannelHandler.ChannelHandler在设计上需要和ChannelPipeline配合共同实现pipeline的事件传递能力,这要求ChannelHandler需要实现一些固定的基本功能.由于这个原因,如果让用户自己完整地实现,会显得比较麻烦.为此netty实现类一系列的类来帮助开发者以简单的方式实

JAVA框架底层源码剖析系列Spring,Mybatis,Springboot,Netty源码深度解析

<Spring源码深度解析>从核心实现和企业应用两个方面,由浅入深.由易到难地对Spring源码展开了系统的讲解,包括Spring的设计理念和整体架构.容器的基本实现.默认标签的解析.自定义标签的解析.bean的加载.容器的功能扩展.AOP.数据库连接JDBC.整合MyBatis.事务.SpringMVC.远程服务.Spring消息服务等内容. <Spring源码深度解析>不仅介绍了使用Spring框架开发项目必须掌握的核心概念,还指导读者如何使用Spring框架编写企业级应用,并

netty源码解析(4.0)-29 Future模式的实现

Future模式是一个重要的异步并发模式,在JDK有实现.但JDK实现的Future模式功能比较简单,使用起来比较复杂.Netty在JDK Future基础上,加强了Future的能力,具体体现在: 更加简单的结果返回方式.在JDK中,需要用户自己实现Future对象的执行及返回结果.而在Netty中可以使用Promise简单地调用方法返回结果. 更加灵活的结果处理方式.JDK中只提供了主动得到结果的get方法,要么阻塞,要么轮询.Netty除了支持主动get方法外,还可以使用Listener被