关于java:Netty面试常驻题你知道Netty的零拷贝机制吗

35次阅读

共计 4062 个字符,预计需要花费 11 分钟才能阅读完成。

了解零拷贝 零拷贝是 Netty 的重要个性之一,而到底什么是零拷贝呢?WIKI 中对其有如下定义:

“Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

从 WIKI 的定义中,咱们看到 “零拷贝”是指计算机操作的过程中,CPU 不须要为数据在内存之间的拷贝耗费资源。 而它通常是指计算机在网络上发送文件时,不须要将文件内容拷贝到用户空间(User Space)而间接在内核空间(Kernel Space)中传输到网络的形式。

Non-Zero Copy 形式:

Zero Copy 形式:

从上图中能够分明的看到,Zero Copy 的模式中,防止了数据在用户空间和内存空间之间的拷贝,从而进步了零碎的整体性能。Linux 中的 sendfile()以及 Java NIO 中的 FileChannel.transferTo()办法都实现了零拷贝的性能,而在 Netty 中也通过在 FileRegion 中包装了 NIO 的 FileChannel.transferTo()办法实现了零拷贝。

而在 Netty 中还有另一种模式的零拷贝,即Netty 容许咱们将多段数据合并为一整段虚构数据供用户应用,而过程中不须要对数据进行拷贝操作,这也是咱们明天要讲的重点。咱们都晓得在 stream-based transport(如 TCP/IP)的传输过程中,数据包有可能会被从新封装在不同的数据包中,例如当你发送如下数据时:

有可能理论收到的数据如下:

因而在理论利用中,很有可能一条残缺的音讯被宰割为多个数据包进行网络传输,而单个的数据包对你而言是没有意义的,只有当这些数据包组成一条残缺的音讯时你能力做出正确的解决,而 Netty 能够通过零拷贝的形式将这些数据包组合成一条残缺的音讯供你来应用。而此时,零拷贝的作用范畴仅在用户空间中。

以 Netty 3.8.0.Final 的源代码来进行阐明 ###ChannelBuffer 接口 Netty 为须要传输的数据制订了对立的 ChannelBuffer 接口。该接口的次要设计思路如下:

1. 应用 getByte(int index)办法来实现随机拜访

2. 应用双指针的形式实现程序拜访

每个 Buffer 都有一个读指针(readIndex)和写指针(writeIndex)

在读取数据时读指针后移,在写入数据时写指针后移

定义了对立的接口之后,就是来做各种实现了。Netty 次要实现了 HeapChannelBuffer,ByteBufferBackedChannelBuffer 等等,上面咱们就来讲讲与 Zero Copy 间接相干的 CompositeChannelBuffer 类。###CompositeChannelBuffer 类 CompositeChannelBuffer 类的作用是将多个 ChannelBuffer 组成一个虚构的 ChannelBuffer 来进行操作。

为什么说是虚构的呢,因为 CompositeChannelBuffer 并没有将多个 ChannelBuffer 真正的组合起来,而只是保留了他们的援用,这样就防止了数据的拷贝,实现了 Zero Copy。上面咱们来看看具体的代码实现,首先是成员变量

private int readerIndex;
private int writerIndex;
private ChannelBuffer[] components;
private int[] indices;
private int lastAccessedComponentId;

以上这里列出了几个比拟重要的成员变量。其中 readerIndex 既读指针和 writerIndex 既写指针是从 AbstractChannelBuffer 继承而来的;而后 components 是一个 ChannelBuffer 的数组,他保留了组成这个虚构 Buffer 的所有子 Buffer,indices 是一个 int 类型的数组,它保留的是各个 Buffer 的索引值;最初的 lastAccessedComponentId 是一个 int 值,它记录了最初一次拜访时的子 Buffer ID。

从这个数据结构,咱们不难发现所谓的 CompositeChannelBuffer 实际上就是将一系列的 Buffer 通过数组保存起来,而后实现了 ChannelBuffer 的接口,使得在下层看来,操作这些 Buffer 就像是操作一个独自的 Buffer 一样。

创立 接下来,咱们再看一下 CompositeChannelBuffer.setComponents 办法,它会在初始化 CompositeChannelBuffer 时被调用。

/**
 * Setup this ChannelBuffer from the list
 */
private void setComponents(List<ChannelBuffer> newComponents) {assert !newComponents.isEmpty();

    // Clear the cache.
    lastAccessedComponentId = 0;

    // Build the component array.
    components = new ChannelBuffer[newComponents.size()];
    for (int i = 0; i < components.length; i ++) {ChannelBuffer c = newComponents.get(i);
        if (c.order() != order()) {
            throw new IllegalArgumentException("All buffers must have the same endianness.");
        }

        assert c.readerIndex() == 0;
        assert c.writerIndex() == c.capacity();

        components[i] = c;
    }

    // Build the component lookup table.
    indices = new int[components.length + 1];
    indices[0] = 0;
    for (int i = 1; i <= components.length; i ++) {indices[i] = indices[i - 1] + components[i - 1].capacity();}

    // Reset the indexes.
    setIndex(0, capacity());
}

通过代码能够看到该办法的性能就是将一个 ChannelBuffer 的 List 给组合起来。它首先将 List 中得元素放入到 components 数组中,而后创立 indices 用于数据的查找,最初应用 setIndex 来重置指针。这里须要留神的是 setIndex(0, capacity())会将读指针设置为 0,写指针设置为以后 Buffer 的长度,这也就是后面须要做 assert c.readerIndex() == 0 和 assert c.writerIndex() == c.capacity()这两个判断的起因,否则很容易会造成数据反复读写的问题。

所以 Netty 举荐咱们应用 ChannelBuffers.wrappedBuffer 办法来进行 Buffer 的合并,因为在该办法中 Netty 会通过 slice()办法来确保构建 CompositeChannelBuffer 是传入的所有子 Buffer 都是符合要求的。

数据拜访 CompositeChannelBuffer.getByte(int index)的实现如下:

public byte getByte(int index) {int componentId = componentId(index);
    return components[componentId].getByte(index - indices[componentId]);
}

从代码咱们能够看到,在随机查找时会首先通过 index 获取这个字节所在的 componentId 既字节所在的子 Buffer 序列,而后通过 index – indices[componentId]计算出它在这个子 Buffer 中的第几个字节,而后返回后果。

上面再来看一下 componentId(int index) 的实现:

private int componentId(int index) {
    int lastComponentId = lastAccessedComponentId;
    if (index >= indices[lastComponentId]) {if (index < indices[lastComponentId + 1]) {return lastComponentId;}

        // Search right
        for (int i = lastComponentId + 1; i < components.length; i ++) {if (index < indices[i + 1]) {
                lastAccessedComponentId = i;
                return i;
            }
        }
    } else {
        // Search left
        for (int i = lastComponentId - 1; i >= 0; i --) {if (index >= indices[i]) {
                lastAccessedComponentId = i;
                return i;
            }
        }
    }

    throw new IndexOutOfBoundsException("Invalid index:" + index + ", maximum:" + indices.length);
}

从代码中咱们发现,Netty 以 lastComponentId 既上次访问的子 Buffer 序号为核心,向左右两边进行搜寻,这样做的目标是,当咱们两次随机查找的字符序列相近时(大部分状况下都是这样),能够最快的搜寻到指标索引的 componentId。

写在最初

欢送大家关注我的公众号【惊涛骇浪如码】,海量 Java 相干文章,学习材料都会在外面更新,整顿的材料也会放在外面。

感觉写的还不错的就点个赞,加个关注呗!点关注,不迷路,继续更新!!!

正文完
 0