牛骨文教育服务平台(让学习变的简单)
博文笔记

Netty --- 作为一个通用库来使用(翻译)

创建时间:2015-09-16 投稿人: 浏览次数:1339

Netty --- 作为一个通用库来使用(翻译)

注:以下内容为http://netty.io/wiki/using-as-a-generic-library.html#wiki-h2-5  的中文翻译

 

Netty是一个用来构建网络应用的框架,但同时也提供了很多基础的类可以使用在其它的场景中,甚至是不执行Socket  I/O的编程。

 

Buffer API

Io.netty.buffer包提供了一个通用的缓存类型:ByteBuf, 它很像java.nio.ByteBuffer,但是更快,更加友好和可扩展。

 

使用友好

你是不是曾经忘了调用java.nio.ByteBuffer.flip() 而不明白为什么buffer中没有包含任何东西,而在ByteBuf中不会出现这样的情况,因为它有两个索引,一个用于读,一个用于写。

ByteBuf buf = ...;
buf.writeUnsignedInt(42);
assertThat(buf.readUnsignedInt(), is(42));

 

ByteBuf有非常丰富的获取放来来方便的获取buffer的内容,例如有获取有符号和无符号整数,查找和字符串的获取方法。

 

扩展性

Java.nio.ByteBuffer无法继承,但是ByteBuf可以,一个抽象的框架实现同时给你带来了便利,你可以写你自己的buffer实现,比如基于文件的,组合的,甚至是一个混合的。

 

性能

当一个新的java.nio.ByteBuffer被申请时,会用0来进行填充,这种填充0的操作会消耗cpu的周期和内存带宽。通常来说,buffer会被马上用来填充一些内容,因此0填充并不好。

 

如声称的java.nio.ByteBuffer依赖于JVM的垃圾回收器,作为堆缓存时工作的好,当对直接缓存就不行了。从设计来说,直接缓存期望可以存活更长时间,因此申请大量的,短生命周期的直接NIO缓存会造成OutOfMemoryError。另外通过直接调用API来释放直接缓存并不快。

 

ByteBuf的生命周期是和引用计数绑定在一起的,当计数为0时,它内部的内存区间(byte[]或直接缓存)会被显式的去引用,释放,返回到池中。

 

Netty同样提供了一个可靠的缓存池实现来避免进行0填充时带来的CPU周期和内存带宽的浪费。

ByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
ByteBuf buf = alloc.directBuffer(1024);
...
buf.release(); // The direct buffer is returned to the pool.

 

 

引用计数并不是目的,如果JVM在内部的内存区域返回给池之前就把池缓存给释放了,这种泄露会造成池资源的耗尽。

 

为了帮助监测到这种泄露,Netty提供了一种灵活的泄露监测机制来在应用性能和监测报告细节之间进行选择,更多细节见Reference-counted-objects.

 

Listenable Futures 和 Event Loops

异步的执行一项任务:安排任务,在完成时接到通知,是很常见的,应该是容易的。当java.util.concurrent.Future第一次出现时,我们的兴奋没有持续太长时间,我们必须阻塞来在完成的时候获得通知,在异步编程中,你应该是指示在完成时该做什么,而不是等待结果。

 

io.netty.concurrent.Future是一个JDK Future的子类,它使得你可以田间一个监听者,当future完成时会在一个event loop中被调用。

io.netty.util.concurrent.EventExecutor是一个单线程的事件循环,扩展自java.util.concurrent.ScheduledExecutorServic。你可以构建自己的事件循环或者使用它来作为功能丰富的人物执行器。通常来说,需要创建多个事件执行器来利用并发的优点:

EventExecutorGroup group = new DefaultEventExecutorGroup(4); // 4 threads
Future<?> f = group.submit(new Runnable() { ... });
f.addListener(new FutureListener<?> {
  public void operationComplete(Future<?> f) {
    ..
  }
});

...

 全局的事件循环

有时候需要一个唯一的执行器,它总是可用的,不需要进行生命周期管理。GlobalEventExecutor是一个单线程的单例事件执行器,它会以懒惰的方式启动线程,并且在没有任务一段时间后停止线程。

GlobalEventExecutor.INSTANCE.execute(new Runnable() { ... });

Netty内部用它来通知其它执行器的终止。

平台相关的操作

注意:这个特性只是内部使用,我们在考虑是否有足够的需求将它移出内部包。

Io.netty.util.internal.PlatformDependent 提供了平台相关的,可能是不安全的操作。你可以认为这是sun.misc.Unsafe之上的简单层次和其它一些平台相关的专属API。

其它工具

为了构建一个高性能的网络应用框架,我们引入了一些工具,你可能会觉得有用。

Thread-local 对象池

