Netty - 内存大小预测器 RecvByteBufAllocator 源码分析 (包含客户端Channel读消息处理)

Netty - 内存大小预测器 RecvByteBufAllocator 源码分析 (包含客户端Channel读消息处理)

前言

我们知道 Netty 中 对消息的处理 都需要申请内存,而这内存默认是 堆外内存 ,为了增加内存的使用率,减少申请内存的不必要的消耗,诞生出了RecvByteBufAllocator( 内存分配大小预测器 ) 。它可以计算预测出下一次申请 的 byteBuf 的容量大小, 弹性伸缩下次分配 byteBuf 的容量大小。

看到这是不是很神奇,居然可以弹性伸缩 byteBuf的容量。 那么我们就来详细分析下,具体是怎样实现的该需求。

在创建 NioServerSocketChannelNioSocketChannel 时, 会创建 一个config 对象

config 对象有三个主要目的:

1. 对外暴露 Channel 中的属性
2. 创建 内存分配大小预测器  
3. 内存分配器

其中 内存分配大小预测器 是我们本篇文章要解析的。

    public DefaultChannelConfig(Channel channel) {
        this(channel, new AdaptiveRecvByteBufAllocator());
    }

DefaultChannelConfig 的构造方法可知, 它会创建一个 AdaptiveRecvByteBufAllocator ,该对象就是内存分配大小预测器。 那么下面我们就来以该AdaptiveRecvByteBufAllocator 类 为入口进行分析。

1.继承体系

先来看下 AdaptiveRecvByteBufAllocator 的继承体系图.

如图,该继承体系很清晰明了,实现了两个接口 和一个抽象类。

其中 MaxMessagesRecvByteBufAllocatorRecvByteBufAllocator 做了方法的增强

DefaultMaxMessagesRecvByteBufAllocator 是个抽象类,实现了部分的方法。

2.RecvByteBufAllocator

首先来看下 第一个接口 RecvByteBufAllocator 的内部方法作用。

public interface RecvByteBufAllocator {

    //  创建 Handler 内存大小预测核心对象
    Handle newHandle();

   
    //  Handler 内存大小预测的核心接口类   核心的方法由该类来提供
    @Deprecated
    interface Handle {
      	
       
        // 方法作用: 内存分配器 根据预测的内存大小 来创建 byteBuf
        ByteBuf allocate(ByteBufAllocator alloc);  // 参数 alloc: 内存分配器

       
        // 方法作用:预测内存的大小
        int guess();

     	// 方法作用: 重置 内存大小预测器的属性
        void reset(ChannelConfig config);

     	// 方法作用:  增加消息读取的数量
        void incMessagesRead(int numMessages);
		
        // 方法作用:  设置最后一次读取的消息大小
        void lastBytesRead(int bytes);

       	// 方法作用:  获取最后一次读取的消息大小
        int lastBytesRead();
		
        // 方法作用:  设置 预测的内存大小
        void attemptedBytesRead(int bytes);
        
		// 方法作用:  获取 预测的内存大小
        int attemptedBytesRead();
		
        // 方法作用: 判断是否可以继续 读循环
        boolean continueReading();

     	// 方法作用: 本次读循环完毕
        void readComplete();
    }
	
    // 对Handler接口类 continueReading方法 的增强
    @SuppressWarnings("deprecation")
    @UnstableApi
    interface ExtendedHandle extends Handle {
        // 方法作用: 判断是否可以继续 读循环
        boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier);
    }

   	
    // 代理模式 (本篇文章用不到) 
    class DelegatingHandle implements Handle {
        private final Handle delegate;

        public DelegatingHandle(Handle delegate) {
            this.delegate = checkNotNull(delegate, "delegate");
        }

        protected final Handle delegate() {
            return delegate;
        }

       	// ..... 省略
    }
}

从上述RecvByteBufAllocator 接口的代码中可知:

  1. RecvByteBufAllocator 中仅有一个方法 newHandle() 创建 Handle对象(真正内存预测器核心逻辑对象)
  2. 内部接口Handle ,它是 真正的 内存预测器 的核心逻辑 接口类
  3. 内部接口ExtendedHandle 继承了Handle, 对Handle 的增强

