理解Netty中的零拷贝(Zero-Copy)机制【转】
理解零拷贝
零拷贝是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可以通过零拷贝的方式将这些数据包组合成一条完整的消息供你来使用。而此时,零拷贝的作用范围仅在用户空间中。
Netty3中零拷贝的实现机制
以下以Netty 3.8.0.Final的源代码来进行说明
ChannelBuffer接口
Netty为需要传输的数据制定了统一的ChannelBuffer
接口。该接口的主要设计思路如下:
- 使用
getByte(int index)
方法来实现随机访问 - 使用双指针的方式实现顺序访问
- 每个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
。
参考资料
- http://my.oschina.net/flashsword/blog/164237
- http://en.wikipedia.org/wiki/Zero-copy
- http://stackoverflow.com/questions/20727615/is-nettys-zero-copy-different-from-os-level-zero-copy
- http://www-old.itm.uni-luebeck.de/teaching/ws1112/vs/Uebung/GrossUebungNetty/VS-WS1112-xx-Zero-Copy_Event-Driven_Servers_with_Netty.pdf?lang=de
理解Netty中的零拷贝(Zero-Copy)机制【转】的更多相关文章
- Netty:Netty中的零拷贝(Zero Copy)
零复制概念: " 零复制"描述了计算机操作,其中CPU不执行将数据从一个存储区复制到另一个存储区的任务.通过网络传输文件时,通常用于节省CPU周期和内存带宽. WIKI的定义中,我 ...
- 对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解
此文章已同步发布在我的 segmentfault 专栏. 根据 Wiki 对 Zero-copy 的定义: "Zero-copy" describes computer opera ...
- 零拷贝-zero copy
Efficient data transfer through zero copy Zero Copy I: User-Mode Perspective 0. 前言 在阅读RocketMQ的官方文档时 ...
- Linux 中的零拷贝技术,第 1 部分
概述 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关 ...
- 【转】浅析Linux中的零拷贝技术
本文探讨Linux中主要的几种零拷贝技术以及零拷贝技术适用的场景.为了迅速建立起零拷贝的概念,我们拿一个常用的场景进行引入: 引文## 在写一个服务端程序时(Web Server或者文件服务器),文件 ...
- [转帖]Linux 中的零拷贝技术,第 2 部分
Linux 中的零拷贝技术,第 2 部分 https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html Linux 中 ...
- [转帖]Linux 中的零拷贝技术,第 1 部分
Linux 中的零拷贝技术,第 1 部分 https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/index.html 引言 传统的 ...
- 【Java】Java中的零拷贝
物理内存 计算机物理内存条的容量,比如我们买电脑会关注内存大小有多少G,这个容量就是计算机的物理内存. 虚拟内存 操作系统为每个进程分配了独立的虚拟地址空间,也就是虚拟内存,虚拟地址空间又分为用户空间 ...
- Linux 中的零拷贝技术,第 2 部分
技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概 ...
随机推荐
- servlet保存数据的几种方式
In Servlets you have 4 scopes where you can store data. Application Session Request Page
- Asp.net中GridView使用详解(引)【转】
Asp.net中GridView使用详解(引) GridView无代码分页排序 GridView选中,编辑,取消,删除 GridView正反双向排序 GridView和下拉菜单DropDownList ...
- java.lang.IllegalStateException: Failed to load ApplicationContext selenium 异常 解决
WARN <init>, HHH000409: Using org.hibernate.id.UUIDHexGenerator which does not generate IETF R ...
- VI 基本可视模式
可视模式让你可以选择文件的一部分内容,以便作比如删除,复制等工作. 进入可视模式 v 用v命令进入可视模式.当光标移动时,就能看到有一些文本被高亮显示了,它们就是被选中的内容. 三种可视模式 v 一个 ...
- SaltStack入门到精通第一篇:安装SaltStack
SaltStack入门到精通第一篇:安装SaltStack 作者:纳米龙 发布日期:2014-06-09 17:50:36 实际环境的设定: 系统环境: centos6 或centos5 实验机 ...
- Unix awk使用手册
什么是awk? 你可能对UNIX比较熟悉,但你可能对awk很陌生,这一点也不奇怪,的确,与其优秀的功能相比,awk还远没达到它应有的知名度.awk是什么?与其它大多数UNIX命令不同的是,从名字上看, ...
- ROS学习(九)—— rqt_console 和 roslaunch
一.rqt_console 和rqt_logger_level 1.作用: rqt_console依据ROS编译日志,输出节点信息 rqt_logger_level可以改变节点的警告出差的警告等级 2 ...
- C# Random 生成不重复随机数
命名空间:System 表示伪随机数生成器,一种能够产生满足某些随机性统计要求的数字序列的设备. 伪随机数是以相同的概率从一组有限的数字中选取的.所选数字并不具有完全的随机性,因为它们是用一种确定的数 ...
- android控件拖动,移动、解决父布局重绘时控件回到原点
这是主要代码: 保证其params发生改变,相对于父布局的位置就能达到位置移动到原来的位置 // 每次移动都要设置其layout,不然由于父布局可能嵌套listview,当父布局发生改变冲毁(如下拉刷 ...
- 带你开发一款给Apk中自己主动注入代码工具icodetools(开凿篇)
一.前言 从这篇開始咋们開始一个全新的静态方式逆向工具icodetools的实现过程.这个也是我自己第一次写的个人认为比較实用的小工具,特别是在静态方式逆向apk找关键点的时候.兴许会分为三篇来具体介 ...