Why is the Doomsday Clock set at 2 minutes to midnight? | IPPNW peace and health blog
⇧ 「原子力科学者会報」による、「世界終末時計」の時刻、2019年は、残り2分ってことに決まったそうですね、「ウォッチメン(監督:ザック・スナイダー)」は何をやっとるか~!あ、どうも、ボクです。
カップラーメンを作ったとしても、完成する前に世界が終わるんだと...
時をかける少女 期間限定スペシャルプライス版 [Blu-ray]
というわけで、「時をかける少女(原作:筒井康隆)」のように現実の時間は止められないし、タイムリープで時間を遡ることもできないけれど、それでも、『嵐の中で輝いて その夢をあきらめないで~♪(【アニメ:機動戦士ガンダム 第08MS小隊】『米倉千尋「嵐の中で輝いて」』)』ということで、プログラミングの閉じた世界の中では、世界の終末を永遠に来ないようにすることは可能であると。
そんなわけで、今回も Java の話です。
Java でタイムアウト なんかを考える
外部と通信するようなシステムの構築になってくると気になるのが、通信が上手くいかなかった時に、処理が止まってしまうことですね。
そんな時にも、システムを稼働させ続ける、それが一番大事~♪ ということで、恋に障害はつきもの、でも恋をしたことないの!という話は置いといて、障害が起こったとしても、システムを止めないで~、っていう設計を、
フォールトトレラント設計(障害許容設計)(フォールトトレラントせっけい、Fault tolerant design)は、システム設計の手法であり、システムの一部に問題が生じても全体が機能停止するということなく(たとえ機能を縮小しても)動作し続けるようなシステムを設計するものである。
この用語はハードウェアあるいはソフトウェアの障害があってもほとんど途切れることなく動作し続けるコンピュータシステムの設計を指して使われることが多い。
⇧ 「フォールトトレラント設計」という らしいですぞ。通信が絡んでくるシステムの「タイムアウト」「リトライアウト」なんかもそういう概念ってことで良いのかしら?
株式会社リコー 様の記事の画像を流用させていただきました。
⇧ 上記の図のように、対外システム(この場合、「データ管理サーバ」)なんかへ接続を試みた時に、対外システムからのレスポンス(応答)が いつまで経っても戻ってこない時、Webサーバでは処理待ち状態で、処理が止まってしまうと。
そこで、そういった状態に陥った時に、「タイムアウト」と判断して、次の処理に遷移させようというのが、「タイムアウト」の処理であると。
ちなみに、「リトライアウト」は、「タイムアウト」した場合に、何回か通信を試みるものらしいです。
⇧ 上記の場合は、タイムアウトの回数とかを、Webサーバで管理して、「リトライアウト」にするかどうかを判断してるようなイメージですかね。
概ね、「リトライ」を3~4回して駄目な場合を、「リトライアウト」とすることが多いようです、シーケンス図を描くのがしんどかったので、上記の図では、1回で「リトライアウト」しちゃってますが。
そんな通信の状態を管理するのにうってつけのライブラリが「Resilience4J」というものらしいです。
⇧ 上記サイト様が詳しいです。上記サイト様で、「Resilience4J」というライブラリの存在を知りました。
By the way、非同期処理ってものが存在するじゃない?
だが、しかし!
非同期処理とかにしたところで、どっちにしろ、ずっと通信の待ち状態で放置しておくわけにもいかないし、それに、いまのところの Java だと非同期処理が、OSに依存してしまうんでした。
⇧ OSに依存しない非同期処理の実現に向けて、「Project Loom」というプロジェクトが進行中ではあるらしいけど、3年後ぐらいになるのではと言われていますね。
なので、これだけは待つけど、っていう時間を決めてしまって、それ以上の時間が経過したタイミングで強制的に、通信を無効とし、次の処理をしてしまおうというのがタイムアウトの考えってことですかね。
というわけで、「Resilience4J」の出番です!
⇧ 上記サイト様によりますと、「リングバッファ」というもので管理されてるそうです。
「Resilience4J」って?
GitHubに公開されている「Resilience4J」の説明によると、
Resilience4j is a lightweight, easy-to-use fault tolerance library inspired by
Netflix Hystrix, but designed for Java 8 and functional programming. Lightweight, because the library only uses Vavr, which does not have any other external library dependencies. Netflix Hystrix, in contrast, has a compile dependency to Archaius which has many more external library dependencies such as Guava and Apache Commons Configuration.
⇧ 「Netfix Hystrix」にインスパイアされた「フォールトトレラント設計」なライブラリで、Java 8 から導入された関数型インターフェイスをバリバリ使ってると。依存関係も「Vavr」ってライブラリだけだから、軽くて速いらしい。
Resilience4j provides higher-order functions (decorators) to enhance any functional interface, lambda expression or method reference with a Circuit Breaker, Rate Limiter, Retry or Bulkhead. You can stack more than one decorator on any functional interface, lambda expression or method reference. The advantage is that you have the choice to select the decorators you need and nothing else. With Resilience4j you don’t have to go all-in, you can pick what you need.
⇧ 使いたい機能だけ、必要に応じて導入することができると...う~ん、どれが必要か選ぶのが難しい気がするんだけど...
「Resilience4J」を使ってみる
さっそく使ってみますか。
今回は、ビルドツールのGradle で外部ライブラリを管理していこうということで、
⇧ Gradle の場合の導入の方法を試しました。
⇧ EclipseでGradleプロジェクトの新規作成は、上記サイト様を参考に
させていただきましたが、途中、他のサイト様も参考にさせていただきました。
Gradle について、よく分かっていないため、かなりカオスな内容になってしまっていますが、ご了承ください。
では、Eclipseを起動し、
「ファイル(F)」>「新規(N)」>「Gradle プロジェクト」で。(「Gradle プロジェクト」が表示されてない場合は、「その他(O)...」を選択した先で、「Gradle」>「Gradle プロジェクト」を選択。)
「プロジェクト名」を適当に入力し、「次へ(N) >」。
プロジェクトを新規開発するという体で、「ワークスペース設定を上書き」にチェックし、「Gradle バージョンの指定」で。(「Resilience4J」に必要なGradleのバージョンとかの記載が特にないので、選択できる一番新しいバージョンにしちゃいました。)
「次へ(N) >」。
途中、コンソールに表示されてたんだけど、処理中で選べないけど...
そして、「プレビュー」が一向に終わらない...
をクリックで、プレビュー中断しました...Eclipse ブラックボックス過ぎるでしょ...
「完了(F)」で。
で、Java が使えるように...なって無くない?
⇧ 「Gradleタスク」の「init」ってのを実行することで、Java プロジェクトができるらしい...
というわけで、「Gradle タスク」の該当するプロジェクトを選択し、「build_setup」>「init」を右クリックし、「Gradle タスクの実行」で。(「Gradle タスク」が表示されてない場合は、「ウィンドウ(W)」>「ビューの表示(V)」>「その他(O)...」を選択し、「Gradle」>「Gradle タスク」で)
で、一向にタスクが終わらない...何で?
⇧ 「init」は終わってたらしい...
とりあえず、「init」は停止させます。
理由は分からんのだけれども、どうやら、JAVA_HOME が認識されていなかったので、Javaプロジェクトの雛型が作成されなかったらしい?
⇧ 上記サイト様によりますと、
- プロジェクトのルートに配置した
gradle.properties
- Gradle ホームフォルダ(
<ユーザのホームフォルダ>\.gradle
)に配置したgradle.properties
のいずれかに、JAVA_HOME を設定してあげれば良いようです。
なので、プロジェクトのディレクトリ直下に、「gradle.properties」ファイルを作成で。
そしたらば、gradle.properties ファイルができますんで、使用したいJavaまでのパスを設定していきます。
自分は、「jdk1.8.0_211」を指定しようと思います。
Gradle で使用するJAVA_HOMEを指定で。
org.gradle.java.home=[Javaまでのパス]
⇧ build.gradle に追記が必要らしい。
ということで、プロジェクトのディレクトリ直下に、「build.gradle」ファイルを作成で。ファイルの内容は、以下のような感じで。
// Apply the java plugin to add support for Java apply plugin: 'java' apply plugin: 'eclipse' task initSourceFolders { // add << before { to prevent executing during configuration phase sourceSets*.java.srcDirs*.each { it.mkdirs() } sourceSets*.resources.srcDirs*.each { it.mkdirs() } }
んで、「Gradle タスク」でプロジェクトのディレクトリの中の、「build_setup」>「init」を実行で。
「パッケージエクスプローラー」でプロジェクトのディレクトリを選択した状態で右クリックし「Gradle」>「Gradleプロジェクトのリフレッシュ」で。
ようやく、見慣れたJavaのWebプロジェクトの雛型のディレクトリ構成になりました。
あらためて、「Resilience4J」を使ってみる
Gradleの情報が錯綜し過ぎてて、準備に時間がかかってしまいました...なんとか「Resilience4J」を導入する準備ができたということで、
で、build.gradle の記述で、超絶な泥沼にハマった(涙)。Gradleの勉強しないと駄目ですな、ネットの情報が錯綜し過ぎてて、どれが本当の情報か分からん...
ちなみに、Gradle 5系から、Gradle 5系以前の記述で非推奨になるものとか出てきたらしい...もうついていけんっすわ...
「build.gradle」に追記します。
// Apply the java plugin to add support for Java plugins { id 'java' id 'eclipse' } // 何か知らんけど、これないとJavaのプロジェクトの雛型(スケルトン)ができなかったので task initSourceFolders { // add << before { to prevent executing during configuration phase sourceSets*.java.srcDirs*.each { it.mkdirs() } sourceSets*.resources.srcDirs*.each { it.mkdirs() } } // ライブラリの取得先リポジトリ repositories { jcenter() mavenCentral() } // build.gradleのファイル内で使える変数を定義できるらしい ext { versions = [ 'resilience4j' : '0.16.0', 'junit' : '5.3.1' ] } dependencies { // 変数を含む場合は、「"」で囲んでやらないと機能しない、「'」はNG(涙)。 // resilience4j implementation "io.github.resilience4j:resilience4j-circuitbreaker:${versions.resilience4j}" implementation "io.github.resilience4j:resilience4j-ratelimiter:${versions.resilience4j}" implementation "io.github.resilience4j:resilience4j-retry:${versions.resilience4j}" implementation "io.github.resilience4j:resilience4j-bulkhead:${versions.resilience4j}" implementation "io.github.resilience4j:resilience4j-cache:${versions.resilience4j}" implementation "io.github.resilience4j:resilience4j-timelimiter:${versions.resilience4j}" // JUnit 5 系からは、「JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage」ってことで、 // 「Jupiter」、「Vintage」って2つに分かれてるらしい。後方互換性がるのが「Vintage」らしい。 testImplementation "org.junit.jupiter:junit-jupiter-api:${versions.junit}" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${versions.junit}" }
んで、あらためて、「Gradle タスク」でプロジェクトのディレクトリの中の、「build_setup」>「init」を実行で。
ようやく、「Resilience4j」が読み込めました。
そんでは、クラスファイルを作成していきます。
⇧ 上記サイト様のコードを写経させていただきました。
クラスは、2つ作成しました。
ソースコードはこんな感じ。
「/TestResilience4J/src/main/java/external/TimeLimiterUtil.java」
package external; import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import io.github.resilience4j.timelimiter.TimeLimiter; import io.github.resilience4j.timelimiter.TimeLimiterConfig; import io.vavr.control.Try; public class TimeLimiterUtil { private final ExecutorService threadPool = Executors.newCachedThreadPool(); // タイムアウトの設定 private final TimeLimiterConfig config = TimeLimiterConfig.custom() .timeoutDuration(Duration.ofMillis(2000)) .cancelRunningFuture(true) .build(); // 処理を実行 public String callExternalSystem() { Callablecallable = TimeLimiter .decorateFutureSupplier( TimeLimiter.of(config), () -> threadPool.submit(() -> innerExternalSystem())); return Try.of(callable::call) .getOrElse("世界は消えてしまった..."); } // 外部への通信とかの処理 public String innerExternalSystem() { // 通信したという体で、時間を経過させる // 時間の制限 long timeLimit = 2000; // 世界は救われた // long timeLimit = 3000; // 世界は破滅した // 処理前の時刻を取得 long startTime = System.currentTimeMillis(); long endTime = startTime; while (endTime - startTime <= timeLimit) { endTime = System.currentTimeMillis(); } return "世界はまだ存在する"; } }
「/TestResilience4J/src/main/java/main/TestResilience4J.java」
package main; import external.TimeLimiterUtil; public class TestResilience4J { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ TimeLimiterUtil timeLimiterUtil = new TimeLimiterUtil(); System.out.println(timeLimiterUtil.callExternalSystem()); } }
んで、実行してみる。
⇧ なんか、結果が出て、コンソールが終了するまでに時間が結構かかるんだけど、スレッドを使ってるのが関係しているのだろうか?
ちなみに、innerExternalSystem() メソッド内のtimeLimit を 3000 にすると、
まぁ、2秒、3秒って、既に、終末時計の2分とは何の関係もなくなってるけども...そして、せっかくJUnit 5系を導入したのに使ってないや...
というわけで、関数型インターフェイスを勉強せねばですかね...
今回もモヤモヤ感が募った、そんな1日でしたかね...
今回はこのへんで。