3.MaxMessagesRecvByteBufAllocator

public interface MaxMessagesRecvByteBufAllocator extends RecvByteBufAllocator {

    // 获取每次读循环操作 最大能读取的消息数量 (每到Channel内 读一次数据,称为一个消息)
    int maxMessagesPerRead();

    // 设置  每次读循环操作  最大能读取的消息数量
    MaxMessagesRecvByteBufAllocator maxMessagesPerRead(int maxMessagesPerRead);
}

4.DefaultMaxMessagesRecvByteBufAllocator

public abstract class DefaultMaxMessagesRecvByteBufAllocator implements MaxMessagesRecvByteBufAllocator {

    // 每次读循环操作 最大能读取的消息数量
    private volatile int maxMessagesPerRead;
    
    // 是否期望要读取更多的消息  默认是 true 
    private volatile boolean respectMaybeMoreData = true;
	
    public DefaultMaxMessagesRecvByteBufAllocator() {
        this(1);
    }
	
    // 参数 maxMessagesPerRead: 每次都循环的 可读取的最大消息数量
    public DefaultMaxMessagesRecvByteBufAllocator(int maxMessagesPerRead) {
        maxMessagesPerRead(maxMessagesPerRead);
    }
	
    // 获取每次都循环 读取的最大消息数量
    @Override
    public int maxMessagesPerRead() {
        return maxMessagesPerRead;
    }

    @Override
    public MaxMessagesRecvByteBufAllocator maxMessagesPerRead(int maxMessagesPerRead) {
        checkPositive(maxMessagesPerRead, "maxMessagesPerRead");
        this.maxMessagesPerRead = maxMessagesPerRead;
        return this;
    }
	
    // 设置 是否期望要读取更多的消息
    public DefaultMaxMessagesRecvByteBufAllocator respectMaybeMoreData(boolean respectMaybeMoreData) {
        this.respectMaybeMoreData = respectMaybeMoreData;
        return this;
    }
	
    // 获取 是否期望要读取更多的消息
    public final boolean respectMaybeMoreData() {
        return respectMaybeMoreData;
    }
	
    
    // 内存预测器核心类
    public abstract class MaxMessageHandle implements ExtendedHandle {

        // 所属Channel中的config对象 
        private ChannelConfig config;

        // 每次读循环操作 最大能读取的消息数量 (每到ch内拉一次数据 称为一个消息)
        private int maxMessagePerRead;

        // 已经读的消息数量
        private int totalMessages;

        // 已经读的消息 size字节数
        private int totalBytesRead;

        // 预估下次读的size字节数
        private int attemptedBytesRead;

        // 最后一次读的size字节数
        private int lastBytesRead;

        //true
        private final boolean respectMaybeMoreData = DefaultMaxMessagesRecvByteBufAllocator.this.respectMaybeMoreData;

		// 用来判断是否继续读取的方法
        private final UncheckedBooleanSupplier defaultMaybeMoreSupplier = new UncheckedBooleanSupplier() {
            
            
            // 判断  预测读取量 == 最后一次读取量(本次读取量)
            // true:  说明 预测读取量 和 读取数据量 一致, Channel中可能 仍存剩余数据未读完.
            // false:  说明 Channel中得数据读取完毕 或者 Channel 处于close状态
            @Override
            public boolean get() {
                return attemptedBytesRead == lastBytesRead;
            }
        };

		
        // 重置 内存大小预测器 ,将变量置为默认值
        @Override
        public void reset(ChannelConfig config) {
            this.config = config;
            maxMessagePerRead = maxMessagesPerRead();
            totalMessages = totalBytesRead = 0;
        }

    	
        // 根据 内存预测大小 申请 byteBuf内存
        @Override
        public ByteBuf allocate(ByteBufAllocator alloc) {
            return alloc.ioBuffer(guess());
        }
		
        // 增加总消息读取量
        @Override
        public final void incMessagesRead(int amt) {
            totalMessages += amt;
        }
		