如果你的线程是一个长期运行的,并且会申请很多同类型的短生命周期对象,你可以使用一个thread-local 对象池,称为Recycler。它减少你产生的垃圾的回收次数,保存了消耗的内存带宽和减少了垃圾回收的负载。

public class MyObject {
 
  private static final Recycler<MyObject> RECYCLER = new Recycler<MyObject>() {
    protected MyObject newObject(Recycler.Handle<MyObject> handle) {
      return new MyObject(handle);
    }
  }
 
  public static MyObject newInstance(int a, String b) {
    MyObject obj = RECYCLER.get();
    obj.myFieldA = a;
    obj.myFieldB = b;
    return obj;
  }
 
  private final Recycler.Handle<MyObject> handle;
  private int myFieldA;
  private String myFieldB;
 
  private MyObject(Handle<MyObject> handle) {
    this.handle = handle;
  }
 
  public boolean recycle() {
    myFieldA = 0;
    myFieldB = null;
    return handle.recycle(this);
  }
}
 
MyObject obj = MyObject.newInstance(42, "foo");
...
obj.recycle();

 

用户扩展的枚举

enum is great for astatic set of constants, but you cannot extend one. When you need to add moreconstants in runtime or allow third parties to define additional constants, usethe extensible io.netty.util.ConstantPool instead:

枚举对于静态的常量集合来说很有用,但是你无法扩展。当你需要在运行时增加更多的常量或允许第三方定义额外的常量,使用io.netty.util.ConstantPool来代替。

public final class Foo extends AbstractConstant<Foo> {
  Foo(int id, String name) {
    super(id, name);
  }
}
 
public final class MyConstants {
 
  private static final ConstantPool<Foo> pool = new ConstantPool<Foo>() {
    @Override
    protected Foo newConstant(int id, String name) {
      return new Foo(id, name);
    }
  };
 
  public static Foo valueOf(String name) {
    return pool.valueOf(name);
  }
 
  public static final Foo A = valueOf("A");
  public static final Foo B = valueOf("B");
}
 
private final class YourConstants {
  public static final Foo C = MyConstants.valueOf("C");
  public static final Foo D = MyConstants.valueOf("D");
}

 

Netty 使用ConstantPool来定义ChannelOptions ,因此非核心的传输也可以类型安全的定义传输相关的参数。

属性Map

使用io.netty.util.AttributeMap接口来作为一个快速的,类型安全的,线程安全的键值对集合。

public class Foo extends DefaultAttributeMap {
  ...
}
 
public static final AttributeKey<String> ATTR_A = AttributeKey.valueOf("A");
public static final AttributeKey<Integer> ATTR_B = AttributeKey.valueOf("B");
 
Foo o = ...;
o.attr(ATTR_A).set("foo");
o.attr(ATTR_B).set(42);

 

你可能已经注意到,AttributeKey是一个常数。

哈希轮转计时器

哈希轮转计时器是一个可测量的计时器,是java.util.Timer和java.util.concurrent.ScheduledThreadPoolExecutor的一个替代品。它可以处理很多计划的任务,可以有效的进行撤销。如下表显示:

 

Schedule a new task

Cancel a task

HashedWheelTimer

O(1)

O(1)

java.util.Timer and ScheduledThreadPoolExecutor

O(logN)

O(logN) where N = number of pending tasks

Internally, it uses a hash table whose key is a task"s timingto yield constant time for most timer operations. (java.util.Timeruses a binary heap.)

内部实现上,使用一个哈希表,whose key is a task"s timing to yieldconstant time for most timer operations(??不大明白),java.util.Timer使用二进制堆来实现。

更多杂的工具

下面是一些有用的类,你可以在其它的库中找到替代的实现,比如:Guava

  • io.netty.util.CharsetUtil 提供了一些常用的 java.nio.charset.Charsets.
  • io.netty.util.NetUtil 提供了一些常用的网络相关的常数如 InetAddress for IPv4 and IPv6 loopback addresses.
  • io.netty.util.DefaultThreadFactory是一个通用的线程工厂实现。

和 Guava ,JDK8的对比

Netty为了最小化依赖,一些工具类和流行的库都非常像,如Guava

这些库提供了非常多的工具类和一些数据类型的代替品来使得JDK的PAI可以更好的使用,并都运行的非常好。

Netty专注在提供一下需求的结构上:

  • 异步编程
  • 底层的操作 (a.k.a "mechanical sympathy") 如:
    • 堆外获取
    • 一些私有的,固有操作的获取
    • 平台相关的行为

Java有时候会采用Netty提供的一些结构的思路。比如JDK8 添加了ComplatableFuture,就和io.netty.util.concurrent.Future类似,在这样的情况下,Netty的构建提供了一种更加好的迁移的途径,我们会频繁将这些脑海中的出现的未来迁移到API中。

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。