Posts tagged ‘Non Blocking I/O’

Blocking I/OとNon Blocking I/Oについて



以前、Grizzlyの概要 : C10K問題に対応するGlassFish(Grizzly) というエントリで、

Grizzly が実装している Non Blocking I/O の利点について説明しましたが、

Project Kenai 中のプロジェクト Grizzly-send file 内で過去に書いたエントリの

補足となるような良い説明資料があがっていましたので、こちらでも紹介します。



※ このエントリを読む前に是非、過去の記事をご覧ください。



マルチスレッドで実装された Webサーバ は下記のようにAcceptor スレッドとWorker スレッドの間に

Queueを置いて、処理の効率化をはかっています。






この時、仮に Worker スレッドの数が5個だった場合に8クライアントから同時にファイル取得の

要求が来た場合を想定します。







Blocking I/Oを使って実装されたマルチスレッドサーバの場合、実際に作業ができるWorker スレッドは

5つしかありませんので、3つのクライアントは他の処理が終わるまで Queue で待たなければなりません。

取得する対象のコンテンツサイズが小さい、もしくはすぐに完了するような場合、このBlocking I/O

のマルチスレッドサーバは高速に動作します。



しかし、クライアントからのリクエスト数が膨大になった場合や、取得するコンテンツのサイズが大きい場合、

もしくはコンテンツの取得に時間がかかるような場合、他のクライアントの要求に対して影響が大きいことが

わかります。









一方、Grizzly のように Non Blocking I/O に対応した Web Server を利用すると、Woker スレッドを

効率的に利用することができるようになります。








5つの Woker スレッドが8クライアントからの同時アクセスに対して、各スレッド毎で分割して

処理できますので、誰か1人が大きなファイルを取得したとしても他の人に対して影響は少なく

なります。

そこで、クライアントからのリクエスト数が増えれば増える程、Non Blocking I/Oの実装の恩恵を

受ける事ができます。



PS.

今回は Project Kenai の Grizzly-Send file の画像を使わさせて頂きました。

私が作成した概念図より各スレッド毎に分割されて処理しているのが

分かりやすいので、二つの資料を両方つかうとわかりやすいですね。


2009年4月24日 at 1:03 午前

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)を記載します。









selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(SERVER_PORT));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
for (Iterator it = selector.selectedKeys().iterator(); it.hasNext();) {
SelectionKey key = (SelectionKey) it.next();
it.remove();
if (key.isAcceptable()) {
doAccept((ServerSocketChannel) key.channel());
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel)key.channel();
doRead(channel);
}
}
}




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で実装したサンプルエコーサーバ







import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
public class NonBlockingServer {
private static final int SERVER_PORT = 8888;
private static final int BUF_SIZE = 2000;
private Selector selector;
public static void main(String[] args) {
NonBlockingServer nserver = new NonBlockingServer();
nserver.start();
}
public void start(){
ServerSocketChannel serverChannel = null;
try {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(SERVER_PORT));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
for (Iterator it = selector.selectedKeys().iterator();
it.hasNext();) {
SelectionKey key = (SelectionKey) it.next();
it.remove();
if (key.isAcceptable()) {
doAccept((ServerSocketChannel) key.channel());
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel)key.channel();
doRead(channel);
}
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
private void doAccept(ServerSocketChannel serverChannel) {
try {
SocketChannel channel = serverChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
private void doRead(SocketChannel channel) {
ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE);
Charset charset = Charset.forName(“UTF-8”);
try {
if (channel.read(buf) < 0) {
return;
}
buf.flip();
System.out.print(
charset.decode(buf).toString());
buf.flip();
channel.write(buf);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}




このように、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. ノンブロッキングチャネル)


2008年5月23日 at 12:05 午前

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)の見方を紹介します。

2008年5月15日 at 12:00 午前


Java Champion & Evangelist

Translate

ご注意

このエントリは個人の見解であり、所属する会社の公式見解ではありません

カレンダー

2024年4月
1234567
891011121314
15161718192021
22232425262728
2930  

カテゴリー

clustermap

ブログ統計情報

  • 1,286,958 hits

Feeds

アーカイブ