了解零拷贝 零拷贝是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相干文章,学习材料都会在外面更新,整顿的材料也会放在外面。

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