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

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

3.1 分配DirectByteBuffer接收网络数据


Sub Reactor在接收NioSocketChannel上的IO数据时,都会分配一个ByteBuffer用来存放接收到的IO数据。

 

这里大家可能觉得比较奇怪,为什么在NioSocketChannel接收数据这里会有两个ByteBuffer分配器呢?一个是ByteBufAllocator,另一个是RecvByteBufAllocator。

    final ByteBufAllocator allocator = config.getAllocator();
    final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();

这两个ByteBuffer又有什么区别和联系呢?

 

在上篇文章?《抓到Netty一个Bug,顺带来透彻地聊一下Netty是如何高效接收网络连接》中,笔者为了阐述上篇文章中提到的Netty在接收网络连接时的Bug时,简单和大家介绍了下这个RecvByteBufAllocator。

 

在上篇文章提到的NioServerSocketChannelConfig中,这里的RecvByteBufAllocator类型为ServerChannelRecvByteBufAllocator。Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 四-鸿蒙开发者社区

image.png


还记得这个ServerChannelRecvByteBufAllocator类型在4.1.69.final版本引入是为了解决笔者在上篇文章中提到的那个Bug吗?在4.1.69.final版本之前,NioServerSocketChannelConfig中的RecvByteBufAllocator类型为AdaptiveRecvByteBufAllocator。

 

而在本文中NioSocketChannelConfig中的RecvByteBufAllocator类型为AdaptiveRecvByteBufAllocator。Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 四-鸿蒙开发者社区

image.png


所以这里recvBufAllocHandle()获得到的RecvByteBufAllocator为AdaptiveRecvByteBufAllocator。顾名思义,这个类型的RecvByteBufAllocator可以根据NioSocketChannel上每次到来的IO数据大小来自适应动态调整ByteBuffer的容量。

 

对于客户端NioSocketChannel来说,它里边包含的IO数据时客户端发送来的网络数据,长度是不定的,所以才会需要这样一个可以根据每次IO数据的大小来自适应动态调整容量的ByteBuffer来接收。

 

如果我们把用于接收数据用的ByteBuffer看做一个桶的话,那么小数据用大桶装或者大数据用小桶装肯定是不合适的,所以我们需要根据接收数据的大小来动态调整桶的容量。而AdaptiveRecvByteBufAllocator的作用正是用来根据每次接收数据的容量大小来动态调整ByteBuffer的容量的。

 

现在RecvByteBufAllocator笔者为大家解释清楚了,接下来我们继续看ByteBufAllocator。

 

大家这里需要注意的是AdaptiveRecvByteBufAllocator并不会真正的去分配ByteBuffer,它只是负责动态调整分配ByteBuffer的大小。

 

而真正具体执行内存分配动作的是这里的ByteBufAllocator类型为PooledByteBufAllocator。它会根据AdaptiveRecvByteBufAllocator动态调整出来的大小去真正的申请内存分配ByteBuffer。

 

PooledByteBufAllocator为Netty中的内存池,用来管理堆外内存DirectByteBuffer。

 

AdaptiveRecvByteBufAllocator中的allocHandle在上篇文章中我们也介绍过了,它的实际类型为MaxMessageHandle。

public class AdaptiveRecvByteBufAllocator extends DefaultMaxMessagesRecvByteBufAllocator {

    @Override
    public Handle newHandle() {
        return new HandleImpl(minIndex, maxIndex, initial);
    }
    
    private final class HandleImpl extends MaxMessageHandle {
                  .................省略................
    }
}

在MaxMessageHandle中包含了用于动态调整ByteBuffer容量的统计指标。

public abstract class MaxMessageHandle implements ExtendedHandle {
        private ChannelConfig config;

        //用于控制每次read loop里最大可以循环读取的次数,默认为16次
        //可在启动配置类ServerBootstrap中通过ChannelOption.MAX_MESSAGES_PER_READ选项设置。
        private int maxMessagePerRead;

        //用于统计read loop中总共接收的连接个数,NioSocketChannel中表示读取数据的次数
        //每次read loop循环后会调用allocHandle.incMessagesRead增加记录接收到的连接个数
        private int totalMessages;

        //用于统计在read loop中总共接收到客户端连接上的数据大小
        private int totalBytesRead;

        //表示本次read loop 尝试读取多少字节,byteBuffer剩余可写的字节数
        private int attemptedBytesRead;

        //本次read loop读取到的字节数
        private int lastBytesRead;
        
        //预计下一次分配buffer的容量,初始:2048
        private int nextReceiveBufferSize;
        ...........省略.............
}

