netty零拷贝之CompositeByteBuf

目录

        一、 背景简介

        二、 netty零拷贝


一、背景简介

在TCP网络数据传输过程中,数据包有可能被分割为独立的几个数据包进行发送,对于服务器接收端来说,单个的数据包是没有任何意义的,只有将这些数据包组合到一起,才能接收后解码然后提供给我们的业务层进行处理,而在数据报价进行整合的过程中,零拷贝做的工作就是避免或者减少内存区域中数据的复制次数,进而提高程序的响应性能。

二、 netty零拷贝

netty对于零拷贝有几种不同的形式,其本质上都是讲两个或者多个ByteBuf逻辑上组合到一起,物理上不一定是连续的,在操作数据时候, 不必再将一个ByteBuf拷贝到另一个ByteBuf中去. 因此少了内存的拷贝。而且netty的ByteBuf 支持 slice 操作, 可以将 ByteBuf 分解为多个 ByteBuf, 这些ByteBuf共享同一个内存区域,这样也就避免了内存的拷贝。

CompositeByteBuf 零拷贝:

CompositeByteBuf字面意思就是组合ByteBuf,他会把多个ByteBuf组合到一起,这些ByteBuf在逻辑上连续,实际物理地址不一定连续。

CompositeByteBuf中主要通过addComponent方法进行ByteBuf的合并。

进入到addComponent方法

    public CompositeByteBuf addComponents(boolean increaseWriterIndex, ByteBuf... buffers) {
        addComponents0(increaseWriterIndex, components.size(), buffers, 0, buffers.length);
        consolidateIfNeeded();
        return this;
    }

核心逻辑就是addComponent0方法,consolidateIfNeeded()仅仅是判断合并组件数是否超过当前操作允许的最大值,如果超过最大值,就会进行扩容操作,如下:

    private void consolidateIfNeeded() {
        // 合并组件数是否超过当前操作允许的最大值。
        final int numComponents = components.size();
        if (numComponents > maxNumComponents) {
            final int capacity = components.get(numComponents - 1).endOffset;
​
            ByteBuf consolidated = allocBuffer(capacity);
​
            // 我们没有使用foreach来避免创建迭代器。
            for (int i = 0; i < numComponents; i ++) {
                Component c = components.get(i);
                ByteBuf b = c.buf;
                consolidated.writeBytes(b);
                c.freeIfNecessary();
            }
            Component c = new Component(consolidated);
            c.endOffset = c.length;
            components.clear();
            components.add(c);
        }
    }

其扩容逻辑并不复杂。

继续来到addComponent0方法。

    private int addComponents0(boolean increaseWriterIndex, int cIndex, ByteBuf[] buffers, int offset, int len) {
        checkNotNull(buffers, "buffers");
        int i = offset;
        try {
            // 校验Components index
            checkComponentIndex(cIndex);
            // 无需合并
            while (i < len) {
                // Increment i now to prepare for the next iteration and prevent a duplicate release (addComponent0
                // will release if an exception occurs, and we also release in the finally block here).
                ByteBuf b = buffers[i++];
                if (b == null) {
                    break;
                }
                cIndex = addComponent0(increaseWriterIndex, cIndex, b) + 1;
                int size = components.size();
                if (cIndex > size) {
                    cIndex = size;
                }
            }
            return cIndex;
        } finally {
            for (; i < len; ++i) {
                ByteBuf b = buffers[i];
                if (b != null) {
                    try {
                        b.release();
                    } catch (Throwable ignored) {
                        // 安静的释放ByteBuf
                    }
                }
            }
        }
    }

其核心逻辑就是通过while循环依次遍历每一个ByteBuf然后调用重载方法addComponent0进行添加每一个ByteBuf,最后将每一个ByteBuf进行内存释放。

