[Java] Non-blocking I/O using Servlet 3.1: Scalable applications using Java EE 7 (TOTD #188)

原文はこちら。
https://blogs.oracle.com/arungupta/entry/non_blocking_i_o_using

Servlet 3.0では非同期リクエスト処理が可能ですが、Traditional I/Oしか使えませんでした。そのため、アプリケーションのスケーラビリティに制限が出ることがあります。通常、アプリケーションではwhileループ内でServletInputStreamを読んでいます。
public class TestServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {     
 ServletInputStream input = request.getInputStream();
       byte[] b = new byte[1024];
       int len = -1;
       while ((len = input.read(b)) != -1) {
          . . . 
       }
   }
}
インバウンドデータがブロックされたり、サーバーの読み取り可能なスピードよりも遅く流れると、サーバースレッドはデータを待ちます。データをServletOutputStreamに書き出す場合でも同じことが起こりえます。

This is resolved in Servet 3.1 (JSR 340:Java EE 7の一部としてリリースされる予定です)ではこの問題をイベントリスナー、ReadListenerWriteListenerインターフェースを追加することによって解決します。リスナーは、コンテンツがブロッキングなしで読み取り可能、もしくは書き出し可能になった時点で起動されるコールバックメソッドを持ちます。
JSR 340: Java Servlet 3.1 Specification
http://jcp.org/en/jsr/detail?id=340
Interface EventListener
http://docs.oracle.com/javase/7/docs/api/java/util/EventListener.html
doGetを書き換えると、次のようになります。
AsyncContext context = request.startAsync();
ServletInputStream input = request.getInputStream();
input.setReadListener(new MyReadListener(input, context));
setXXXListenerメソッドを呼び出すということは、Traditional I/Oではなく、non-blocking I/Oを使うことを意味します。高々1個のReadListenerServletIntputStream に登録でき、同様に高々1個のWriteListenerServletOutputStreamに登録することができます。 ServletInputStream.isReadyServletInputStream.isFinished は新しいメソッドで、non-blocking I/O readの状況をチェックできます。ServletOutputStream.canWriteはデータをブロックせずに書き込めるかどうかを確認する新しいメソッドです。

MyReadListenerの実装は次のような感じです。
@Override
public void onDataAvailable() {
 try {
 StringBuilder sb = new StringBuilder();
 int len = -1;
 byte b[] = new byte[1024];
 while (input.isReady()
 && (len = input.read(b)) != -1) {
 String data = new String(b, 0, len);
 System.out.println("--> " + data);
 }
 } catch (IOException ex) {
 Logger.getLogger(MyReadListener.class.getName()).log(Level.SEVERE, null, ex);
 }
}

@Override
public void onAllDataRead() {
 System.out.println("onAllDataRead");
 context.complete();
}

@Override
public void onError(Throwable t) {
 t.printStackTrace();
 context.complete();
}
この実装には3個のコールバックメソッドがあります。
  • onDataAvailable コールバック・メソッド:データをブロックせずに読み出すことが可能な場合に呼び出される
  • onAllDataRead コールバック・メソッド:現在のリクエストを完全に読み込んだ際に呼び出される
  • onError コールバック・メソッド:リクエスト処理中にエラーが発生した場合に呼び出される
context.complete()onAllDataReadもしくはonErrorでデータの読み込み完了を通知するために呼び出されることにご注意ください。

現在のところ、データの最初のチャンク(塊)をサーブレットのdoGetもしくはserviceメソッドで読み取る必要がありますが、そのあと、残りのデータはnon-blocking方式でReadListenerを使って読み取ることができます。これにより、すべての読み取りデータはReadListenerでのみ発生することになります。

このエントリでご紹介したサンプルはここからダウンロードできます。実行する場合はGlassFish 4.0 build 64以降のものをお使いください。
GlassFish 4.0 build 64
http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/glassfish-4.0-b64.zip
Promoted build
http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/
JavaOneでのセッション「What's new in Servlet 3.1: An Overview」の動画とセッション資料は以下のリンクからどうぞ。
CON6793 - What’s New in Servlet 3.1: An Overview
https://oracleus.activeevents.com/connect/sessionDetail.ww?SESSION_ID=6793 

以下は参考資料です。
[訳注]
WebLogic Channelの過去記事も参考にどうぞ。
Java Servlet 3.1の新機能――クラウド対応のJava EE 7でどう変わるのか?【Java EEエキスパート・シリーズ】
https://blogs.oracle.com/weblogic_channel/entry/javaee_c116 

    0 件のコメント:

    コメントを投稿