Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 六

r660926
发布于 2022-7-28 17:52
浏览
0收藏

4.3 扩缩容逻辑


现在扩缩容索引表SIZE_TABLE已经初始化完毕了,那么当我们需要对ByteBuffer进行扩容或者缩容的时候如何根据SIZE_TABLE决定扩容多少或者缩容多少呢??

 

这就用到了在AdaptiveRecvByteBufAllocator类中定义的扩容步长INDEX_INCREMENT = 4,缩容步长INDEX_DECREMENT = 1了。

 

我们就以上面两副扩缩容容量索引表SIZE_TABLE中的容量索引展示截图为例,来介绍下扩缩容逻辑,假设我们当前ByteBuffer的容量索引为33,对应的容量为2048

 

4.3.1 扩容


当对容量为2048的ByteBuffer进行扩容时,根据当前的容量索引index = 33 加上 扩容步长INDEX_INCREMENT = 4计算出扩容后的容量索引为37,那么扩缩容索引表SIZE_TABLE下标37对应的容量就是本次ByteBuffer扩容后的容量SIZE_TABLE[37] = 32768

 

4.3.1  缩容


同理对容量为2048的ByteBuffer进行缩容时,我们就需要用当前容量索引index = 33 减去 缩容步长INDEX_DECREMENT = 1计算出缩容后的容量索引32,那么扩缩容索引表SIZE_TABLE下标32对应的容量就是本次ByteBuffer缩容后的容量SIZE_TABLE[32] = 1024

 

4.4 扩缩容时机

public abstract class AbstractNioByteChannel extends AbstractNioChannel {
        @Override
        public final void read() {
            .........省略......
            try {
                do {
                      .........省略......
                } while (allocHandle.continueReading());

                //根据本次read loop总共读取的字节数,决定下次是否扩容或者缩容
                allocHandle.readComplete();

                .........省略.........

            } catch (Throwable t) {
                ...............省略...............
            } finally {
               ...............省略...............
            }
        }
}

在每轮read loop结束之后,我们都会调用allocHandle.readComplete()来根据在allocHandle中统计的在本轮read loop中读取字节总大小,来决定在下一轮read loop中是否对DirectByteBuffer进行扩容或者缩容。

public abstract class MaxMessageHandle implements ExtendedHandle {

       @Override
       public void readComplete() {
                //是否对recvbuf进行扩容缩容
                record(totalBytesRead());
       }

       private void record(int actualReadBytes) {
            if (actualReadBytes <= SIZE_TABLE[max(0, index - INDEX_DECREMENT)]) {
                if (decreaseNow) {
                    index = max(index - INDEX_DECREMENT, minIndex);
                    nextReceiveBufferSize = SIZE_TABLE[index];
                    decreaseNow = false;
                } else {
                    decreaseNow = true;
                }
            } else if (actualReadBytes >= nextReceiveBufferSize) {
                index = min(index + INDEX_INCREMENT, maxIndex);
                nextReceiveBufferSize = SIZE_TABLE[index];
                decreaseNow = false;
            }
        }        
}

我们以当前ByteBuffer容量为2048,容量索引index = 33为例,对allocHandle的扩容缩容规则进行说明。

 

扩容步长INDEX_INCREMENT = 4,缩容步长INDEX_DECREMENT = 1

Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 六-鸿蒙开发者社区 image.png


4.4.1 缩容


•如果本次OP_READ事件实际读取到的总字节数actualReadBytes在SIZE_TABLE[index - INDEX_DECREMENT]与SIZE_TABLE[index]之间的话,也就是如果本轮read loop结束之后总共读取的字节数在[1024,2048]之间。说明此时分配的ByteBuffer容量正好,不需要进行缩容也不需要进行扩容。比如本次actualReadBytes = 2000,正好处在10242048之间。说明2048的容量正好。


•如果actualReadBytes 小于等于 SIZE_TABLE[index - INDEX_DECREMENT],也就是如果本轮read loop结束之后总共读取的字节数小于等于1024。表示本次读取到的字节数比当前ByteBuffer容量的下一级容量还要小,说明当前ByteBuffer的容量分配的有些大了,设置缩容标识decreaseNow = true。当下次OP_READ事件继续满足缩容条件的时候,开始真正的进行缩容。缩容后的容量为SIZE_TABLE[index - INDEX_DECREMENT],但不能小于SIZE_TABLE[minIndex]。

 

注意需要满足两次缩容条件才会进行缩容,且缩容步长为1,缩容比较谨慎


4.4.2 扩容


如果本次OP_READ事件处理总共读取的字节数actualReadBytes 大于等于 当前ByteBuffer容量(nextReceiveBufferSize)时,说明ByteBuffer分配的容量有点小了,需要进行扩容。扩容后的容量为SIZE_TABLE[index + INDEX_INCREMENT],但不能超过SIZE_TABLE[maxIndex]。

 

满足一次扩容条件就进行扩容,并且扩容步长为4, 扩容比较奔放

 

4.5 AdaptiveRecvByteBufAllocator类的实例化


AdaptiveRecvByteBufAllocator类的实例化主要是确定ByteBuffer的初始容量,以及最小容量和最大容量在扩缩容索引表SIZE_TABLE中的下标:minIndexmaxIndex

 

AdaptiveRecvByteBufAllocator定义了三个关于ByteBuffer容量的字段:

 

DEFAULT_MINIMUM :表示ByteBuffer最小的容量,默认为64,也就是无论ByteBuffer在怎么缩容,容量也不会低于64

 

DEFAULT_INITIAL:表示ByteBuffer的初始化容量。默认为2048

 

