ジェネリックライブラリとしての使用
Netty はネットワークアプリケーションを構築するためのフレームワークですが、ソケット I/O を実行しないプログラムであっても、他の用途に役立つ基礎クラスも提供します。
io.netty.buffer
は、ByteBuf
と呼ばれる汎用的なバッファ型を提供します。これは java.nio.ByteBuffer
のようなものですが、より高速で、ユーザーフレンドリーで、拡張可能です。
java.nio.ByteBuffer.flip()
を呼び出すのを忘れて、バッファに何も含まれていない理由に困惑したことはありませんか?ByteBuf
には読み込み用と書き込み用の2つのインデックスがあるため、そのようなことは決して起こりません。
ByteBuf buf = ...;
buf.writeUnsignedInt(42);
assertThat(buf.readUnsignedInt(), is(42));
バッファの内容に簡単にアクセスできるように、より豊富なアクセスメソッドのセットがあります。たとえば、符号付きおよび符号なし整数、検索、文字列のアクセサメソッドがあります。
java.nio.ByteBuffer
はサブクラス化できませんが、ByteBuf
では可能です。また、便宜のために抽象スケルトン実装も提供されています。そのため、ファイルバックのもの、複合的なもの、さらにはハイブリッドなど、独自のバッファ実装を作成できます。
新しい java.nio.ByteBuffer
が割り当てられると、その内容はゼロで埋められます。この「ゼロ埋め」は CPU サイクルとメモリ帯域幅を消費します。通常、バッファはその後すぐに何らかのデータソースから埋められるため、ゼロ埋めは役に立ちませんでした。
java.nio.ByteBuffer
は、回収されるために JVM ガベージコレクターに依存します。これはヒープバッファでは問題ありませんが、ダイレクトバッファではそうではありません。設計上、ダイレクトバッファは長期間存在することが期待されています。したがって、多くの短寿命のダイレクト NIO バッファを割り当てると、多くの場合 OutOfMemoryError
が発生します。また、(隠された、独自の)API を使用してダイレクトバッファを明示的に解放することは、あまり高速ではありません。
ByteBuf
のライフサイクルは、参照カウントにバインドされています。カウントがゼロになると、基になるメモリ領域(byte[]
またはダイレクトバッファ)は明示的に参照解除、解放、またはプールに返されます。
Netty は、バッファをゼロ埋めして CPU サイクルやメモリ帯域幅を浪費しない、堅牢なバッファプール実装も提供します。
ByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
ByteBuf buf = alloc.directBuffer(1024);
...
buf.release(); // The direct buffer is returned to the pool.
ただし、参照カウントは万能ではありません。基になるメモリ領域がプールに返される前に JVM ガベージコレクションがプールされたバッファを回収した場合、リークは最終的にプールを使い果たします。
リークのトラブルシューティングを支援するために、Netty はアプリケーションのパフォーマンスとリークレポートの詳細のバランスを取ることができる、柔軟なリーク検出メカニズムを提供します。詳細については、参照カウントオブジェクトを参照してください。
タスクを非同期的に実行する、つまりタスクをスケジュールして完了時に通知を受けることは一般的であり、簡単である必要があります。java.util.concurrent.Future
が最初に登場したとき、私たちの興奮は長く続きませんでした。完了時に通知を受け取るためにブロックする必要がありました。非同期プログラミングでは、結果を待つのではなく、完了時に何をすべきかを指定します。
io.netty.concurrent.Future
は JDK Future
のサブタイプです。これにより、Future が完了したときにイベントループによって呼び出されるリスナーを追加できます。
io.netty.util.concurrent.EventExecutor
は、java.util.concurrent.ScheduledExecutorService
を拡張したシングルスレッドのイベントループです。独自のイベントループを構築するか、機能豊富なタスクエグゼキュータとして使用できます。通常、並列処理を利用するために複数の EventExecutor
を作成します。
EventExecutorGroup group = new DefaultEventExecutorGroup(4); // 4 threads
Future<?> f = group.submit(new Runnable() { ... });
f.addListener(new FutureListener<?> {
public void operationComplete(Future<?> f) {
..
}
});
...
常に利用可能で、ライフサイクル管理を必要としない一意のエグゼキュータが必要になる場合があります。GlobalEventExecutor
は、そのスレッドを遅延起動し、しばらく保留中のタスクがない場合に停止する、シングルトンのシングルスレッド EventExecutor
です。
GlobalEventExecutor.INSTANCE.execute(new Runnable() { ... });
内部的には、Netty は他の EventExecutor
の終了を通知するために使用します。
この機能は内部使用専用であることに注意してください。十分な需要があれば、内部パッケージから移動することを検討しています。
io.netty.util.internal.PlatformDependent
は、プラットフォームに依存し、潜在的に安全でない操作を提供します。これは、sun.misc.Unsafe
およびその他のプラットフォーム依存の独自 API の薄いレイヤーと考えることができます。
高性能なネットワークアプリケーションフレームワークを構築するために、ユーティリティを導入しました。役立つものが見つかるかもしれません。
スレッドが長時間実行され、同じ型の短寿命オブジェクトを多数割り当てる場合は、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
は定数の静的なセットには最適ですが、拡張することはできません。ランタイムで定数を追加したり、サードパーティが追加の定数を定義できるようにする必要がある場合は、拡張可能な 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 は、非コアトランスポートがタイプセーフな方法でトランスポート固有のオプションを定義できるように、ChannelOption
を定義するために ConstantPool
を使用します。
キーと値のペアの高速で、タイプセーフで、スレッドセーフなコレクションには、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
は Constant
です。
ハッシュホイールタイマーは、java.util.Timer
および java.util.concurrent.ScheduledThreadPoolExecutor
に代わるスケーラブルな代替手段です。次の表に示すように、多くのスケジュールされたタスクとそのキャンセルを効率的に処理できます。
新しいタスクをスケジュールする | タスクをキャンセルする | |
---|---|---|
HashedWheelTimer |
O(1) | O(1) |
java.util.Timer および ScheduledThreadPoolExecutor |
O(logN) | O(logN) ここで、N は保留中のタスク数です |
内部的には、ほとんどのタイマー操作で一定時間になるように、キーがタスクのタイミングであるハッシュテーブルを使用します(java.util.Timer
はバイナリヒープを使用します)。
ハッシュホイールタイマーの詳細については、これらのスライド ("Hashed and Hierarchical Timing Wheels," Dharmapurikar) と この論文 ("Hashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility," Varghese and Lauck) を参照してください。
次のクラスは役立ちますが、Guava などの他のライブラリにも優れた代替手段が見つかります。
-
io.netty.util.CharsetUtil
は、一般的に使用されるjava.nio.charset.Charset
を提供します。 -
io.netty.util.NetUtil
は、IPv4 および IPv6 ループバックアドレスのInetAddress
など、一般的に使用されるネットワーク関連の定数を提供します。 -
io.netty.util.DefaultThreadFactory
は、エグゼキュータースレッドを簡単に構成できる汎用的なThreadFactory
実装です。
Netty は依存関係のセットを最小限に抑えようとするため、一部のユーティリティクラスは、Guava などの他の一般的なライブラリのものと似ています。
このようなライブラリは、JDK API をより扱いやすくするためのさまざまなユーティリティクラスと代替データ型を提供しており、通常は非常に優れています。
Netty は、以下に必要な構成要素の提供に焦点を当てています。
- 非同期プログラミング
- 次のような低レベルの操作 (「機械的共感」とも呼ばれます)
- オフヒープアクセス
- 独自の組み込み操作へのアクセス
- プラットフォーム依存の動作
Java は、Netty が提供する構成要素を包含するアイデアを採用することで進歩することがあります。たとえば、JDK 8 では、io.netty.util.concurrent.Future
と多少重複する CompletableFuture
が追加されています。このような場合、Netty の構成要素は、あなたに移行のための優れたパスを提供します。将来の移行を念頭に置いて、API を勤勉に更新します。