|
1.1 従来のBIOプログラミング ネットワークプログラミングの基本モデルはクライアント/サーバーモデルであり、これは2つのプロセス間の通信です。サーバーは位置情報(バインドされたIPアドレスとリスニングポート)を提供し、クライアントは接続操作を通じてサーバーがリッスンしているアドレスへの接続要求を開始します。接続は3ウェイハンドシェイクによって確立されます。接続が正常に確立されると、両者はネットワークソケット(Socket)を介して通信できるようになります。 従来の同期ブロッキングモデルに基づく開発では、ServerSocketはIPアドレスのバインドとリスニングポートの開始を担当し、Socketは接続操作の開始を担当します。接続が成功すると、両者は入出力ストリームを介して同期的かつブロッキング的に通信を行います。 1.1.1 BIO通信モデル図 まず、図 2-1 に示す通信モデル図を使用して、BIO サーバー側通信モデルについて理解しましょう。 BIO通信モデルを使用するサーバーは、通常、クライアント接続をリッスンする専用のAcceptorスレッドを備えています。クライアントからの接続要求を受信すると、サーバーは各クライアントごとに新しいスレッドを作成し、接続を処理します。処理後、出力ストリームを介してクライアントに応答を返し、その後スレッドは破棄されます。これは典型的なリクエスト・レスポンス型通信モデルです。 図1-1 同期ブロッキングI/Oサーバ通信モデル(1クライアント、1スレッド) このモデルの最大の問題は、柔軟なスケーリングが不可能なことです。同時クライアントアクセス数が増加すると、サーバーのスレッド数は同時クライアントアクセス数に1:1で正比例します。スレッドはJava仮想マシンにとって非常に貴重なシステムリソースであるため、スレッド数が増えるとシステムパフォーマンスは急激に低下します。同時アクセス数が増加し続けると、スレッドスタックオーバーフローや新規スレッド作成の失敗などの問題が発生し、最終的にはプロセスのクラッシュやフリーズ、そしてサービス提供不能につながります。 1.2 擬似非同期I/Oプログラミング 接続ごとに個別のスレッドが必要となる同期ブロッキングI/Oの問題に対処するため、スレッドモデルは後に最適化されました。バックエンドはスレッドプールを使用して複数のクライアントからのリクエストを処理し、M(クライアント数)とN(スレッドプール内の最大スレッド数)の比率を作成します。MはNよりも大幅に大きくなる場合があります。スレッドプールにより、スレッドリソースを柔軟に割り当て、最大スレッド数を設定できるため、大量の同時接続によるスレッド枯渇を防止できます。 以下では、接続モデル図とソースコードを組み合わせて擬似非同期 I/O を分析し、同期ブロッキング I/O が直面する問題を解決できるかどうかを確認します。 1.2.1 擬似非同期I/Oモデル図 疑似非同期 I/O 通信フレームワークは、スレッド プールとタスク キューを使用して実装できます。そのモデル図を図 1-2 に示します。 新しいクライアントが接続すると、そのソケットは Task(java.lang.Runnable インターフェースを実装)にカプセル化され、バックエンドのスレッドプールに送られて処理されます。JDK スレッドプールは、メッセージキューと、メッセージキュー内のタスクを処理するための N 個のアクティブスレッドを管理します。スレッドプールはメッセージキューのサイズと最大スレッド数を設定できるため、リソース消費を制御可能です。同時アクセスするクライアントの数に関わらず、リソース枯渇やクラッシュが発生することはありません。 図1-2 擬似非同期I/Oサーバ通信モデル(M:N) 擬似非同期I/Oは、実際には従来のI/Oスレッドモデルの単純な最適化に過ぎず、同期I/Oによって引き起こされる通信スレッドのブロッキング問題を根本的に解決することはできません。以下では、相手側が確認応答を返すのに時間がかかりすぎる場合に発生する可能性のある連鎖的な障害について簡単に分析します。 1. サーバーの処理が遅く、応答メッセージを返すのに通常は 10 ミリ秒しかかからないところ、60 秒かかります。 2. 擬似非同期I/Oを使用しているスレッドが、障害が発生したサービスノードからの応答を読み取っています。入力ストリームの読み取りはブロッキング状態であるため、60秒間同期的にブロックされます。 3. 利用可能なすべてのスレッドが障害のあるサーバーによってブロックされている場合、後続のすべての I/O メッセージはキューに入れられます。 4. スレッド プールはブロッキング キューを使用して実装されているため、キューがいっぱいになると、キューに追加される後続の操作はブロックされます。 5. フロントエンドにはクライアント接続を受信する Accentor スレッドが 1 つしかなく、スレッド プールの同期ブロッキング キューでブロックされているため、新しいクライアント要求メッセージは拒否され、クライアントで多数の接続タイムアウトが発生します。 6. ほぼすべての接続がタイムアウトするため、呼び出し元はシステムがクラッシュし、新しい要求メッセージを受信できないと想定します。 1.3 NIOプログラミング NIO プログラミングを紹介する前に、まず概念を明確にする必要があります。NIO とはいったい何の略語なのでしょうか。 既存のI/Oライブラリに新たに追加されたため、「New I/O」と呼ぶ人もいます。これが正式名称です。しかし、以前のI/OライブラリはブロッキングI/Oを使用していたのに対し、New I/Oライブラリの目的はJavaでノンブロッキングI/Oをサポートできるようにすることです。そのため、一般的にはノンブロッキングI/Oと呼ばれます。ノンブロッキングI/OはNIOの特性をよりよく反映しているため、この記事ではNIOをノンブロッキングI/Oと呼びます。 NIOは、SocketクラスとServerSocketクラスに対応して、SocketChannelとServerSocketChannelという2つの異なるソケットチャネル実装も提供しています。これらの新しいチャネルはどちらも、ブロッキングモードとノンブロッキングモードの両方をサポートしています。ブロッキングモードは非常に使いやすいですが、パフォーマンスと信頼性が低く、ノンブロッキングモードはその逆です。開発者はニーズに応じて適切なモードを選択できます。一般的に、低負荷で同時実行性の低いアプリケーションでは、プログラミングの複雑さを軽減するために同期ブロッキングI/Oを選択できますが、高負荷で同時実行性の高いネットワークアプリケーションでは、NIOのノンブロッキングモードを使用する必要があります。 1.4 AIOプログラミング NIO 2.0では、非同期チャネルという新しい概念が導入され、非同期ファイルチャネルと非同期ソケットチャネルの実装が提供されました。非同期チャネルは、取得操作の結果を取得する2つの方法を提供します。
NIO 2.0の非同期ソケットチャネルは、UNIXネットワークプログラミングにおけるイベント駆動型I/O(AIO)に相当する、真の非同期ノンブロッキングI/Oです。マルチプレクサ(セレクタ)を介して登録されたチャネルをポーリングすることなく、非同期の読み書きを実現できるため、NIOプログラミングモデルが簡素化されます。 1.5 複数のI/Oモデルの比較 異なる I/O モデルではスレッド モデルや API などが大きく異なるため、使用方法にも大きな違いが生じます。 前のセクションでは、これらの I/O メソッドの API と使用方法の説明に重点を置いたため、このセクションでは、表 2-1 に示すように、それらの機能の比較に重点を置きます。 表1-1 各種I/Oモデルの機能と特性の比較 1.6 業界における主流のNIOフレームワークの紹介 モバイルインターネットの発展とビッグデータ時代の到来に伴い、大規模分散サービスフレームワークと分散ストリームコンピューティングフレームワークが主流のアーキテクチャとなっています。分散サービスノード間の通信形式は、FacebookのThriftプロトコルに代表される内部ロングコネクションであることが多いです。ノード間の通信スループットとパフォーマンスを向上させるため、現在主流の内部通信フレームワークはNIOフレームワークを採用しています。大企業や深い技術的蓄積を持つチームは、個別または業界固有のニーズを満たすために自社開発のNIOフレームワークを使用する場合もありますが、ほとんどのアーキテクトは非同期通信開発において業界主流のNIOフレームワークを選択します。 現在、業界には2つの主要なNIOフレームワーク、MinaとNettyがあり、どちらもApache License 2.0に基づくオープンソースです。MinaとNettyの違いは、MinaがApache Foundationの公式NIOフレームワークであるのに対し、Nettyは以前はJBossのNIOフレームワークであったことです。その後、NettyはJBossから分離し、独自にnetty.ioドメインを登録してJBossとの関係を断ち切り、バージョンをリファクタリングしたため、APIの非互換性が生じました。 MinaとNettyには歴史的な繋がりがあります。Minaの初期設計者はTrustin Leeでした。その後、様々な理由からTrustin LeeはMinaコミュニティを離れ、Nettyチームに加わり、Nettyの再設計と開発に携わりました。多くの読者は、NettyにMinaの影響が見られることに気付くでしょう。2つのフレームワークは多くの点で類似したアーキテクチャ哲学を共有しており、コードの一部にも驚くほどの類似点が見られます。これがその理由です。 現在、MinaとNettyは広く利用されており、多くのオープンソースフレームワークが基盤となるNIOフレームワークとしてこれらを使用しています。例えば、Hadoopの通信コンポーネントであるAvroは、基盤となる通信フレームワークとしてNettyを使用しています。 Openfireは、基盤となる通信フレームワークとしてMinaを使用しています。Minaと比較して、Nettyコミュニティは現在より活発で、より幅広い用途に使用されています。 1.7 Netty を選ぶ理由 1.7.1 JavaネイティブNIOプログラミングを選択しない理由 それでは、JDKのNIOライブラリを直接開発に使用しないことを推奨する理由をまとめてみましょう。具体的な理由は次のとおりです。 1. NIOのクラスライブラリとAPIは複雑で扱いにくいです。Selector、ServerSocketChannel、SocketChannel、ByteBufferなどに精通している必要があります。 2. Javaマルチスレッドプログラミングの知識など、基礎として追加のスキルが必要です。これは、NIOプログラミングにはReactorパターンが関係しており、高品質なNIOプログラムを作成するには、マルチスレッドとネットワークプログラミングに精通している必要があるためです。 3. 信頼性機能の向上は、非常に大きな取り組みであり、かつ極めて困難です。例えば、クライアントは、切断と再接続、断続的なネットワーク障害、部分的なパケットの読み取り/書き込み操作、障害キャッシュ、ネットワーク輻輳、異常なビットストリームの処理といった問題に直面します。NIOプログラミングは機能開発が比較的容易であるのに対し、信頼性機能の向上は膨大な取り組みであり、極めて困難です。 4. 悪名高いepollバグなどのJDK NIOのバグにより、セレクタが空のポーリングを実行し、最終的にCPU使用率が100%に達する可能性があります。JDKはこの問題をJDK 1.6のアップデート18で修正したと公式に発表していますが、発生率は低下したものの、JDK 1.7まで問題は解決されず、根本的な解決には至りませんでした。このバグと関連する問題チケットは、以下のリンクからご覧いただけます。
上記の理由から、NIOプログラミングに精通している場合や特別なニーズがある場合を除いて、ほとんどのシナリオではJDKのNIOライブラリを直接使用することは推奨されません。ほとんどのビジネスシナリオでは、NIOプログラミングにNIOフレームワークのNettyを使用できます。Nettyはクライアントとしてもサーバーとしても使用できます。 UDP と非同期ファイル転送をサポートしており、非常に強力な機能です。 1.7.2 Nettyを選んだ理由 Nettyは、業界で最も人気のあるNIOフレームワークの1つです。その堅牢性、機能性、パフォーマンス、カスタマイズ性、そしてスケーラビリティは、類似のフレームワークの中でも群を抜いています。数百もの商用プロジェクトで検証されています。例えば、HadoopのRPCフレームワークであるAvroは、基盤となる通信フレームワークとしてNettyを使用しています。業界の他の多くの主流RPCフレームワークも、高性能な非同期通信機能を構築するためにNettyを使用しています。 Netty の分析に基づいて、その利点を次のようにまとめます。
これらの利点により、Netty は徐々に Java NIO プログラミングに適したフレームワークになってきました。 Netty のアーキテクチャ図を以下に示します。 1.8 Netty開発環境のセットアップ まず、既にマシンにJDK 1.7をインストールし、JDK環境変数PATHを設定し、IDEツールであるEclipseをダウンロードして正常に起動できていると仮定します。Java初心者で、これまでマシンにJava開発環境を構築したことがない場合は、まずJavaの基礎を学ぶための書籍やコースを選ぶことをお勧めします。 NetBeans IDEなど、Java開発に他のIDEツールの使用に慣れている場合は、このセクションの入門サンプルも実行できます。ただし、実際のIDEに合わせて設定を変更する必要があります。本書では、Java開発ツールとしてeclipse-jee-kepler-SR1-win32を使用します。 1.8.1 Nettyライブラリをダウンロードする Nettyのウェブサイト(http://netty.io/)にアクセスし、【ダウンロード】タブから4.1.5.Finalパッケージを選択してください。このパッケージにはソースコード、コンパイル済みライブラリ、JavaDocが含まれており、サイズは約18.1MBです。解凍後のパッケージは以下の通りです。 この時点で、各モジュールの.jarファイルとソースコードが含まれていることがわかります。Nettyをバイナリライブラリとして直接使用するため、netty-all-4.1.5.Final.jarのみを取得する必要があります。 1.8.2 開発プロジェクトのセットアップ netty-all-4.1.5.Final.jar を Java プロジェクトの lib ディレクトリにインポートします(lib ディレクトリは自分で作成する必要があります)。netty を右クリックします。 Netty 開発環境をセットアップするには、ポップアップ メニューから「-all-4.1.5.Final.jar」を選択し、.jar ファイルをビルド パスに追加します。 |