DEFAULT_MAXIMUM :表示ByteBuffer的最大容量,默认为65536,也就是无论ByteBuffer在怎么扩容,容量也不会超过65536

public class AdaptiveRecvByteBufAllocator extends DefaultMaxMessagesRecvByteBufAllocator {

    static final int DEFAULT_MINIMUM = 64;
    static final int DEFAULT_INITIAL = 2048;
    static final int DEFAULT_MAXIMUM = 65536;

    public AdaptiveRecvByteBufAllocator() {
        this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);
    }

    public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {
       
         .................省略异常检查逻辑.............

        //计算minIndex maxIndex
        //在SIZE_TABLE中二分查找最小 >= minimum的容量索引 :3
        int minIndex = getSizeTableIndex(minimum);
        if (SIZE_TABLE[minIndex] < minimum) {
            this.minIndex = minIndex + 1;
        } else {
            this.minIndex = minIndex;
        }

        //在SIZE_TABLE中二分查找最大 <= maximum的容量索引 :38
        int maxIndex = getSizeTableIndex(maximum);
        if (SIZE_TABLE[maxIndex] > maximum) {
            this.maxIndex = maxIndex - 1;
        } else {
            this.maxIndex = maxIndex;
        }

        this.initial = initial;
    }
}

接下来的事情就是确定最小容量DEFAULT_MINIMUM 在SIZE_TABLE中的下标minIndex,以及最大容量DEFAULT_MAXIMUM 在SIZE_TABLE中的下标maxIndex

 

从AdaptiveRecvByteBufAllocator类初始化的过程中,我们可以看出SIZE_TABLE中存储的数据特征是一个有序的集合。

 

我们可以通过二分查找在SIZE_TABLE中找出第一个容量大于等于DEFAULT_MINIMUM的容量索引minIndex

 

同理通过二分查找在SIZE_TABLE中找出最后一个容量小于等于DEFAULT_MAXIMUM的容量索引maxIndex

 

根据上一小节关于SIZE_TABLE中容量数据分布的截图,我们可以看出minIndex = 3maxIndex = 38

 

4.5.1 二分查找容量索引下标
 

 private static int getSizeTableIndex(final int size) {
        for (int low = 0, high = SIZE_TABLE.length - 1;;) {
            if (high < low) {
                return low;
            }
            if (high == low) {
                return high;
            }

            int mid = low + high >>> 1;//无符号右移,高位始终补0
            int a = SIZE_TABLE[mid];
            int b = SIZE_TABLE[mid + 1];
            if (size > b) {
                low = mid + 1;
            } else if (size < a) {
                high = mid - 1;
            } else if (size == a) {
                return mid;
            } else {
                return mid + 1;
            }
        }
    }

经常刷LeetCode的小伙伴肯定一眼就看出这个是二分查找的模板了。


它的目的就是根据给定容量,在扩缩容索引表SIZE_TABLE中,通过二分查找找到最贴近给定size的容量的索引下标(第一个大于等于 size的容量)

 

4.6 RecvByteBufAllocator.Handle


前边我们提到最终动态调整ByteBuffer容量的是由AdaptiveRecvByteBufAllocator中的Handler负责的,我们来看下这个allocHandle的创建过程。

protected abstract class AbstractUnsafe implements Unsafe {

        private RecvByteBufAllocator.Handle recvHandle;

        @Override
        public RecvByteBufAllocator.Handle recvBufAllocHandle() {
            if (recvHandle == null) {
                recvHandle = config().getRecvByteBufAllocator().newHandle();
            }
            return recvHandle;
        }

}

从allocHandle的获取过程我们看到最allocHandle的创建是由AdaptiveRecvByteBufAllocator#newHandle方法执行的。

public class AdaptiveRecvByteBufAllocator extends DefaultMaxMessagesRecvByteBufAllocator {

    @Override
    public Handle newHandle() {
        return new HandleImpl(minIndex, maxIndex, initial);
    }

    private final class HandleImpl extends MaxMessageHandle {
        //最小容量在扩缩容索引表中的index
        private final int minIndex;
        //最大容量在扩缩容索引表中的index
        private final int maxIndex;
        //当前容量在扩缩容索引表中的index 初始33 对应容量2048
        private int index;
        //预计下一次分配buffer的容量,初始:2048
        private int nextReceiveBufferSize;
        //是否缩容
        private boolean decreaseNow;

        HandleImpl(int minIndex, int maxIndex, int initial) {
            this.minIndex = minIndex;
            this.maxIndex = maxIndex;

            //在扩缩容索引表中二分查找到最小大于等于initial 的容量
            index = getSizeTableIndex(initial);
            //2048
            nextReceiveBufferSize = SIZE_TABLE[index];
        }

        .......................省略...................
    }

}

这里我们看到Netty中用于动态调整ByteBuffer容量的allocHandle的实际类型为MaxMessageHandle

 

下面我们来介绍下HandleImpl中的核心字段,它们都和ByteBuffer的容量有关:

 

minIndex :最小容量在扩缩容索引表SIZE_TABE中的index。默认是3


maxIndex :最大容量在扩缩容索引表SIZE_TABE中的index。默认是38


index :当前容量在扩缩容索引表SIZE_TABE中的index。初始是33


nextReceiveBufferSize :预计下一次分配buffer的容量,初始为2048。在每次申请内存分配ByteBuffer的时候,采用nextReceiveBufferSize的值指定容量。

 

decreaseNow : 是否需要进行缩容。

标签
已于2022-7-28 17:52:34修改
收藏
回复
举报
回复
    相关推荐