• 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 中读取数据。

  • 相关阅读:
    LETTers比赛第十二场解题报告
    LETTers练习赛第十场 第四题
    LETTers练习赛第十场 第二题
    LETTers练习赛第十场 第一题
    LETTers练习赛十一场解题报告
    RedHat Enterprise Linux 基本网络配置
    c# 连接MySQL中文乱码问题的解决方法
    [原]iBatis.Net(C#)系列一:简介及运行环境
    ArcGIS 9.3 sp1 下载更新
    [原]iBatis.Net(C#)系列二:SQL数据映射
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15898798.html
Copyright © 2020-2023  润新知