Spring Framework:数据缓冲区和编解码器(Data Buffers and Codecs)

https://docs.spring.io/spring-framework/reference/core/databuffer-codec.html

Java NIO 提供了 ByteBuffer,但许多库在其基础上构建了自己的字节缓冲区 API,特别是在网络操作中,重用缓冲区和/或使用直接缓冲区对性能有益。例如,Netty 有 ByteBuf 层次结构,Undertow 使用 XNIO,Jetty 使用带有回调的池化字节缓冲区来释放,等等。spring-core 模块提供了一组抽象,用于与各种字节缓冲区 API 一起工作,如下所示:

  • DataBufferFactory 抽象了数据缓冲区的创建。
  • DataBuffer 表示一个字节缓冲区,可能是池化的。
  • DataBufferUtils 为数据缓冲区提供了实用方法。
  • 编解码器(Codecs)将数据缓冲区流解码或编码为更高级别的对象。

DataBufferFactory

DataBufferFactory 可以以两种方式之一创建数据缓冲区:

  • 分配一个新的数据缓冲区,并可选地提前指定容量(如果已知),尽管 DataBuffer 的实现可以根据需要增长和缩小,但这样做会更高效。
  • 包装一个现有的 byte[]java.nio.ByteBuffer,用 DataBuffer 实现来装饰给定的数据,并且不涉及分配。

请注意,WebFlux 应用程序不会直接创建 DataBufferFactory,而是通过客户端的 ServerHttpResponseClientHttpRequest 访问它。工厂的类型取决于底层客户端或服务器,例如 Reactor Netty 的 NettyDataBufferFactory,其它情况的 DefaultDataBufferFactory

DataBuffer

DataBuffer 接口提供了与 java.nio.ByteBuffer 类似的操作,但也带来了一些额外的好处,其中一些灵感来自 Netty 的 ByteBuf。下面是一些好处的不完全列表:

  • 使用独立的位置进行读取和写入,即不需要调用 flip() 来在读取和写入之间切换。
  • 按需扩展容量,类似于 java.lang.StringBuilder
  • 通过 PooledDataBuffer 进行缓冲池化和引用计数。
  • 将缓冲区视为 java.nio.ByteBufferInputStreamOutputStream
  • 确定给定字节的索引或最后一个索引。

PooledDataBuffer

正如 ByteBuffer 的 Javadoc 中所解释的,字节缓冲区可以是直接的或非直接的。直接缓冲区可能位于 Java 堆之外,从而消除了本地 I/O 操作所需的复制。这使得直接缓冲区在通过socket 接收和发送数据时特别有用,但它们的创建和释放成本也更高,这导致了缓冲池化的想法。

PooledDataBufferDataBuffer 的扩展,它有助于引用计数,这对于字节缓冲区池化至关重要。它是如何工作的?当分配 PooledDataBuffer 时,引用计数为 1。对 retain() 的调用会增加计数,而对 release() 的调用会减少计数。只要计数大于 0,就可以保证不会释放缓冲区。当计数减少到 0 时,可以释放池化的缓冲区,这实际上可能意味着为缓冲区保留的内存被返回到内存池中。

在大多数情况下,最好不要直接对 PooledDataBuffer 进行操作,而是使用 DataBufferUtils 中的便利方法,这些方法仅在 DataBufferPooledDataBuffer 的实例时才应用 releaseretain

DataBufferUtils

DataBufferUtils 提供了许多对数据缓冲区进行操作的实用方法:

  • 将数据流缓冲区连接成一个单独的缓冲区,可能无需复制,例如通过组合缓冲区,如果底层字节缓冲区 API 支持的话。
  • InputStream 或 NIO Channel 转换为 Flux<DataBuffer>,反之亦然,将
    Publisher<DataBuffer> 转换为 OutputStream 或 NIO Channel
  • 如果缓冲区是 PooledDataBuffer 的实例,则释放或保留 DataBuffer 的方法。
  • 从字节流中跳过或取出特定字节数。

Codecs

org.springframework.core.codec 包提供了以下策略接口:

  • Encoder:将 Publisher<T> 编码为数据流缓冲区。
  • Decoder:将 Publisher<DataBuffer> 解码为更高级别对象的流。

spring-core 模块提供了对 byte[]ByteBufferDataBufferResourceString 的编码器和解码器实现。spring-web 模块添加了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其它编码器和解码器。

使用DataBuffer

当处理数据缓冲区时,必须特别小心以确保释放缓冲区,因为它们可能是池化的。我们将使用编解码器来演示它的工作原理,但这些概念更普遍适用。让我们看看编解码器在内部管理数据缓冲区时必须做什么。

Decoder 是在创建更高级别对象之前读取输入数据缓冲区的最后一个组件,因此它必须按照以下方式释放它们:

  • 如果Decoder 是读取每个输入缓冲区并准备立即释放它,则可以通过DataBufferUtils.release(dataBuffer)来实现。
  • 如果Decoder使用 FluxMono 操作符(如 flatMapreduce 等)来在内部预取和缓存数据项,或者使用 filterskip 等操作符来省略某些项,则必须将 doOnDiscard(DataBuffer.class, DataBufferUtils::release) 添加到组合链中,以确保在丢弃这些缓冲区之前释放它们,这可能是由于错误或取消信号导致的。
  • 如果Decoder以任何其它方式保留一个或多个数据缓冲区,则必须确保在完全读取时释放它们,或者在读取和释放缓存的数据缓冲区之前发生错误或取消信号的情况下释放它们。

DataBufferUtils#join 提供了一种安全且高效的方式来聚合数据流缓冲区到单个数据缓冲区。同样,skipUntilByteCounttakeUntilByteCount 是解码器可以使用的其它安全方法。

Encoder 分配其他人必须读取(和释放)的数据缓冲区。因此,Encoder 不需要做太多事情。但是,如果在向缓冲区填充数据时发生序列化错误,Encoder 必须小心释放数据缓冲区。例如:

DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
	// serialize and populate buffer..
	release = false;
}
finally {
	if (release) {
		DataBufferUtils.release(buffer);
	}
}
return buffer;

Encoder 的使用者负责释放其接收到的数据缓冲区。在 WebFlux 应用程序中,Encoder 的输出用于写入 HTTP 服务器响应或客户端 HTTP 请求,在这种情况下,释放数据缓冲区是写入服务器响应或客户端请求的代码的责任。

当在 Netty 上运行时,有用于排查缓冲区泄漏的调试选项。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值