在每轮read loop开始之前,都会调用allocHandle.reset(config)重置清空上一轮read loop的统计指标。

@Override
        public void reset(ChannelConfig config) {
            this.config = config;
            //默认每次最多读取16次
            maxMessagePerRead = maxMessagesPerRead();
            totalMessages = totalBytesRead = 0;
        }

在每次开始从NioSocketChannel中读取数据之前,需要利用PooledByteBufAllocator在内存池中为ByteBuffer分配内存,默认初始化大小为2048,这个容量由guess()方法决定。   

 byteBuf = allocHandle.allocate(allocator);

     

 @Override
        public ByteBuf allocate(ByteBufAllocator alloc) {
            return alloc.ioBuffer(guess());
        }

        @Override
        public int guess() {
            //预计下一次分配buffer的容量,一开始为2048
            return nextReceiveBufferSize;
        }

在每次通过doReadBytes从NioSocketChannel中读取到数据后,都会调用allocHandle.lastBytesRead(doReadBytes(byteBuf))记录本次读取了多少字节数据,并统计本轮read loop目前总共读取了多少字节。

        @Override
        public void lastBytesRead(int bytes) {
            lastBytesRead = bytes;
            if (bytes > 0) {
                totalBytesRead += bytes;
            }
        }

每次循环从NioSocketChannel中读取数据之后,都会调用allocHandle.incMessagesRead(1)。统计当前已经读取了多少次。如果超过了最大读取限制此时16次,就需要退出read loop。去处理其他NioSocketChannel上的IO事件。

     @Override
        public final void incMessagesRead(int amt) {
            totalMessages += amt;
        }

在每次read loop循环的末尾都需要通过调用allocHandle.continueReading()来判断是否继续read loop循环读取NioSocketChannel中的数据。

 @Override
        public boolean continueReading() {
            return continueReading(defaultMaybeMoreSupplier);
        }

        private final UncheckedBooleanSupplier defaultMaybeMoreSupplier = new UncheckedBooleanSupplier() {
            @Override
            public boolean get() {
                //判断本次读取byteBuffer是否满载而归
                return attemptedBytesRead == lastBytesRead;
            }
        };

        @Override
        public boolean continueReading(UncheckedBooleanSupplier maybeMoreDataSupplier) {
            return config.isAutoRead() &&
                   (!respectMaybeMoreData || maybeMoreDataSupplier.get()) &&
                   totalMessages < maxMessagePerRead &&
                   totalBytesRead > 0;
        }

 • attemptedBytesRead :表示当前ByteBuffer预计尝试要写入的字节数。

 

 • lastBytesRead :表示本次read loop真实读取到了多少个字节。

 

defaultMaybeMoreSupplier用于判断经过本次read loop读取数据后,ByteBuffer是否满载而归。如果是满载而归的话(attemptedBytesRead == lastBytesRead),表明可能NioSocketChannel里还有数据。如果不是满载而归,表明NioSocketChannel里没有数据了已经。

 

是否继续进行read loop需要同时满足以下几个条件:

 

 • totalMessages < maxMessagePerRead 当前读取次数是否已经超过16次,如果超过,就退出do(...)while()循环。进行下一轮OP_READ事件的轮询。因为每个Sub Reactor管理了多个NioSocketChannel,不能在一个NioSocketChannel上占用太多时间,要将机会均匀地分配给Sub Reactor所管理的所有NioSocketChannel。

 

totalBytesRead > 0 本次OP_READ事件处理是否读取到了数据,如果已经没有数据可读了,那么就直接退出read loop。

 

!respectMaybeMoreData || maybeMoreDataSupplier.get() 这个条件比较复杂,它其实就是通过respectMaybeMoreData字段来控制NioSocketChannel中可能还有数据可读的情况下该如何处理。

 

    ◆maybeMoreDataSupplier.get():true表示本次读取从NioSocketChannel中读取数据,ByteBuffer满载而归。说明可能NioSocketChannel中还有数据没读完。fasle表示ByteBuffer还没有装满,说明NioSocketChannel中已经没有数据可读了。
   

    ◆respectMaybeMoreData = true表示要对可能还有更多数据进行处理的这种情况要respect认真对待,如果本次循环读取到的数据已经装满ByteBuffer,表示后面可能还有数据,那么就要进行读取。如果ByteBuffer还没装满表示已经没有数据可读了那么就退出循环。 

 

    ◆respectMaybeMoreData = false表示对可能还有更多数据的这种情况不认真对待 not respect。不管本次循环读取数据ByteBuffer是否满载而归,都要继续进行读取,直到读取不到数据在退出循环,属于无脑读取。

 

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