netty源码学习第三章:ByteBuf

来源:互联网 发布:debian centos 读音 编辑:程序博客网 时间:2024/03/29 10:25

先来学习几个概念:
一、对象池技术
对象池其实就是缓存一些对象从而避免大量创建同一个类型的对象,类似线程池的概念。对象池缓存了一些已经创建好的对象,避免需要时才创建对象,同时限制了实例的个数。池化技术最终要的就是重复的使用池内已经创建的对象。从上面的内容就可以看出对象池适用于以下几个场景:


创建对象的开销大
会创建大量的实例
限制一些资源的使用
Apache Commons Pool开源软件库提供了一个对象池API和一系列对象池的实现,支持各种配置,比如活跃对象数或者闲置对象个数等。DBCP数据库连接池基于Apache Commons Pool实现。而Netty自己实现了一套轻量级的对象池。在Netty中,通常会有多个IO线程独立工作,基于NioEventLoop的实现,每个IO线程轮询单独的Selector实例来检索IO事件,并在IO来临时开始处理。最常见的IO操作就是读写,具体到NIO就是从内核缓冲区拷贝数据到用户缓冲区或者从用户缓冲区拷贝数据到内核缓冲区。这里会涉及到大量的创建和回收Buffer,Netty对Buffer进行了池化从而降低系统开销。


实现细节:http://www.cnblogs.com/hzmark/p/netty-object-pool.html


性能测试表明,采用内存池的ByteBuf相比于朝生夕灭的ByteBuf,性能高23倍左右(性能数据与使用场景强相关)。在4.x版本中,UnpooledByteBufAllocator是默认的allocator,尽管其存在某些限制。现在PooledByteBufAllocator已经广泛使用一段时间,并且我们有了增强的缓冲区泄漏追踪机制,所以是时候让PooledByteBufAllocator成为默认了。 


测试详见:http://itindex.net/detail/51181-netty4-%E5%AF%B9%E8%B1%A1-%E5%AF%B9%E8%B1%A1


二、外观模式Facade(结构型)


http://blog.csdn.net/hguisu/article/details/7533759


netty ByteBuf 分类:

A、Pool和Unpool

final class PooledDirectByteBuf extends PooledByteBuf<ByteBuffer> {    private static final Recycler<PooledDirectByteBuf> RECYCLER = new Recycler<PooledDirectByteBuf>() {        @Override        protected PooledDirectByteBuf newObject(Handle<PooledDirectByteBuf> handle) {            return new PooledDirectByteBuf(handle, 0);        }    };    static PooledDirectByteBuf newInstance(int maxCapacity) {        PooledDirectByteBuf buf = RECYCLER.get();        buf.reuse(maxCapacity);        return buf;    }

如上代码是Pool方式,代码通过一个Recycler类来进行对象池管理,其内部是通过thread-local+queue来实现的。而Unpool,则只是简单的进行buf的用完即销毁策略。

B、Heap和Direct

public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {    private final ByteBufAllocator alloc;    byte[] array;    private ByteBuffer tmpNioBuf;    /**     * Creates a new heap buffer with a newly allocated byte array.     *     * @param initialCapacity the initial capacity of the underlying byte array     * @param maxCapacity the max capacity of the underlying byte array     */    protected UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {        this(alloc, new byte[initialCapacity], 0, 0, maxCapacity);    }


堆内存字节缓冲区(HEAP BUFFERS)

最常用的ByteBuf模式把数据存储在JVM的堆空间。这个模式被称为字节数组(backing array),在不用对象池的情况下,提供快速分配和再分配(的功能)。如下图

,这个方式非常适合你处理遗留数据(legacy data)。

l5-1 


注意:如果hasArray()返回false,试图存取一个字节数组(比如调用array()方法)会触发UnsupportedOperationException。这个模式(HEAP BUFFERS)和JDK的ByteBuffer使用类似。

直接内存字节缓冲区(DIRECT BUFFERS)

Direct buffer是另一种ByteBuf的模式。我们总是期望分配给对象创建的内存来自堆,但其实不一定是——ByteBuffer类和NIO一起在1.4版本被引入JDK,允许JVM的实现通过本地调用(native calls)来分配内存。这是为了避免在每个本地I/O操作的调用之前(或者之后)拷贝buffer的内容到一个intermediate buffer(或者从intermediate buffer拷贝到buffer)

ByteBuffer的Javadoc说的很明确,“direct buffer的内容会位于常规(可被)垃圾收集的堆之外”。这解释了为什么direct buffer很适合用于网络数据传输。如果你的数据放在一个堆分配的buffer里,在被送往socket发送之前,JVM实际上在内部会拷贝你的heap buffer到一个direct buffer。

高吞吐量下的过于频繁的对象分配。在这种模式下,单个服务处理的时间片其实很短,但却产生了很大的对象分配。虽然这里的对象也是具有很短暂的生命周期,但过于频繁的分配导致GC也变得频繁。即使是一次Younger GC,其成本也远大于一次简单的服务处理。

所以,理论上,尴尬的GC实际上比较适合于处理介于这2者之间的情况:对象分配的频繁程度相比数据处理的时间要少得多的,但又是相对短暂的,典型的,对于OLTP型的服务,处理能力在1K QPS量级,每个请求的对象分配在10K-50K量级,能够在5-10s的时间内进行一次younger GC,每次GC的时间可以控制在10ms水平上,这类的应用,实在是太适合GC行的模式了:而且结合Java高效的分代GC,简直就是一个理想搭配。

但是,对于类似于业务逻辑相对简单,譬如网络路由转发型应用(很多erlang应用其实是这种类型),QPS非常高,比如1M级,在这种情况下,在每次处理中即便产生1K的垃圾,都会导致频繁的GC产生。在这种模式下,或者是erlang的按进程回收模式,或者是C/C++的手工回收机制,效率更高。

至于Cache型应用,由于对象的存在周期太长,GC基本上就变得没有价值。

参考:http://www.cnblogs.com/gaoxing/p/4253892.html

Direct buffer主要的缺点是它们分配和释放的开销比heap buffer更大。如果你用的是遗留代码,你可能还会碰上另一个问题:因为数据不在堆上,你不得不做一次拷贝,如下所示。

l5-2

显然,这比用一个字节数组更费事,所以,如果你事先知道容器里的数据会通过一个数组存取,你也许会更倾向于用堆内存。


C、Unsafe


零拷贝

很多用户都听说过Netty具有“零拷贝”功能,但是具体体现在哪里又说不清楚,本小节就详细对Netty的“零拷贝”功能进行讲解。

Netty的“零拷贝”主要体现在如下三个方面:

1) Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

2) Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。

3) Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。

下面,我们对上述三种“零拷贝”进行说明,先看Netty 接收Buffer的创建:

图2-5 异步消息读取“零拷贝”

每循环读取一次消息,就通过ByteBufAllocator的ioBuffer方法获取ByteBuf对象,下面继续看它的接口定义:

图2-6 ByteBufAllocator 通过ioBuffer分配堆外内存

当进行Socket IO读写的时候,为了避免从堆内存拷贝一份副本到直接内存,Netty的ByteBuf分配器直接创建非堆内存避免缓冲区的二次拷贝,通过“零拷贝”来提升读写性能。

下面我们继续看第二种“零拷贝”的实现CompositeByteBuf,它对外将多个ByteBuf封装成一个ByteBuf,对外提供统一封装后的ByteBuf接口,它的类定义如下:

图2-7 CompositeByteBuf类继承关系

通过继承关系我们可以看出CompositeByteBuf实际就是个ByteBuf的包装器,它将多个ByteBuf组合成一个集合,然后对外提供统一的ByteBuf接口,相关定义如下:

图2-8 CompositeByteBuf类定义

添加ByteBuf,不需要做内存拷贝,相关代码如下:

图2-9 新增ByteBuf的“零拷贝”

最后,我们看下文件传输的“零拷贝”:

图2-10 文件传输“零拷贝”

Netty文件传输DefaultFileRegion通过transferTo方法将文件发送到目标Channel中,下面重点看FileChannel的transferTo方法,它的API DOC说明如下:

图2-11 文件传输 “零拷贝”

对于很多操作系统它直接将文件缓冲区的内容发送到目标Channel中,而不需要通过拷贝的方式,这是一种更加高效的传输方式,它实现了文件传输的“零拷贝”。

内存池

随着JVM虚拟机和JIT即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓冲区Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty提供了基于内存池的缓冲区重用机制。下面我们一起看下Netty ByteBuf的实现:

图2-12 内存池ByteBuf

Netty提供了多种内存管理策略,通过在启动辅助类中配置相关参数,可以实现差异化的定制。

下面通过性能测试,我们看下基于内存池循环利用的ByteBuf和普通ByteBuf的性能差异。

用例一,使用内存池分配器创建直接内存缓冲区:

图2-13 基于内存池的非堆内存缓冲区测试用例

用例二,使用非堆内存分配器创建的直接内存缓冲区:

图2-14 基于非内存池创建的非堆内存缓冲区测试用例

各执行300万次,性能对比结果如下所示:

图2-15 内存池和非内存池缓冲区写入性能对比

性能测试表明,采用内存池的ByteBuf相比于朝生夕灭的ByteBuf,性能高23倍左右(性能数据与使用场景强相关)。

下面我们一起简单分析下Netty内存池的内存分配:

图2-16 AbstractByteBufAllocator的缓冲区分配

继续看newDirectBuffer方法,我们发现它是一个抽象方法,由AbstractByteBufAllocator的子类负责具体实现,代码如下:

图2-17 newDirectBuffer的不同实现

代码跳转到PooledByteBufAllocator的newDirectBuffer方法,从Cache中获取内存区域PoolArena,调用它的allocate方法进行内存分配:

图2-18 PooledByteBufAllocator的内存分配

PoolArena的allocate方法如下:

图2-18 PoolArena的缓冲区分配

我们重点分析newByteBuf的实现,它同样是个抽象方法,由子类DirectArena和HeapArena来实现不同类型的缓冲区分配,由于测试用例使用的是堆外内存,

图2-19 PoolArena的newByteBuf抽象方法

因此重点分析DirectArena的实现:如果没有开启使用sun的unsafe,则

图2-20 DirectArena的newByteBuf方法实现

执行PooledDirectByteBuf的newInstance方法,代码如下:

图2-21 PooledDirectByteBuf的newInstance方法实现

通过RECYCLER的get方法循环使用ByteBuf对象,如果是非内存池实现,则直接创建一个新的ByteBuf对象。从缓冲池中获取ByteBuf之后,调用AbstractReferenceCountedByteBuf的setRefCnt方法设置引用计数器,用于对象的引用计数和内存回收(类似JVM垃圾回收机制)。

引用自:http://www.infoq.com/cn/articles/netty-high-performance


Reference Count




0 0
原创粉丝点击