        // 设置 最后一次读取消息size大小 并增加总读取消息size大小
        @Override
        public void lastBytesRead(int bytes) {
            lastBytesRead = bytes;
            if (bytes > 0) {
                totalBytesRead += bytes;
            }
        }
		
        // 获取 最后一次读取消息size大小
        @Override
        public final int lastBytesRead() {
            return lastBytesRead;
        }
		
        // 是否继续读取
        @Override
        public boolean continueReading() {
            return continueReading(defaultMaybeMoreSupplier);
        }
		
         // 是否继续读取
        @Override
        public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
			
            // 当返回 true 需要 四个条件都为 true:
            // 1. config.isAutoRead() 默认为true
            // 2. maybeMoreDataSupplier.get() 当Channel中可能存在剩余未读消息时为 true
            // 3. totalMessages < maxMessagePerRead  当总读取消息量 < 最大读取消息量时为true
            // 4. totalBytesRead > 0 
            //     1. 服务端  服务端总为 false
            //     2. 客户端  true: 客户端正常读取到数据得情况下会成立
            //               false: 当客户端读取到得数据量 > Integer.Max时
            return config.isAutoRead() &&
                   (!respectMaybeMoreData || maybeMoreDataSupplier.get()) &&
                   totalMessages < maxMessagePerRead &&
                   totalBytesRead > 0;
        }

		
        @Override
        public void readComplete() {
        }
		
        // 获取 预测内存大小
        @Override
        public int attemptedBytesRead() {
            return attemptedBytesRead;
        }
		
        // 设置 预测内存大小
        @Override
        public void attemptedBytesRead(int bytes) {
            attemptedBytesRead = bytes;
        }
		
        // 获取 总读取消息size大小
        protected final int totalBytesRead() {
            return totalBytesRead < 0 ? Integer.MAX_VALUE : totalBytesRead;
        }
    }
}

上述代码虽然很长,但是每个方法得功能都言简意赅, 重点关注注释上得内容。

5.AdaptiveRecvByteBufAllocator

public class AdaptiveRecvByteBufAllocator extends DefaultMaxMessagesRecvByteBufAllocator {

    static final int DEFAULT_MINIMUM = 64;
    // Use an initial value that is bigger than the common MTU of 1500
    static final int DEFAULT_INITIAL = 2048;
    static final int DEFAULT_MAXIMUM = 65536;

    // 索引增量 4
    private static final int INDEX_INCREMENT = 4;

    // 索引减量 1
    private static final int INDEX_DECREMENT = 1;

    // size table
    private static final int[] SIZE_TABLE;

    static {
        /**
         * 向sizeTable 添加 [16,32,48,64,....,496]
         */
        List<Integer> sizeTable = new ArrayList<Integer>();
        for (int i = 16; i < 512; i += 16) {
            sizeTable.add(i);
        }

        /**
         * 继续向sizeTable 添加 [16,32,48,64,....,496,512,1024,2048,4096,...Integer.Max] 直到int溢出
         */
        // Suppress a warning since i becomes negative when an integer overflow happens
        for (int i = 512; i > 0; i <<= 1) { // lgtm[java/constant-comparison]
            sizeTable.add(i);
        }

        /**
         * SIZE_TABLE = sizeTable [16,32,48,64,....,496,512,1024,2048,4096,...Integer.Max]
         *
         * 数组赋值
         */
        SIZE_TABLE = new int[sizeTable.size()];
        for (int i = 0; i < SIZE_TABLE.length; i ++) {
            SIZE_TABLE[i] = sizeTable.get(i);
        }
    }

	//  饱汉 单例模式  
    @Deprecated
    public static final AdaptiveRecvByteBufAllocator DEFAULT = new AdaptiveRecvByteBufAllocator();
	
