JavaOne 2015 4日目メモ (10/28)
JavaOne 4日目の夜には、毎年トレジャーアイランドでOracle Appreciation Event(オラクルによる感謝祭?)が行われ、有名な方のライブが行われる。今年はエルトンジョンとBECKだった。毎年寒いので、薄手のダウンなど暖かい服装が必須。
4日目は以下のセッションに参加。
- [CON2709] The New HTTP Client API, Including HTTP/2 and WebSocket
- [CON2809] Deep Dive into Top Java Performance Mistakes in 2015
- [CON6446] WebSocket in Enterprise Applications
- [CON6856] Saving the Future from the Past
[CON2709] The New HTTP Client API, Including HTTP/2 and WebSocket
JEP110で提案されている、Java SEに盛り込む新しいHTTPクライアントについて。JDK9には、HttpUrlConnectionに代わるHTTPクライアントを盛り込む計画があり、HTTP1.1/HTTP2/WebSocketのクライアントがサポートされる予定である。
セッションの前半はHTTP2の説明と、Java8から導入されたComputableFutureの振り返りであったため省略。後半から、具体的なAPIのアイディアが紹介された。いずれもまだOpenJDK9のリポジトリに含まれていないため、まだ変更が入る可能性が高い。
HTTP GETし、結果を文字列(HttpResponse.asStringメソッド)で取得:
HttpResponse resp = HttpRequest .create(new URI("http://www.foo.com/") .GET() .response(); if (reap.statusCode() == 200) { String responseBody = resp.body(HttpResponse.asString()); System.out.println(System.out.println(responseBody); }
POSTする場合は、以下のようにbodyメソッドに設定する:
HttpResponse resp = HttpRequest .create(new URI("http://www.foo.com/") .body(HttpRequest.fromString("param1=1,param2=2") .POST() .response();
非同期実行。レスポンスボディをファイルに書き出す:
ComputableFuture<Path> future= HttpRequest.create(uri) .GET() .responseAsync() .thenApplyAsync(resp -> { if (reap.statusCode() == 200) { return reap.body(asFile(path)); } else { throw new UncheckedIOException(new IOException()); } }); //=> このメソッドチェーンの返り値はComputableFuture<Path> future.join(); //=> thenApplyAsyncが完了するまでブロック
ExecutorServiceを渡せるので、Java EE環境でもComputableFutureが使いやすい:
(APサーバが管理しているスレッドプールManagedExecutorが渡せる)
HttpClient client = HttpClient.create()
.executorService(Executors.newCachedThreadPool())
.build();
HttpRequest request = client.request(URI.create("https://www.foo.com/")).GET();
HTTP2でリクエストするときは、メソッドチェーンに追加する:
HttpRequest request = client.request(URI.create("https://www.foo.com/"))
.version(HttpClient.Version.HTTP_2)
.GET();
セッションの中ではリソースクローズに関する言及がなかったが、HttpUrlConnectionのように、クリーンアップをどうやるかわかりくいAPIにならないことを願っている。
[CON2809] Deep Dive into Top Java Performance Mistakes in 2015
性能解析ツールのdynatrace社の方による、どんなメトリクス(ページサイズ、HTTPエラーレスポンス数などの指標)が、性能遅延に直結しやすいか実例を交えながら紹介するセッション。セッションではdynatraceによる以下のようなメトリクス収集結果のデモが紹介された。
メトリクス1: 画像などのリソース数、サイズに注意
- アメフトのスーパーボウルのページは、1ページに434の画像などのリソース、合計20MBのサイズによりレスポンス遅延が発生していた
- FIFAワールドカップのページで、faviconアイコンが370KBを超え、そのほかにも150KBを超えるCSSが複数あって遅延
- 大きな背景画像もかっこいいが、サイズには注意が必要
- 遅いと思ったら、まずブラウザF12のデバッグツールで見てみる
メトリクス2: SQL大量row取得、同一SQLの繰返実行(N+1)、大量のコネクション生成
- Room Reservertion System(会議室予約?) で1リクエストで2万回以上のSQL発行 (典型的なORMのN+1問題)
- 1万以上のコネクション生成 (プールによるコネクション再利用漏れ)
- データキャッシュをHashTableに全て置いて、ブロック多発による性能劣化
メトリクス3: 外部サービスの呼び出し数、WebAPI実行時のN+1問題
- 100回以上の外部WebAPIの繰り返し呼出
- ORMのN+1問題と同様に、キーごとに毎回APIコールすると同じ問題が起こる
メトリクス4: ロギング量
メトリクス5: GC回数、ライブヒープの増加
- アプリケーションの改修に伴いライブヒープが一気に増加
- 低負荷時のレスポンスタイムに変化はないが、高負荷時にライブヒープ増加に伴いCPUネックが顕在化し、大幅なレスポンス低下
他にも確認した方が良いデータ:
ここからはセッション内容と関連しないメモ。有償であるdynatraceを使わずにELK(ElasticSearch+Logstash+Kibana)でメトリクスが収集できないか考える。
- フロントエンド
- DB関連
- slow queryはログから取れる。
- ログからのN+1の検知は難しそう。今まで通り、スロークエリが出てないがDBアクセスクラスで遅い場合は、デバッグ用途でORMのSQL出力を有効化するしかない?
- コネクション数も、例えばPostgreSQLであればpg_stat_databaseビューのnumbackendsを拾ってくればいけそう。
- 連携サービスのRPC呼び出し過多
- ロギング過多
- プロファイラ使わずにログからとなると dfによるディスク使用量ぐらい?
- GC回数、ライブヒープの増加
- OSリソース系
- ELK構成でsar, df, proc xxx を取り込む以外にも、RHEL7(および6.7)以上であれば、Performance Co-Pilot + Netflix vectorの選択肢がある。vectorがELKより便利そうかは要調査。
[CON6446] WebSocket in Enterprise Applications
WebSocketの導入セッション。ゲーム以外のWebSocketの使い道をイメージするために聴講。前半はLong polling/SSE/WebSocket/HTTP2の違いについてだったので省略。
WebSocketを何に使うか:
いくつかの例が紹介された。
- サーバプッシュ
- 頻繁にリクエスト&レスポンスがある処理。株トレーディングなど。
- リモートコントロール。リアルタイムでフィードバックを受けながら操作するもの。機械操作など。
- リアルタイムモニタリング。
- バックエンドサーバ間の高速なリクエスト&レスポンス。
- メモ: 高速RPC用途であれば、grpcなどの選択肢もあると思う
WebSocketのサブプロトコル:
WebSocket自体は、ブラウザの世界にソケット接続を持ってきたものなので、アプリケーション固有のjson以外にも、サブプロトコルがいくつか出ている。
WebSocketのクラスタ化:
複数のサーバに分かれてWebSocket接続する場合は、他のサーバで接続するWebSocketの存在を考慮する必要がある。たとえば一斉通知する場合、Java EEであればJMSやJCacheによってWebSocketのサーバ間連携を実現する。また、サーバサイドでのフェールオーバは難しいため、障害時はクライアントから再接続する。
[CON6856] Saving the Future from the Past
Javaの非推奨(@Deprecated)の歴史と今後について紹介するセッション。
JJUG CCC 2014 Springで講演されていた、オラクルのStuart Marksさんが"Dr. Deprecator"として白衣を着て発表。
Deprecatedの歴史:
- 歴史は古く、JDK1.1から既に非推奨は存在
- 当初はJDK1.1で新しく多くのAPIが導入されたために、古いものを捨てる(abandon)ため、代わりのもっと良いもの(supersede)が出ていることを、ユーザに知らせる仕組みとして導入された。
- ユーザに新しいものに移行したもらうためのものだった。
Deprecatedの意図の多様化:
当初は今後はメンテしない(abandon)、代わりに良いのがある(supersede)がDeprecatedの意図であったが、現在ではもっと多用な意図でDeprecatedが使われている。
- CONDEMNED: 今後削除される可能性がある
- DANGEROUS: データが壊れたり、デッドロックが起こる可能性がある。
- DANGEROUSの代表例はThread.stop()
Deprecatedこそ付いていないものの、Vectorなどの古いAPIは、ユーザにとって"非推奨"と見られている。
Deprecatedに気が付かないパターン:
コンパイル警告、JavaDoc、IDEの取り消し線でユーザに知らせているが、JDKの移行の仕方によっては気が付かないパターンもある。再コンパイルせずにバイナリ互換を活かしてJDKのバージョンアップをした場合はユーザはわからない。
APIのライフサイクル:
以下のようなフローが理想とされているが、Java SEの実態は異なる。
- 新しいAPIが導入され、古いAPIが非推奨になる
- しかし実態は、多くの人は非推奨警告を無視する
- ユーザは新しいAPIに移行する
- 実態は様々な理由からなかなか移行しない
- 移行が済んだころに、APIは削除される
- 実態は、JDK9となってもほとんどのAPIは削除されていない。
- APIの削除は究極の非互換である
- ソース互換性はコンパイルが通らず壊れる、バイナリ互換性もNoSuchMethodErrorで壊れる、振る舞い互換性もAPIがないのでもちろん壊れる
- なぜ無くなったのか、何がなくなったのかjavadoc化が困難
これからのDeprecated:
- 非推奨の理由カテゴリを再整理し、enum型で表現したい。
- UNSPECIFIED(特になし)
- CONDEMNED(将来的に削除予定)
- DANGEROUS(リスクあり)
- OBSOLETE(時代遅れで古い)
- SUPERSEDED(もっと良い代替がある)
- UNIMPLEMENTED(実装なし、呼ぶとUnSupportedOperationExceptionになるので注意)
- @Deprecatedのアノテーション要素として情報が補足できるようにしたい。
@Deprecated(value={DANGEROUS,SUPERSEDED}, replacement="String#getBytes()", since="1.1") public void getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin)
- ランタイム時にも@Deprecatedの警告を出して、ユーザに通知したい
- javadocの"All Methods"から@Deprecatedのメソッドを削除して、ユーザに存在を知らせないようにしたい
- IDEが非推奨APIの利用をもっと自動置換してくれるようになって欲しい
- しかし、スレッドセーフを意図してHashTableを使っているなど、単純な置き換えはバグを招く
- これらの提案は JEP 277: Enhanced Deprecation にまとめられている。
- JDK9への盛り込みを目指して検討中。