Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Javaの進化にともなう運用性の向上はシステム設計にどういう変化をもたらすのか

7,917 views

Published on

Java Day Tokyo 2016 のセッション3-Eの資料です。

Java8, Java9の新機能がシステムの設計にどういう影響があるのかを考えてみました。

Published in: Software
  • Be the first to comment

Javaの進化にともなう運用性の向上はシステム設計にどういう変化をもたらすのか

  1. 1. Javaの進化にともなう 運用性の向上はシステム設計に どういう変化をもたらすのか TIS株式会社 アプリケーション開発センター 川島 義隆 Java Day Tokyo 2016 3-E
  2. 2. Javaの機能進化おさらい
  3. 3. Java8 ● Lambda expression ● Method reference ● Default methods ● Stream API ● Optional ● Map#computeIfAbsent
  4. 4. Java9 ● Jigsaw ● JShell ● HTTP/2 ● SO_REUSEPORT
  5. 5. シンタックスの進化に 目が奪われがちだけれども
  6. 6. 設計のやり方も アップデートしましょう
  7. 7. Embrace null Java8 〜
  8. 8. NullPointerExceptionの典型例 HttpSession session = request.getSession(); User user = session.getAttribute("user"); if (user.isAdmin()) { // ... } ここでNullPointerException発生 引数なしでgetSessionを呼ぶのでセッションがない 場合は空のものが作られる 空のSessionの場合、userはnullになる。
  9. 9. Guard against null HttpSession session = request.getSession(false); if (session == null) { response.sendRedirect(LOGIN_URL); return; } User user = session.getAttribute("user"); if (user == null) { response.sendRedirect(LOGIN_URL); return; } if (user.isAdmin()) { // ... } nullガード戦法 Java有史以来、最も多く使われている戦術だが、 ガードの漏れが検出しにくく、実際それが本番障害の 原因としてたびたび登場している
  10. 10. Optional Optional.empty(value) Optional.ofNullable(value) Optional.of(value) optional.get() optional.orElse(T other) optional.orElseGet(Supplier other) optional.orElseThrow(Supplier exceptionSupplier) ここの間が重要 Optionalの生成 Optionalから値の取り出し
  11. 11. Optionalの意義 User user = Optional .ofNullable(request.getSession(false)) .map(session -> session.getAttribute("user")) .orElse(new AnonymousUser()); if (user.isAdmin()) { // ... } Operation中にnullが出現しうる一連の操作を、 Optionalで包みこむこと
  12. 12. Embrace null 標準APIがOptional前提で設計されていないJavaにおいては、 Optionalを使ってどうAPIを設計しようかと考えるよりも 一連のnull発生箇所を包み込むことを主眼において考 えた方が実用的である。 ラムダの扱いをもう少し工夫した事例 http://qiita.com/kawasima/items/744914c2fb0b81686a9c
  13. 13. Mixin Java8 〜
  14. 14. Default method ● インタフェースに実装が持てるようになった ● インスタンス変数は当然ながら持てない default void forEach(BiConsumer<? super K, ? super V> action) { Objects.requireNonNull(action); for (Map.Entry<K, V> entry : entrySet()) { K k; V v; try { k = entry.getKey(); v = entry.getValue(); } catch(IllegalStateException ise) { // this usually means the entry is no longer in the map. throw new ConcurrentModificationException(ise); } action.accept(k, v); } }
  15. 15. Use case ミドルウェアパターン Request TraceMiddleware AuthenticateMiddleware Request writeLog() setRequestTime() Request writeLog() setRequestTime() setUserPrincipal() 適用するミドルウェアに応じて、メソッドを追加したい
  16. 16. 多重継承できないJavaでは… 従来は継承でやるしか無かったので、使う使わざる に関わらず、多くのメソッドを予め用意しておく設計 が多い java.sql.ResultSetは実に、193のメソッドをもつ 更新可能ResultSetの機能や、全てのJDBC型のメ ソッドを持つため。
  17. 17. Mixin using default methods public interface Traceable { Logger LOG = LoggerFactory .getLogger(Traceable.class); default void writeLog() { LOG.info(toString()); } } こういうインタフェースを用意して、implementsすれば、 writeLogメソッドが使えるようになる。
  18. 18. Default methodでインスタンス変数を 持てないことへの対策 任意のデータを出し入れするインタフェースを用意して、 public interface Extendable { Object getExtension(String name); void setExtension(String name, Object extension); } public interface Traceable extends Extendable { default void setRequestTime(Long time) { setExtension("requestTime", time); } } デフォルトメソッドではそのインタフェースとのやり取りをする。
  19. 19. private Map<String, Object> extensions; @Override public void setExtension(String name, Object extension) { if (extensions == null) { extensions = new HashMap<>(); } extensions.put(name, extension); } @Override public Object getExtension(String name) { if (extensions == null) { extensions = new HashMap<>(); } return extensions.get(name); } It's not beautiful, but pragmatic. 実装をこんな感じにすれば、データを持つMixinが実現可能
  20. 20. 適用するミドルウェアによって implementsするインタフェースが決まる。 ミドルウェアが要求するインタフェースを 動的にMixinできるとよいのでは?
  21. 21. 動的mixin 予めインタフェースをimplementsしてなくても動的に implementsして振る舞いを足す。 Proxy.newProxyInstance( classloader, new Class[]{ Request.class, Traceable.class }, new MixinProxyHandler());
  22. 22. Default methodの呼び出し MethodHandles.lookup() .in(declaringClass) .unreflectSpecial(method, declaringClass) .bindTo(proxy) .invokeWithArguments(args); MethodHanldeを使うと、実装してないクラスに対して もdefault methodが呼び出せる。 http://qiita.com/kawasima/items/f735ef0c0a9fa96f6eb4
  23. 23. 注意事項 ● Java8のJVM実装では、MethodHandleによる invokeSpecialは非常に遅い。 ● 通常のリフレクションの10倍〜100倍のコスト 開発時は、試行錯誤するので動的Mixinを使い 運用時は、性能を優先し静的Mixinを使う。 という戦術がよいのでは。
  24. 24. 多重継承できないことに起因する - クラス数の増加 - メソッドを沢山持つクラス を抑制できる。 Mixinの効能
  25. 25. システム挙動の動的な変更 (using REPL) Java8 or 9〜
  26. 26. Use case アプリケーションの一部サービスを閉鎖し、 Sorryページにリダイレクトする 503 Service Temporarily Unavailable
  27. 27. 従来よく見られた設計 こんなテーブルを用意し、閉塞するときにはフラグを "1"にアップデートする 全リクエストにおいて、この閉塞テーブルを 検索するSQLが実行されてしまう… 機能ID 閉塞フラグ 閉塞 �
  28. 28. 管理用APIを用意する これは最近のJavaでないとできないわけではないけれども… 「閉塞状態であること」を永続化する必要はないので メモリ内の閉塞フラグを書き換えてやればよい https://[host]:[port]/admin/service/close 限られた工数で、管理用のAPI用意するのが 難しい場合もある � @GET public String closeService(@QueryParam String serviceCd) { serviceClosingService.add(serviceCd); return "OK"; }
  29. 29. REPLがあれば大丈夫 (な日がくるかもしれない)
  30. 30. Enkanフレームワークの擬似REPL enkan> /middleware app list ANY defaultCharset (enkan.middleware.DefaultCharsetMiddleware NONE serviceUnavailable (enkan.middleware.ServiceUnavailableM ANY stacktrace (enkan.middleware.StacktraceMiddleware@545872d enkan> /middleware app predicate serviceUnavailable ANY enkan> /middleware app list ANY defaultCharset (enkan.middleware.DefaultCharsetMiddleware ANY serviceUnavailable (enkan.middleware.ServiceUnavailableMi ANY stacktrace (enkan.middleware.StacktraceMiddleware@545872d REPL上からMiddlewareの適用条件を操作できる https://enkan.github.io/
  31. 31. Enkanフレームワークの起動・再起動時間 REPLからの起動 → 1~3 sec     再起動 → < 1sec MTTRを短くしようという時代の流れの中で、 アプリケーションの起動の速さはより重要になっていく http://www.slideshare.net/kawasima/enkankotowarirepl 速さの秘密はこちらをご覧ください
  32. 32. JShellはどうなっているのか? ● コマンドのカスタマイズはできない ● 動いているプロセスにアタッチできない https://bugs.openjdk.java.net/browse/JDK-8157208 Java10では、このあたりが解消するかも。
  33. 33. Try Artifact JShellの中で、Mavenの依存関係を動的に解決し、その 機能を試してみることができる。 -> /resolve org.apache.commons:commons-lang3:jar:3.4 | Path /home/kawasima/.m2/repository/org/apache/commons/commons- lang3/3.4/commons-lang3-3.4.jar added to classpath -> org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(10) | Expression value is: "peXXUYGin6" | assigned to temporary variable $1 of type String https://github.com/kawasima/try-artifact  現段階でのJShellカスタマイズ事例
  34. 34. Java9時代の無停止デプロイ Java9 〜
  35. 35. Java8までの無停止デプロイ Server#1 WebApplication Load balancer Server#2 WebApplication 縮退しながら、1台ずつリスタート 少なくとも2台の仮想サーバが必要�
  36. 36. Application serverの無停止デプロイ機能 Server WebLogic Server WebApplication WebApplication プロダクション再デプロイメント 前述のサーバでやっていたことをWebLogic Serverの中で実現する アプリケーションの作りによってはメモリリークが 発生してしまう�
  37. 37. Server Java9時代 Process WebApplication Process WebApplication 同一サーバ内で同じポートをListenするプロセスを複数立ち上げ、 順次再起動する
  38. 38. SO_REUSEPORT ● 複数のプロセスから同じポートをListenできる – 従来は"Address already in use:bind"になっていた ● 同じuidである必要がある。 ● Linux kernel 3.9より利用可能 (WindowsではUnsupportedOperationException)
  39. 39. SO_REUSEPORTを使うコード ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.setOption( StandardSocketOptions.SO_REUSEPORT, true); setOptionメソッドがJava 1.7からなので、各Webサーバプロダクト (Jetty, Undertow, and so on)は直ぐには呼び出せない
  40. 40. SO_REUSEPORT in Jetty class ReusePortAvailableConnector extends ServerConnector { public ReusePortAvailableConnector(@Name("server") Server server) { super(server); } @Override public void open() throws IOException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.setOption(StandardSocketOptions.SO_REUSEPORT, true); InetSocketAddress bindAddress = getHost() == null ? new InetSocketAddress(getPort()) : new InetSocketAddress(getHost(), getPort()); serverChannel.socket().setReuseAddress(getReuseAddress()); serverChannel.socket().bind(bindAddress, getAcceptQueueSize()); serverChannel.configureBlocking(true); addBean(serverChannel); try { Field acceptChannel = ServerConnector.class .getDeclaredField("_acceptChannel"); acceptChannel.setAccessible(true); acceptChannel.set(this, serverChannel); } catch (Exception ex) { throw new IOException(ex);
  41. 41. Falchion container ● JVMプロセスのコンテナ ● Anotherプロセスが起動するのを待ってから、古い JVMプロセスをKillできる。 ● SO_REUSEPORTとの組合せで、リクエストをロストす ることなく、論理的なアプリケーションの再起動が 可能になる。 https://github.com/kawasima/falchion experimental
  42. 42. Falchion Container Falchion container architecture JVM real process WebApplication JVM pool JVM virtual process JVM virtual process JVM real process WebApplication Listen the same port
  43. 43. Monitoring JVM ● JMXによるDropwizard Metricsの取得 – Request数 – Error数 – スループット ● jstatによるGC Metricsの取得
  44. 44. REST APIでの状態取得
  45. 45. アプリケーションの再起動が より手軽で身近なものに アプリケーションの再起動 = 一大事 当然ながらご利用は慎重かつ計画的に…
  46. 46. JVM auto tuning ● JVMのパラメータを少しづつ変えながら、性能の変 化を計測する。 ● 最適なものを選択 – Full GC countを最小に – 1回あたりのFull GC Timeが最長のものが一番短く (Full GC Timeのマクシミン) JVMコンテナの活用可能性
  47. 47. 『柔軟な設計』の変化
  48. 48. これまでに手にした武器 ● 無停止デプロイの気軽さ ● 高速な起動 ● システムの挙動の動的変更
  49. 49. 課題設定 ETC割引の計算ロジックを実装します。 – ただし、平日朝夕割引は実際には後日還元なの ですが、ここでは他の割引と同じく即時適用かつ 走行距離による還元率の変化はないものとしま す。 – 走行記録は、24時間を超えないものとします。 https://github.com/kawasima/kata/tree/master/ex01-business-rules http://www.driveplaza.com/traffic/tolls_etc/ より。 ルールは簡単のため多少いじっています。
  50. 50. 割引ルール ● 平日朝夕割引 – 平日「朝:6時〜9時」、「夕:17時〜20時」 – 地方部  – 当月の利用回数が5回〜9回 30%割引、10回以上 50%割引 ● 休日割引 – 普通車、軽自動車等(二輪車)限定 – 土曜・日曜・祝日 – 地方部 – 30%割引 ● 深夜割引 – すべての車種 – 毎日0〜4時 – 30%割引
  51. 51. システム運用負荷を下げたいので… できるだけ止めずに
  52. 52. 従来の柔軟な設計 ルールをRDBMSに登録し、これを 読み込んで、割引率を算出する。 データを追加・変更すればシステム改修 なしに、ルールの追加・変更が可能だ!! ルールID ルール名 開始時刻1 終了時刻1 開始時刻2 終了時刻2 月曜日フラグ 火曜日フラグ   ︙ 日曜日フラグ 走行エリア 割引率 適用開始日 適用終了日 ルールID 車種コード 車種コード 車種名 ルール 許容車種 車種
  53. 53. 実際運用してみると発生する課題 ● 1つのルールで、時間帯を3つ以上持ちたいという要件。 増えてくカラム。 ● 用意していない属性(排気量やハイブリッド車優遇など)を 使ったルールの追加。増えてくカラム。プログラム改修。 ● 予期しないデータが入って、Exceptionが発生。 増えていく再発防止策。 ● 結局ルールを変更するのも開発チームに 依頼してやってもらう運用に…
  54. 54. 「自由度の高いシステムを作ったぜ」 という思惑とは裏腹に… ● 高すぎる自由度は、十分にテストするのが難しい ことを意味する ● 特にデータの組合せにテストケースが隠されると、 十分なテストが出来てないことが多くなる
  55. 55. 再起動が容易であれば… ● ふつうにルールの変更にしたがい ● コードを書いて ● テストを書いて ● 安全に簡単にデプロイしてしまえばよい。
  56. 56. ルールの実装例 public class DiscountInMorningOrEvening implements DiscountRule { private RulePeriod morning = new RulePeriod(6, 9); private RulePeriod evening = new RulePeriod(17, 20); @Override public boolean isApplicable(HighwayDrive drive) { return ((!morning.isHoliday(drive) && morning.isIn(drive)) || (!evening.isHoliday(drive) && evening.isIn(drive))) && drive.getRouteType() == RouteType.RURAL; } @Override public long discountPercentage(HighwayDrive drive) { int count = drive.getDriver().getCountPerMonth(); if (count >= 10) { return 50; } else if (count >= 5){ return 30; } else { return 0; }
  57. 57. YAGNI has come to Java 運用の大変さ(のイメージ)から、過剰設計になりがち だったJavaの世界も、今必要なものだけを設計し作れ ばよい時代に
  58. 58. まとめ
  59. 59. 堅い、固い、硬いイメージのJavaも、工夫次第で は、時代に合わせて柔軟な運用・設計が可能 シンタックスのキャッチアップだけでなく、 設計の手法のアップデートもお忘れなく

×