likes
comments
collection
share

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

作者站长头像
站长
· 阅读数 16

往期相关文章:

前言

为了方便阅读本文,这里简单的总结一下AbstractByteBuf的扩容规则:

  • 如果扩容量是超过阈值threshold,则以阈值为步长进行扩容。
  • 如果没有超过阈值,则以64倍增。

容量小的时候,扩得幅度大,容量大的时候扩得幅度小。这就是他的特点。

今天讲的AdaptiveRecvByteBufAllocator扩容规则跟前者完全相反:

容量小的时候,扩得幅度小,容量大的时候扩得幅度大

同属于ByteBuf,缺走上了不同的道路,属实是有点叛逆心理。

背后到底是人性..还是道德..呃呃呃...进入正片!


AdaptiveRecvByteBufAllocator

NioBytUnsafe#read方法会调用#revBufAllocHandler创建handler

read方法

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

重点是看allocHandle的初始化。他是一个RecvByteBufAllocator的一个内部类Handler类型的,主要介绍2个比较特别的子类实现:

  • FixedRecvByteBufAllocator
  • AdaptiveRecvByteBufAllocator

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

FixedRecvByteBufAllocator非常好理解,人如其名,他是固定大小的一个类型。

主要是看AdaptiveRecvByteBufAllocator,他的缓冲区大小是可以动态调整的ByteBuf分配器

成员变量

缓冲区大小可以动态调整的ByteBuf分配器

他有3个系统默认值:

  • DEFAULT_MINIMUM(最低):64
  • DEFAULT_INITIAL(初始容量):1024
  • DEFAULT_MAXIMUM (最大容量):65536

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

还定义了2个动态调整容量时用到的步进参数:

  • INDEX_INCREMENT (扩张):4
  • INDEX_DECREMENT (收缩):1

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

最后还有一张定义了长度的向量表SIZE_TABLE

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

record方法

重点的扩缩容方法:#record

当NioSocketChannel执行完读操作后,会计算获得本次轮询读取的总字节数,他就是参数:actualReadBytes,然后执行#record方法,根据实际读取的字节数对ByteBuf进行动态伸缩和扩展

意思就是:它可以根据上一次实际读取到的大小,来影响下一次接受Buffer缓冲区的大小分配情况

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

优点在于:

  • 通用性:Netty作为一个通用的NIO框架,不对用户的应用场景进行假设,不管你是用来做流媒体传输这种大流量的,还是用来做聊天工具。传输的码率千变万化,所以Netty设计得可以根据上一次实际读取得码流大小来对下一次接受Buffer缓冲区进行预测和调整
  • 性能高:容量大,占内存,容量小,频繁扩容会带来性能下降,这套算法会更加平滑
  • 节约内存:平时聊天可能就消耗1mb左右,突然用户发送了一个10mb的文件,缓冲区扩展成10mb,如果不能支持收缩,那么每次缓冲区都会被分配10mb,会浪费内存,这就是收缩的好处

AdaptiveRecvByteBufAllocator平滑扩缩容

扩缩容,意味着意味着当流量变大时,这个byteBuf会扩容,当流量变小时,也懂得缩容来减少空间占用

刚刚提到了一个他维护了一个步进长度向量表,他的样子长成这样:

 0-->16 1-->32 2-->48 3-->64 4-->80 5-->96 6-->112 7-->128 8-->144
 9-->160 10-->176 11-->192 12-->208 13-->224 14-->240 15-->256 16-->272
 17-->288 18-->304 19-->320 20-->336 21-->352 22-->368 23-->384 24-->400 25-->416
 26-->432 27-->448 28-->464 29-->480 30-->496 
 ================================================================================
 31-->512 32-->1024 
 33-->2048 34-->4096 
 35-->8192 36-->16384 
 37-->3276838-->65536
 39-->13107240-->262144
 41-->52428842-->1048576 
 43-->2097152 44-->4194304 
 45-->838860846-->16777216
 47-->3355443248-->67108864
 49-->134217728 50-->268435456 
 51-->536870912 52-->1073741824

我们可以根据这张表,来总结一些规律:

  • 当容量小于512的时候,由于缓冲区已经比较小,需要降低步进值,每次扩容16
  • 当容量大于512时,说明需要解码的消息码流比较大,应该调大步进幅度来减少动态扩展的频率,所以采用512的倍数进行扩展

这里的扩容规则,跟我们前面提到的AbstratByteBuf扩容是完全不同的两个风格

  • AbstractByteBuf:容量小的时候,猛猛扩容。容量大的时候,每次扩容的大小是阈值,这个定值来扩容的,就是怕浪费扩容后的冗余空间。
  • AdaptiveRecvByteBufAllocator:流量小的时候,小小的扩容。流量大的时候,猛猛扩,害怕扩了之后短时间由继续扩。

主要是因为他们参考的依据不同:容量流量。并且后者支持缩容,就算猛猛扩了,后面流量变小了也会自己缩回去。

还有一点就是扩容错误的代价两者不同:

  • AbstractByteBuf扩容大了,顶多就是空间,资源的浪费,并不会严重地影响到应用程序对外提供服务
  • AdaptiveRecvByteBufAllocator在流量大的时候,应用程序承受的压力绝对是比普通时候要大,出故障率也更多。此时你还多搞这么一出,自己在那里频繁地慢吞吞扩容,占用资源。风险还是很大大。

总结

  • AbstractByteBuf 采取保守节约空间的策略,因为扩容扩到越后面,如果出现浪费空间的情况,那就是浪费一大块空间。
  • AdaptiveRecvByteBufAllocator 采取比较抵御流量洪水的策略,在流量大的时候,下一次扩大一点,避免频繁扩容造成资源的消耗。并且支持缩容,不必太担心空间上的问题。
AbstractByteBufAdaptiveRecvByteBufAllocator
扩容支持是✅支持✅
缩容不支持❌支持✅
参考指标容量流量
不正确扩容的代价浪费空间在大流量压力时,给应用程序雪上加霜

Netty:与AbstractByteBuf渐进式步进截然不同的扩容规则--AdaptiveRecvByteBufAllocator

来都来了,点个赞再走吧彦祖👍,这对我来说非常重要!