    // 二分查找法
    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;
            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;
            }
        }
    }

    private final class HandleImpl extends MaxMessageHandle {
        private final int minIndex;
        private final int maxIndex;
        private int index;

        // 下一次预分配的容器大小
        private int nextReceiveBufferSize;

        // 是否缩小 容器大小
        private boolean decreaseNow;

        // 参数1: 64在 SIZE_TABLE 的下标
        // 参数2: 65536 在SIZE_TABLE的下标
        // 参数3: 1024
        HandleImpl(int minIndex, int maxIndex, int initial) {
            this.minIndex = minIndex;
            this.maxIndex = maxIndex;

            // 计算出来 size 1024 在SIZE_TABLE的 下标
            index = getSizeTableIndex(initial);

            // nextReceiveBufferSize 表示下一次 分配出来的 byteBuf 容量大小
            nextReceiveBufferSize = SIZE_TABLE[index];
        }

        @Override
        public void lastBytesRead(int bytes) {
           
            // 条件成立: 说明 读取的数据量和评估的数据量一致。 说明ch内可能还有数据未读取完.. 还需要继续
            if (bytes == attemptedBytesRead()) {

                // 这个方法想要更新 nextReceiveBufferSize的大小, 因为前面评估的量被读满了,可能意味着ch内有很多数据,需要更大的容器来读
                record(bytes);
            }
            super.lastBytesRead(bytes);
        }

        @Override
        public int guess() {
            return nextReceiveBufferSize;
        }

        // 参数: 本次从ch内 真实读取的数据量
        private void record(int actualReadBytes) {

            //举个例子:
            // 假设 SIZE_TABLE[index] = 512 => SIZE_TABLE[index-1] = 496
            // 如果本次读取的数据量 <= 496 ,说明ch的缓冲区 数据不是很多, 可能不需要那么大的 ByteBuf
            // 如果第二次的数据量 还是 <=496 ,那么就确定不需要那么大的 ByteBuf了
            if (actualReadBytes <= SIZE_TABLE[max(0, index - INDEX_DECREMENT)]) {

                if (decreaseNow) {
                    // 初始阶段 定义过: 最小 不能 TABLE_SIZE[minIndex]
                    index = max(index - INDEX_DECREMENT, minIndex);

                    // 获取相对减小的 BufferSize值 赋值给 nextReceiveBufferSize
                    nextReceiveBufferSize = SIZE_TABLE[index];
                    decreaseNow = false;
                } else {

                    // 第一次 设置成 true
                    decreaseNow = true;
                }
            } // 条件成立: 说明 ByteBuf容器 已经装满了.. 说明ch内还有很多数据,所以这里让Index 右移一位,得到更大的容器
            else if (actualReadBytes >= nextReceiveBufferSize) {
                index = min(index + INDEX_INCREMENT, maxIndex);
                nextReceiveBufferSize = SIZE_TABLE[index];
                decreaseNow = false;
            }
        }

        @Override
        public void readComplete() {
            record(totalBytesRead());
        }
    }

    private final int minIndex;
    private final int maxIndex;
    private final int initial;


    public AdaptiveRecvByteBufAllocator() {

        //64
        //2048
        //65536
        // 赋值 minIndex  maxIndex initial
        this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);
    }

 

    //64
    //2048
    //65536
    public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {
        checkPositive(minimum, "minimum");
        if (initial < minimum) {
            throw new IllegalArgumentException("initial: " + initial);
        }
        if (maximum < initial) {
            throw new IllegalArgumentException("maximum: " + maximum);
        }
        // sizeTable [16,32,48,64,....,496,512,1024,2048,4096,...Integer.Max]


        // 使用二分查找算法 获取 mininum size在数组内的下标
        int minIndex = getSizeTableIndex(minimum);


        if (SIZE_TABLE[minIndex] < minimum) {
            //因为不能小于minimum  所以这里右移index    确保 SIZE_TABLE[minIndex] >= minimum 值
            this.minIndex = minIndex + 1;
        } else {
            this.minIndex = minIndex;
        }

        // 使用二分查找算法 获取 maximum size在数组内的下标
        int maxIndex = getSizeTableIndex(maximum);

        //确保 SIZE_TABLE[maxIndex] <= maximum
        if (SIZE_TABLE[maxIndex] > maximum) {
            // 因为不能超出 maximum值, 所以这里左移 index
            this.maxIndex = maxIndex - 1;
        } else {
            this.maxIndex = maxIndex;
        }
        // 初始值 1024
        this.initial = initial;
    }

    @SuppressWarnings("deprecation")
    @Override
    public Handle newHandle() {

        // 参数1: 64在 SIZE_TABLE 的下标
        // 参数2: 65536 在SIZE_TABLE的下标
        // 参数3: 1024
        return new HandleImpl(minIndex, maxIndex, initial);
    }

    @Override
    public AdaptiveRecvByteBufAllocator respectMaybeMoreData(boolean respectMaybeMoreData) {
        super.respectMaybeMoreData(respectMaybeMoreData);
        return this;
    }
}

