2013年6月にリリースされる「Java EE 7」では、さまざまな機能改善や追加が行われるが、それらの中で目玉となるのが、HTML5の構成技術の1つである「WebSocket」への対応と、バッチ処理のサポートだ。2013年5月14日に開催された「Java Day Tokyo 2013」の技術セッションにおいて、米国オラクル Javaテクノロジーアンバサダーのアルン・グプタ氏が、このWebSocket対応とバッチ処理について解説した。その主なポイントを紹介しよう。(編集部) 約1200名の参加者を集めて盛大に開催されたJava Day Tokyo 2013。そのメイン・セッション会場では、“Java EEの伝道師”とも称されるグプタ氏が、Java EE 7の目玉となる新機能「WebSocket対応」、「バッチ処理サポート」をテーマに講演を行った。
WebSocket対応について取り上げたのは、セッション「ここからはじめる、JSR-356 WebSocket」だ。同セッションでグプタ氏は、HTML5の主要な構成要素の1つであるWebSocketの意義や役割、そしてWebSocketを使ったWebアプリケーションの作成方法を説明した。
講演の冒頭、グプタ氏は現状のWebアプリケーションで使われているHTTP通信の仕組みについて「経済効率が非常に悪い」と指摘した。
「現状のHTTP通信では、クライアントがリクエストを要求すると、サーバがそれにレスポンスを返すだけ。クライアント側とサーバ側が交互にデータを送り合う半二重(Half Duplex)の通信方式であり、またレスポンス内にはたくさんのヘッダ情報が含まれており冗長性が高い。さらに悪いことに、中断したコネクションを再開するときには、一から接続をやり直さなければならない」(グプタ氏)
こうしたHTTP通信の特性は、特に近年増加している“インタラクティブなWebサイト”を開発する際に大きな問題となりやすい。そこで、これまではServer PushやCometなどの技術を使い、開発者がこれらの問題に個別に対応していたが、グプタ氏によれば、それらは「標準的な方法がなかったがゆえのハックにすぎない」。そうした中、次世代Web開発の標準技術と目されるHTML5に双方向プッシュ技術として取り込まれたのがWebSocketなのである。
「WebSocketを使えば、クライアント/サーバ間で双方向の全二重(Full Duplex)通信が行える。クライアントからのリクエストを起点にし、それに対してサーバがレスポンスを返すという形式にこだわる必要もない。WebSocketが可能にすることで重要なのは、クライアント/サーバ間で行う一連のやり取りを単一のTCPコネクション内で行い、複数のメッセージがやり取りできるようになることだ」(グプタ氏)
1つ目はプロトコルだ。WebSocketのプロトコル仕様はRFC 6455で定義されているが、通信は最初の「ハンドシェイク(Handshake)」とその後の「データ転送(Data Transfer)」によって行われる。
2つ目はJavaScript API(WebSocket API)である。これは現在、W3Cで勧告候補(Candidate Recommendation)となっている。
グプタ氏は、WebSocketを使ったWebアプリケーション開発のデモを交えて、これら2つのポイントを解説した。
まずプロトコルだが、WebSocketによるクライアント/サーバ間通信では、初めに通常のHTTP通信によってコネクションを確立した後、使用プロトコルをWebSocketへとアップグレードする。具体的には、クライアントがサーバとHandshakeを行って通信を確立する際、HTTPヘッダで次のように「Upgrade: websocket」、「Connection: Upgrade」を指定すればよい。
サーバがWebSocketに対応していれば、サーバはプロトコルをHTTPからWebSocketにスイッチするレスポンスを返す。
以降は、GET、PUTといったHTTPのメソッドは無効となり、「http://」ではなく「ws://」で始まるURLによってクライアント/サーバ間がピア・ツー・ピアにつながり、コネクションをクローズするまでメッセージのやり取りを双方向で行えるようになる。
一方、2つ目のJavaScript APIについて、グプタ氏はWebSocket APIのインタフェースを示しながら、メッセージングはメソッドsendで行い、テキストのほかにBlob、ArrayBufferを送れること、エクステンションによって機能を拡張できることなどを説明した。
それでは、WebSocketを使うと、具体的にクライアント/サーバ間の通信はどの程度効率化できるのだろうか。グプタ氏は、メッセージ送信のパフォーマンスをRESTとWebSocketで比較した結果の例を示した。
それによれば、10バイトのメッセージを100回送信する程度ではRESTとWebSocketにそれほどの性能差は見られないが、100バイトのメッセージを1000回、1000バイトのメッセージを5000回などと増やしていくと、両者の間には数十倍の性能差が生じたという。例えば、1000バイトのメッセージを5000回送信した場合、RESTでは約55秒かかったのに対して、WebSocketでは約1秒で処理が完了した。
Java EE 7では、WebSocket対応機能がWebコンテナに統合され、非常に簡単に利用できるようになる。具体的には、サーバ、クライアントのプログラムに、それぞれ「@ServerEndpoint」、「@ClientEndpoint」というアノテーションを付加するだけでよい。
【クライアント側プログラムの例】
また、メソッドに付加する主なアノテーションには、次のようなものがある。
グプタ氏は、実際にこれらを使った簡単なチャット・サーバのアプリケーションを作り、そのデモを披露した。
Java Batchを使用した典型的なアプリケーションのユースケースは、「インタラクティブではなく長い時間がかかるタスクで、何千ものデータベース・レコードを一気に書き換えるような処理」(グプタ氏)である。
「例えば、銀行などで、月末の締め日に10万以上の口座(アカウント)を対象にして勘定報告を作成するといったケースが想定される。この場合、1〜1万までの口座を1つ目のスレッドで、1万〜2万までの口座を2つ目のスレッドでといった具合に処理を分割し、複数のスレッドを同時に実行することでパフォーマンスを高めるといった使い方をする」(グプタ氏)
Java Batchには、IBMのWebSphere Compute Grid技術のほか、Spring Batchの技術などが取り込まれている。コンセプトとしては、バッチ処理内で反復実行する「Job」に関する定義(Jobとは何か、どのようなStepがあるのかなど)を格納した「JobRepository」があり、JobRepositoryからJobの定義を「JobOperator」が取り出して「Step」として実行するといったかたちになる。Step内では、「ItemReader」、「ItemProcessor」、「ItemWriter」が、それぞれ「データの読み出し」、「データ処理」、「データの書き込み」を行う。
「例えば、口座ごとの勘定報告を作成し、それを顧客に電子メールで送信する場合、まず各口座から取引の情報を口座オブジェクトとして読み出して勘定オブジェクトを作り、それを計算して結果を渡す。読み出しはItemReaderで行い、ItemProcessorで処理して、その口座に関する情報をアグリゲートしたうえでItemWriterに書き込んでメールで送信するといった流れになる。このとき、読み出し(Read)、処理(Process)、書き込み(Write)を1つ1つ処理するのではなく、まとめてバッチで処理するところがポイントとなる」(グプタ氏)
なお、Jobについては、1日1回実行するものに対してはJobInstanceが生成され、1日のうちに複数回のJobを実行する場合は、JobInstanceから複数のJobExecutionが生成される仕組みになる。
また、Stepには、「Chunk」と「Batchlet」という2つの方式がある。このうちChunk方式は、下図に示すようにRead、Process、Writeを1つのトランザクションとして扱う方式だ。
Jobの定義は「Job Specification Language(JSL)」というXML形式の言語によって記述する。このJobの定義において、タグchunk内でReader(Read処理を行う)、Processor(反復実行する処理を行う)、Writer(結果を書き出す)の実装と実行回数などを指定する。例えば、次の例ではReaderとして口座情報を読み込むaccountReader、Processorとして口座情報を処理するaccountProcessor、Writerとして電子メール送信を行うemailWriterを指定している。
「具体的な実装では、JPA(Java Persistence API)やJavaMailを使うことで、Java EEによるバッチ・アプリケーションを簡単に作ることができる」(グプタ氏)
Java Batchの優れた点は、Jobの実行中に何らかの問題が生じた際、特定のポイントに戻ることのできる「チェック・ポイント機能」を備えているところだ。それぞれのChunk(1つのトランザクションに相当)からもチェック・ポイントに戻ることができ、例えばレコードに問題があり、ReaderがNullを返した場合には、そのレコードの処理をスキップして次のレコードの処理を継続する。また、ProcessorがNullを返した場合には、Writerによる書き込みだけをスキップすることもできる。いずれにせよ、バッチ処理全体を停止させることなく処理が行えるのだ。
さらに、例外処理については、例外が発生した場合にスキップしてもよい例外(skippable-exception)や、リトライする例外(retryable-exception)、ロールバックを行わない例外(no-rollback-exception)などを指定できる。どのレコードをスキップしたかのログを取得することも可能だ。
加えてグプタ氏は、スレッドを分けて並列実行する方法として「Partitioning」機能が用意されていることも紹介した。Partitioning機能では、テーブルをパーティションに分け、プロセッサ・コアごとにスレッドを割り振ることによって処理を高速化するという。
このほか、Java Batchには、Stepの実行順序をフロートとして決める「Flow」や、Flowのセットを並列で実行する「Split」、そしてChunk方式とは異なり処理の単位でタスクを実装するBatchlet方式などが用意されている。グプタ氏は、これらについて説明した後、GlassFish 4にもJava Batchが統合されることを紹介。最後に参加者からの質問に答え、「仕様や実装に対するリクエスト/フィードバックは常に歓迎しているので、ぜひ今回紹介した新機能を試し、それぞれのプロジェクトに対してコメントを寄せていただきたい」と呼びかけて講演を締めくくった。
WebSocket対応により、クライアント/サーバ間で双方向のプッシュが可能になる
米国オラクル・コーポレーション Javaテクノロジーアンバサダーのアルン・グプタ氏
WebSocket対応について取り上げたのは、セッション「ここからはじめる、JSR-356 WebSocket」だ。同セッションでグプタ氏は、HTML5の主要な構成要素の1つであるWebSocketの意義や役割、そしてWebSocketを使ったWebアプリケーションの作成方法を説明した。
講演の冒頭、グプタ氏は現状のWebアプリケーションで使われているHTTP通信の仕組みについて「経済効率が非常に悪い」と指摘した。
こうしたHTTP通信の特性は、特に近年増加している“インタラクティブなWebサイト”を開発する際に大きな問題となりやすい。そこで、これまではServer PushやCometなどの技術を使い、開発者がこれらの問題に個別に対応していたが、グプタ氏によれば、それらは「標準的な方法がなかったがゆえのハックにすぎない」。そうした中、次世代Web開発の標準技術と目されるHTML5に双方向プッシュ技術として取り込まれたのがWebSocketなのである。
「WebSocketを使えば、クライアント/サーバ間で双方向の全二重(Full Duplex)通信が行える。クライアントからのリクエストを起点にし、それに対してサーバがレスポンスを返すという形式にこだわる必要もない。WebSocketが可能にすることで重要なのは、クライアント/サーバ間で行う一連のやり取りを単一のTCPコネクション内で行い、複数のメッセージがやり取りできるようになることだ」(グプタ氏)
WebSocketによるWebアプリケーション開発のポイント
このWebSocketを用いたWebアプリケーション開発のポイントは2つある。1つ目はプロトコルだ。WebSocketのプロトコル仕様はRFC 6455で定義されているが、通信は最初の「ハンドシェイク(Handshake)」とその後の「データ転送(Data Transfer)」によって行われる。
2つ目はJavaScript API(WebSocket API)である。これは現在、W3Cで勧告候補(Candidate Recommendation)となっている。
グプタ氏は、WebSocketを使ったWebアプリケーション開発のデモを交えて、これら2つのポイントを解説した。
まずプロトコルだが、WebSocketによるクライアント/サーバ間通信では、初めに通常のHTTP通信によってコネクションを確立した後、使用プロトコルをWebSocketへとアップグレードする。具体的には、クライアントがサーバとHandshakeを行って通信を確立する際、HTTPヘッダで次のように「Upgrade: websocket」、「Connection: Upgrade」を指定すればよい。
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
サーバがWebSocketに対応していれば、サーバはプロトコルをHTTPからWebSocketにスイッチするレスポンスを返す。
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
以降は、GET、PUTといったHTTPのメソッドは無効となり、「http://」ではなく「ws://」で始まるURLによってクライアント/サーバ間がピア・ツー・ピアにつながり、コネクションをクローズするまでメッセージのやり取りを双方向で行えるようになる。
一方、2つ目のJavaScript APIについて、グプタ氏はWebSocket APIのインタフェースを示しながら、メッセージングはメソッドsendで行い、テキストのほかにBlob、ArrayBufferを送れること、エクステンションによって機能を拡張できることなどを説明した。
【WebSocketのインタフェース】
[Constructor(DOMString url, optional (DOMString or DOMString[]) protocols)] interface WebSocket : EventTarget { readonly attribute DOMString url; // ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSING = 2; const unsigned short CLOSED = 3; readonly attribute unsigned short readyState; readonly attribute unsigned long bufferedAmount; // networking attribute EventHandler onopen; attribute EventHandler onerror; attribute EventHandler onclose; readonly attribute DOMString extensions; readonly attribute DOMString protocol; void close([Clamp] optional unsigned short code, optional DOMString reason); // messaging attribute EventHandler onmessage; attribute DOMString binaryType; void send(DOMString data); void send(Blob data); void send(ArrayBuffer data); void send(ArrayBufferView data); };
それでは、WebSocketを使うと、具体的にクライアント/サーバ間の通信はどの程度効率化できるのだろうか。グプタ氏は、メッセージ送信のパフォーマンスをRESTとWebSocketで比較した結果の例を示した。
それによれば、10バイトのメッセージを100回送信する程度ではRESTとWebSocketにそれほどの性能差は見られないが、100バイトのメッセージを1000回、1000バイトのメッセージを5000回などと増やしていくと、両者の間には数十倍の性能差が生じたという。例えば、1000バイトのメッセージを5000回送信した場合、RESTでは約55秒かかったのに対して、WebSocketでは約1秒で処理が完了した。
Java EE 7では、WebSocket対応機能がWebコンテナに統合され、非常に簡単に利用できるようになる。具体的には、サーバ、クライアントのプログラムに、それぞれ「@ServerEndpoint」、「@ClientEndpoint」というアノテーションを付加するだけでよい。
【サーバ側のプログラムの例】
import javax.websocket.*; @ServerEndpoint("/hello") public class HelloBean { @OnMessage public String sayHello(String name) { return “Hello “ + name; } }
【クライアント側プログラムの例】
@ClientEndpoint public class HelloClient { @OnMessage public void message(String message, Session session) { // process message from server } } WebSocketContainer c = ContainerProvider.getWebSocketContainer(); c.connectToServer(HelloClient.class, “hello”);
また、メソッドに付加する主なアノテーションには、次のようなものがある。
- @onMessage:WebSocketのメッセージ・イベントを捕捉する
- @OnOpen:WebSocketのオープン・イベントを捕捉する
- @OnClose:WebSocketのクローズ・イベントを捕捉する
グプタ氏は、実際にこれらを使った簡単なチャット・サーバのアプリケーションを作り、そのデモを披露した。
【WebSocketを使ったチャット・サーバ・プログラムの例】
@ServerEndpoint("/chat") public class ChatBean { static Setpeers = Collections.synchronizedSet(…); @OnOpen public void onOpen(Session peer) { peers.add(peer); } @OnClose public void onClose(Session peer) { peers.remove(peer); } . . . @OnMessage public void message(String message, Session client) { for (Session peer : peers) { peer.getBasicRemote().sendObject(message); } } }
【関連情報】
- JSR 356:Java API for WebSocket
- GlassFish 4(リファレンス・インプリメーテーションとしてWebSocketをいち早く実装)
「Java Batch」でJava EEがいよいよバッチ処理に対応
グプタ氏が講師を務めたもう1つのセッション「Java プラットフォームにおけるBatchアプリケーション JSR 352」では、Java EE 7で追加される「バッチ機能(JSR 352:Java Bacth)」についての解説が行われた。Java Batchを使用した典型的なアプリケーションのユースケースは、「インタラクティブではなく長い時間がかかるタスクで、何千ものデータベース・レコードを一気に書き換えるような処理」(グプタ氏)である。
広いセッション会場はグプタ氏の解説を聞きに集まった多くの参加者で立ち見が出るほどの盛況を博した
Java Batchには、IBMのWebSphere Compute Grid技術のほか、Spring Batchの技術などが取り込まれている。コンセプトとしては、バッチ処理内で反復実行する「Job」に関する定義(Jobとは何か、どのようなStepがあるのかなど)を格納した「JobRepository」があり、JobRepositoryからJobの定義を「JobOperator」が取り出して「Step」として実行するといったかたちになる。Step内では、「ItemReader」、「ItemProcessor」、「ItemWriter」が、それぞれ「データの読み出し」、「データ処理」、「データの書き込み」を行う。
なお、Jobについては、1日1回実行するものに対してはJobInstanceが生成され、1日のうちに複数回のJobを実行する場合は、JobInstanceから複数のJobExecutionが生成される仕組みになる。
また、Stepには、「Chunk」と「Batchlet」という2つの方式がある。このうちChunk方式は、下図に示すようにRead、Process、Writeを1つのトランザクションとして扱う方式だ。
Jobの定義は「Job Specification Language(JSL)」というXML形式の言語によって記述する。このJobの定義において、タグchunk内でReader(Read処理を行う)、Processor(反復実行する処理を行う)、Writer(結果を書き出す)の実装と実行回数などを指定する。例えば、次の例ではReaderとして口座情報を読み込むaccountReader、Processorとして口座情報を処理するaccountProcessor、Writerとして電子メール送信を行うemailWriterを指定している。
「具体的な実装では、JPA(Java Persistence API)やJavaMailを使うことで、Java EEによるバッチ・アプリケーションを簡単に作ることができる」(グプタ氏)
Java Batchの優れた点は、Jobの実行中に何らかの問題が生じた際、特定のポイントに戻ることのできる「チェック・ポイント機能」を備えているところだ。それぞれのChunk(1つのトランザクションに相当)からもチェック・ポイントに戻ることができ、例えばレコードに問題があり、ReaderがNullを返した場合には、そのレコードの処理をスキップして次のレコードの処理を継続する。また、ProcessorがNullを返した場合には、Writerによる書き込みだけをスキップすることもできる。いずれにせよ、バッチ処理全体を停止させることなく処理が行えるのだ。
さらに、例外処理については、例外が発生した場合にスキップしてもよい例外(skippable-exception)や、リトライする例外(retryable-exception)、ロールバックを行わない例外(no-rollback-exception)などを指定できる。どのレコードをスキップしたかのログを取得することも可能だ。
加えてグプタ氏は、スレッドを分けて並列実行する方法として「Partitioning」機能が用意されていることも紹介した。Partitioning機能では、テーブルをパーティションに分け、プロセッサ・コアごとにスレッドを割り振ることによって処理を高速化するという。
このほか、Java Batchには、Stepの実行順序をフロートとして決める「Flow」や、Flowのセットを並列で実行する「Split」、そしてChunk方式とは異なり処理の単位でタスクを実装するBatchlet方式などが用意されている。グプタ氏は、これらについて説明した後、GlassFish 4にもJava Batchが統合されることを紹介。最後に参加者からの質問に答え、「仕様や実装に対するリクエスト/フィードバックは常に歓迎しているので、ぜひ今回紹介した新機能を試し、それぞれのプロジェクトに対してコメントを寄せていただきたい」と呼びかけて講演を締めくくった。
【関連情報】
- JSR 352:Java Batch
- GlassFish 4(リファレンス・インプリメーテーションとしてJava Batchに対応)