接着进入重载方法addComponent0看看是如何添加每一个ByteBuf的。

    private int addComponent0(boolean increaseWriterIndex, int cIndex, ByteBuf buffer) {
        assert buffer != null;
        boolean wasAdded = false;
        try {
            checkComponentIndex(cIndex);
​
            int readableBytes = buffer.readableBytes();
​
            // 无需合并-只需将组件添加到列表中.
            @SuppressWarnings("deprecation")
            Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
            if (cIndex == components.size()) {
                wasAdded = components.add(c);
                if (cIndex == 0) {
                    c.endOffset = readableBytes;
                } else {
                    Component prev = components.get(cIndex - 1);
                    c.offset = prev.endOffset;
                    c.endOffset = c.offset + readableBytes;
                }
            } else {
                components.add(cIndex, c);
                wasAdded = true;
                if (readableBytes != 0) {
                    updateComponentOffsets(cIndex);
                }
            }
            if (increaseWriterIndex) {
                writerIndex(writerIndex() + buffer.readableBytes());
            }
            return cIndex;
        } finally {
            if (!wasAdded) {
                buffer.release();
            }
        }
    }

其核心逻辑不难分析,主要就是添加组件、改变偏移量和end偏移量,如果开启了写索引递增,那么就将写索引加上每个ByteBuf的可读字节,最后如果添加组件失败,就将当前组件ByteBuf进行release释放。

总的来说CompositeByteBuf是通过内部封装的List<Component> components将ByteBuf进行合并,在合并的过程中我们可以手动设置是否递增写索引,进而从逻辑上可以操作多个ByteBuf而不必在物理地址上将其拷贝。

个人才疏学浅、信手涂鸦,netty框架更多模块解读相关源码持续更新中,感兴趣的朋友请移步至个人公众号,谢谢支持😜😜......

公众号:wenyixicodedog

### Netty零拷贝技术的实现与应用 Netty 是一款异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。在处理大量数据传输时,性能优化至关重要,而零拷贝(zero-copy)技术正是其中的关键之一。 #### 零拷贝的概念及其优势 传统文件传输方式通常涉及多次内存复制操作:先从磁盘读取到内核缓冲区,再由内核空间复制到用户态的应用程序缓存中;发送端还需再次将这些数据搬移到套接字缓冲区内等待发出。这种模式下不仅增加了 CPU 的负担还浪费了大量的时间开销于上下文切换上[^1]。 相比之下,“零拷贝”的核心理念在于减少乃至消除上述不必要的中间环节——尽可能让操作系统直接把磁盘上的资料映射成虚拟地址供进程访问或者通过 DMA 控制器完成整个过程中的大部分工作量从而极大提高了效率并降低了延迟。 #### Netty 实现零拷贝的具体方法 为了支持高效的数据交换机制,Netty 提出了多种策略来达成所谓的“零拷贝”。以下是几种主要的方式: - **复合缓冲区**:CompositeByteBuf 类允许组合多个 ByteBuf 对象作为一个整体来进行管理而不必真正地移动任何实际存储位置里的内容。这使得可以在不改变原始消息结构的前提下轻松拼接或拆分报文体。 ```java CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer(); compositeBuffer.addComponent(true, bufferA); compositeBuffer.addComponent(true, bufferB); ``` - **文件区域转移**:FileRegion 接口定义了一个可以被写出到 Channel 的抽象类,默认实现了 writeTransfer 方法利用 sendfile() 系统调用来代替常规写入流程,进而减少了两次额外的副本创建动作以及相应的 I/O 请求次数。 ```java FileChannel fc = new FileInputStream(file).getChannel(); DefaultFileRegion region = new DefaultFileRegion(fc, position, count); channel.writeAndFlush(region); ``` - **直接分配堆外内存池**:PooledDirectByteBufFactory 能够预先划分好固定大小的块状区域作为共享资源池供给后续新实例化出来的 Buffer 使用,既加快了对象构建速度又避免频繁触发 GC 周期回收无用碎片带来的负面影响。 综上所述,借助以上特性以及其他底层优化措施的支持,Netty 成功地简化了许多原本复杂的通信场景下的编程模型同时也显著增强了系统的吞吐能力和响应灵敏度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值