具体的方法功能,注释上解释的很清楚, 下面我们根据具体得内存预测流程,来再次熟悉上面方法得功能。

6.内存预测流程

这里我们以 客户端 Channel 读消息处理为例 ,看下具体是如何进行内存预测的。

废话不多说,直接上代码:

        // 客户端NioSocketChannel read()
        @Override
        public final void read() {
			// 获取 客户端Channel 的config对象
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
    		// 获取 客户端Channel 的 pipeline对象
            final ChannelPipeline pipeline = pipeline();
			
            // 获取 内存分配器
            final ByteBufAllocator allocator = config.getAllocator();
			
            // 获取 内存大小预测器
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
 			
            // 重置 内存大小预测器
            allocHandle.reset(config);

           
            // 读取的消息对象引用
            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do { 
                    
                    // 按照预测的内存大小 申请 byteBuf内存
                    byteBuf = allocHandle.allocate(allocator);
					
                    // doReadBytes(byteBuf) 从Channel中读取消息
                    // 设置本次读取消息的size大小
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                	
                    // 条件成立: 本次读取消息的size <= 0 
                    // 1. size = 0  本次为读到数据
                    // 2. size < 0  Channel为 close状态,则会返回 -1
                    if (allocHandle.lastBytesRead() <= 0) {
                        // 释放byteBuf
                        byteBuf.release();
                        // GC help
                        byteBuf = null;
                        // 若 lastBytesRead < 0  则说明 Channel为close状态	
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                         
                            readPending = false;
                        }
                        break;
                    }
                    // 总消息的读取量 + 1
                    allocHandle.incMessagesRead(1);
                    readPending = false;
                    
                    // 向 pipeline 传播 读到的消息 (也就是 处理消息 了)
                    pipeline.fireChannelRead(byteBuf);
                    
                    // GC help
                    byteBuf = null;
                    
                }
                // 判断是否 需要继续从Channel读数据
                while (allocHandle.continueReading());
				
                allocHandle.readComplete();

                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }
    }
  1. byteBuf = allocHandle.allocate(allocator) : 通过guess() 预测内存大小 (首次预测大小默认是 1024), 并申请对应大小的内存

  2. allocHandle.lastBytesRead(doReadBytes(byteBuf)) : 设置 lastBytesRead 值 为本次从Channel中实际读取到的数据量大小, 此时内部会根据本次实际读取的数据量大小, 调用 record() 方法 来动态调整下一次预测的内存申请的大小值。

  3. allocHandle.incMessagesRead(1) : 已读消息总量 加1

  4. allocHandle.continueReading() : 判断是否继续进行读循环

若要继续读循环,主要根据下 预测内存大小 与 本次读取数据大小来决定:

  1. 预测的内存大小 与 本次读的数据大小 一致。 说明预测的内存读满了,可能Channel内还有剩余的数据未读。

若不继续都循环,有以下几点原因:

  1. Channel close状态 关闭了
  2. 本次读取数据量大小 < 内存预测的大小 ,说明channel 中的数据已经被读取完毕了
  3. 本次读取数据量大小 超过了 Integer.Max 值, 变为了 负值。

7. 总结

总的来说, 内存大小预测器, 主要就是根据 本次读取的数据量大小 与 内存预测大小 的值来 弹性伸缩下一次内存预测的大小。 并分别根据 这两者的值,来决定 是否需要继续循环从Channel 中读取数据。

posted @ 2022-02-16 01:58  s686编程传  阅读(383)  评论(0编辑  收藏  举报