Posts tagged ‘Java New I/O’
Grizzlyの概要 2 : Java New I/Oで実装されたサーバ
さてさて、今日も引き続きGrizzlyを説明したいと思いますが、
その前に、やはりJava New I/Oについて理解して頂かなければ
本当の意味でGrizzlyを理解していただけませんので、Grizzlyの
ソースコードを追う前にJava New I/Oについて簡単に復習して
おきたいと思います。
ただし、ここではほんの概要程度の説明ですのでちゃんと
理解されたい方は別途Java NIOについて説明されている記事を
読んで理解してください。
※ 本エントリの一番最後にJava New I/Oを学ぶことができる
参考URLを示しています。
Java New I/Oでノンブロッキングサーバの実装:
前回、Java New I/Oを使って実装されたサーバは、今までの
マルチスレッド型のBlocking I/Oを使用して実装されたサーバより
新規スレッドを作成する必要がなくスレッド数を少なくすることが
できると説明しましたが、では具体的にどのようにして実装されているかを
説明致します。
まず、Non Blocking I/Oを理解する上で必要なJava New I/OのAPIを紹介します。
チャネル:
チャネルとは、ハードウェアデバイス、ファイル、ネットワークソケットのほか、
個別の入出力操作を実行できる接続を表します。
Non Blockingにできるチャネルは、java.nio.channels.SelectableChannelクラスを
継承したサブクラスで、java.nio.channels.SocketChannelや
java.nio.channels.ServerSocketChannelクラスがあります。
このクラスのインスタンスメソッドで、configureBlocking(false)を
実行するとチャネルをNon Blockingモードに調整することができます。
例えば、ServerSocketChannelを使ってチャネルをノンブロッキングモードに
する為には下記のようにserverChannel.configureBlocking(false)を記載します。
|
serverChannelというチャネルをNon Blockingモードに設定した後、
Selectorのインスタンス selector に対してregister(登録)します。
実はこの行がJava New I/Oの中でとても重要な箇所です。
今までのマルチスレッド型のサーバの場合、java.net.ServerSocket.accept()メソッド
を呼び出してソケットのアクセプト処理を行っていましたが、Java New I/Oでは
java.nio.channels.ServerSocketChannel.accept()メソッドでアクセプト処理を行います。
この際、ServerSocketChannel.accept()メソッドはNon Blockingモードに設定されている場合、
保留されている接続がない場合、直ちにnullを返します。
つまりServerSocket.accept()のように新たなクライアントからの接続がくるまで
待ち続けることはありません。
しかし、クライアントからの接続を待たないということはサーバ側ではどのタイミングで
アクセプト処理を行えばよいのかが分からなくなります。
そこで、利用できるチャネル(接続)を取得する為にセレクタ(Selector.select())を使用します。
チャネルをセレクタに登録しておき、セレクタの中で利用できるようになったチャネルを
SelectionKeyとして取得して入出力操作を行うことができます。
セレクタには、複数のチャネルを登録することもでき、
複数の入出力操作を同時に行うことができるようになります。
例えば、下記にかんたんなエコーサーバをJava NIOを使用して
作成してみましたが、下記ではクライアントからの接続毎に
スレッドを生成していないことが御分かりいただけるかと思います。
また、マルチスレッド型のサーバのようにコネクション毎に新規スレッドを
生成していないことがわかります。
例:Java New I/Oで実装したサンプルエコーサーバ
|
このように、Java New I/Oを使用して作成されたサーバは、接続毎に
新規スレッドを作成しなくてもよくなるため、既存のサーバ実装とは
特に大量のアクセス等が発生した際に大きな差がでてきます。
例えば、メモリの消費量も少なくて済むので高負荷時になればなるほど
使用するサーバリソースが大きく変わってくるかと思います。
次回は、Grizzly 1.0.19のソースコードを読む際にどこから見ていけばよいのか、
また、Grizzly 1.0.19の中でどのようなクラスが重要なのか等
ソースコードを見るために必要な情報を紹介したいと思います。
※ Grizzlyのソースコードを読む前に、上記のSelector,SocketChannel
ServerSocketChannel,SelectionKey等のクラスを理解しておいてください。
Java New IOの参考記事へのURL:
● 横河電機の櫻庭さんによるIT Proの記事「New I/Oで高速な入出力」
● TECHSCORE (5. ノンブロッキングチャネル)
Grizzlyの概要 : C10K問題に対応するGlassFish(Grizzly)
さて、おまたせ致しました。
本日より、JJUGで発表したGrizzlyについて紹介したいと思います。
まず、Grizzlyって聞いたことありますか?
「GrizzlyはGlassFishコミュニティの中のサブプロジェクトの1つで
JavaのNew I/Oを使って記述された汎用的なネットワークサーバエンジンです。」
Project Grizzly :
https://grizzly.dev.java.net/
Project Grizzlyによって開発された成果物(ネットワークサーバエンジン)は GlassFishとは独立して単体で使用することができます。また、Grizzlyは独自の進化を遂げていっています。現在、Grizzlyの最新バージョンは1.7.3.1です。
今回紹介するGrizzlyは少しバージョンが古いのですが、GlassFish v2.x系にバンドルされているGrizzly 1.0.x(1.0.19)について紹介します。
●Grizzly誕生の背景
まず、Grizzlyのできあがった背景について説明します。Grizzlyは元々GlassFishのHTTPをハンドリングするHTTPサーバを開発するプロジェクトとして開始しました。それまでのSunのアプリケーションサーバは、TomcatのCoyoteを内部で使用していたのですが、GlassFishではCoyoteを使用せずにJavaのNew I/Oを使用して実現するHTTPサーバとして実験的に作成を開始しました。
(Grizzly1.0.19でもHTMLの解析等で一部apacheのAPIを使用してます)
そして開発を進めて行くに従い、Grizzlyの潜在能力の高さが判明してき、HTTPだけではなく、他のプロトコルも扱うことのできる汎用的なネットワークサーバエンジンになりえることが分かってきました。その結果、現在ではHTTP以外にTCP,UDPといった下位のレイヤープロトコルを始めとし、TLS,FTPやSIP等といったマルチプロトコルに対応するハイパフォーマンスなネットワークサーバエンジンになっています。ですので、仮に開発者が新たに独自のネットワークサーバを構築する必要がでてきた場合、GrizzlyのAPIを一部拡張して実装して頂くことでハイパフォーマンスな独自ネットワークサーバを構築することも可能になっています。
例えば、GrizzlyのHTTPサーバエンジン以外の使用例として独自のsyslogサーバを構築することも可能になります。Grizzlyの背景とできることは上で説明しましたが、何故今ここでGrizzlyという新しいネットワークサーバエンジン(フレームワーク)が登場したのでしょうか。以下ではWebサーバの実装における過去の歴史とGrizzlyが登場した理由を説明します。
●Web サーバの実装における歴史
Webサーバは古くはCERN,NCSA等といった所で作成されていましたが、Apacheでhttpdが作成された後はApacheにシェアを奪われていきました。こういった古いWebサーバや初期のApache httpdはプロセス起動型のサーバの実装になっていました。
このプロセス起動型のサーバではHTTPクライアントであるブラウザからリクエストがくる度にhttpdのプロセスを起動しリクエスト数が多くなると子プロセスをfork()して処理を行っていました。当時のWebサーバは今程リクエスト数も多くなく、FastCGI等も無いこの時代にはサーバサイドで実行するプログラム(Perl,C等のCGI)もfork()して子プロセスで実行していました。
しかし、プロセスのfork()はシステム(OS)に対して多大な負担を掛けます。そこで登場したのがマルチスレッド型のサーバです。
マルチスレッド型のサーバでは単一のプロセス内でリクエスト毎にスレッドを起動し、各スレッドでHTTPの処理を行うことで、子プロセスを起動するよりシステムに対する負担は大幅に軽減するようになります。この頃になり、マルチスレッドサーバ上でサーバサイドで実行するプログラムとしてServletが脚光を浴びました。しかし、実際のWebサーバの実装は上に説明した程簡単ではありません。上記の図中にスレッドの起動プログラム例を記載していますが、Webサーバのように大量のリクエストを扱うようなプログラムの場合、accept()の処理は負荷はあまり掛からないのですが、スレッドの起動はaccept()の処理に比べ非常に負荷が高くなってしまいます。この状況ではパフォーマンスにボトルネックが生じます。
そこで、スレッドの起動に関する問題を軽減する為に、実際のWebサーバではaccept()処理とスレッドの処理を分離し、間に接続キューを設けることでaccept()の処理を素早く受け流すことができるようになります。(SunのWeb ServerはC++で上記のように実装)そして、実際にHTTPの処理を行うワーカスレッドが接続キューからコネクションを取得し、HTTPの処理を行うようになります。他のサーバの実装を細かく見たことはありませんが、恐らく現在のWebサーバの実装は似たような実装になっているのではないかと思います。
●マルチスレッドサーバの問題 (Blocking I/O型サーバ)
ソケット通信を行うプログラムを実装する場合、accept()やI/Oのread(),write()等を使用しますが、これらのメソッドは処理をブロックします。
例えば、ServerSocket#accept()メソッドはクライアントが接続するまで待ち状態になり、クライアントからの接続があって初めてメソッドの処理を終了します。複数のクライアントからの接続を処理できるようにする為には、サンプルのようにスレッドを生成し実際の処理は別スレッドで行うように実装します。このような実装の場合、接続してくるクライアントの数が増えれば増える程、大量のスレッドが生成されることになります。そして、スレッドが大量に生成される場合、大量のメモリが必要になってきます。下記のグラフではJavaでスレッド数を増やしていった場合に実際に必要なスレッドのスタックサイズを現していますが、これによると10,000スレッドを同時に生成した場合、約10Gバイトのメモリが必要になっています。また、2万スレッドになると約20Gバイトのメモリが必要になっていることがわかります。このように、単純にスレッドを増やしていくモデルのサーバはアクセス数が増えれば増える程大量のメモリを消費していくことがわかります。
●C10K問題を解決できるサーバ (Non Blocking I/O型サーバ)
GlassFish(Grizzly)は上記の問題を解決するためにNon Blocking I/Oを使用して実装が施されています。Non Blocking I/Oを使用するとスレッドの作成数を減らすことができます。
実際には、Java NIOではServerSocketChannel#accept()メソッドでaccept処理を行いますが、この処理はブロックしません。仮にこのメソッドが呼び出された時にクライアントからの接続がない場合は、すぐにnullを返します。ServerSocket#accept()メソッドのように処理をブロックしないので、クライアントのリクエスト毎に新たなスレッドを生成する必要もなくなります。言い換えると、ServerSocketChannel#accept()を呼び出した元のスレッドで引き続き処理を続けることができるようになります。
※ つまり複数のリクエストを処理するために、接続毎に新規スレッドを生成しなくてもよいようになります。
Grizzlyの開発者の1人であるJean-Francoisは次のように述べています。
「Grizzlyではたった30スレッドで10,000の接続を捌くことができます。」
This strategy prevent one thread per request, and enable Grizzly to server more that 10 000
concurrent users with only 30 threads.
●Asynchronus Request Processing(ARP)
さて、GrizzlyはJava New I/Oで実装されているだけでもすごいのですがさらに、ARPも実装されています。ARPはComeのアプリケーションや、ビジネスプロセスの処理にとても時間が掛かるような場合、つまり長時間接続を保持しなければならないアプリケーションの動作も実現できるように実装されています。APRも又1つの接続辺り1スレッドを消費しないように実装されているため、今までのマルチスレッドサーバ、Synchronus Request Processingに比べComet等のアプリケーションを実行する環境としても最適です。
●まとめ
Grizzlyは汎用的なネットワークサーバエンジンです。独自にサーバを構築する際はGrizzlyのフレームワークを再利用することでハイパフォーマンスな独自サーバを構築することが可能になります。GrizzlyはJava NIOで実装されARPに対応しているのでComet等のアプリケーションの実行環境としても最適です、そしてパフォーマンスも良いのです。GlassFish(Grizzly)を使用して新しい先進的なアプリケーションを作成してください。
最後に、今回はGrizzlyの概要について紹介しました。GlassFish(Grizzly)が何故パフォーマンスがよいのかの概要を掴んでいただけたのではないかと思います。次回は、さらに深くGrizzlyの内部の実装を知りたい方、もしくはJava NIOで実装されたサーバに興味のある方を対象に、Grizzlyのソースコード(Grizzly 1.0.19)の見方を紹介します。