Status:堅牢なシステム・ヘルスチェックのための Java ライブラリ

今回は Status (ステータス) のオープンソース化を特集したいと思います。

Status は、システムのステータスを読みやすいフォーマットでレポートできる Java  ライブラリです。この Status ライブラリは動的なヘルスチェックと、システムの依存関係の監視を可能にします。

この記事では、皆さん自身のアプリケーションにヘルスチェックを追加する方法を説明します。

システムステータス・ヘルスチェックを使用する理由は?

ヘルスチェックは、 Indeed で重要な役割を果たしています。

私たちは毎日大規模のサービスやアプリケーションをセットアップし、実行しています。

ヘルスチェックは、問題のあるコンポーネントをログからくまなく探すのではなく、エンドポイントでの発見を可能にします。

 

本番環境では、サービスが到達不能であったり、ファイルが見つからない場合、またはシステムがデータベースに接続できない場合にヘルスチェックが通知してくれます。

加えて、これらのヘルスチェックは、開発者が制御された方法でシステム管理者に問題を通知する方法を提供します。

これらのどんな状況でも、アプリケーションは全システムをオフラインにするのではなく、自身のヘルスチェックを評価し、挙動をグレイスフルデグラデーションすることが可能になります。

Status ライブラリは依存関係のスタックトレースをキャプチャし、その結果を一つの場所に返します。この機能は、問題がどの環境で発生しても、解決を容易にしてくれるのです。

典型的な依存関係には、 MySQL テーブル 、 MongoDB のコレクション、 RabbitMQ のメッセージキュー、ならびに API のステータスなどがあります。

システムの状態

依存関係に障害が発生した場合、システムの状態に影響します。

システムの状態は以下のように表示されます。

  • OUTAGE (停止)  – システムがリクエストを全く処理不可能な状態
  • MAJOR (重要度:高) – システムが一部を除き殆どのリクエストを提供不可能な状態
  • MINOR (重要度:低) – 一部を除き殆どのリクエストを提供可能な状態
  • OK – システムが全てのリクエストを処理可能な状態

Status を始めてみよう

以下の手順に従い、早速 Status ライブラリを使ってみましょう。

AbstractDependencyManager を継承したクラスを作成します。これは、全ての依存関係を追跡してくれます。

public class MyDependencyManager extends AbstractDependencyManager {
  public MyDependencyManager() {
    super("MyApplication");
  }
}

アプリケーションが実行を必要とする各コンポーネントに PingableDependency を継承します。

public class MyDependency extends PingableDependency {
  @Override
  public void ping() throws Exception {
    // Throw exception if considered unhealthy or unavailable
  }
}

PingableDependency のクラスを継承するのが、アプリケーションに依存性を統合するのに最もシンプルなやりかたです。

それとは別に、 AbstractDependencyComparableDependency を拡張すると、依存関係の状態をさらに管理することができます。緊急度レベルを提供すると、依存関係が、システムの状況にどう影響するかをコントロールできます。
新しい依存関係を、 dependency manager に追加します。

dependencyManager.addDependency(myDependency);
...

Web ベースのアプリケーションやサービスには、アプリケーションのステータスをレポートしてくれる AbstractDaemonCheckReportServlet を作成します。

public class StatusServlet extends AbstractDaemonCheckReportServlet {
  private final AbstractDependencyManager manager;

  public StatusServlet(AbstractDependencyManager manager) {
    this.manager = manager;
  }

  @Override
  protected AbstractDependencyManager newManager(ServletConfig config) {
    return manager;
  }
}

このプロセスが完了し、アプリケーションが実行された時点で、アプリケーションのステータスを JSON で表現するサーブレットにアクセス可能になります。

以下はサーブレットが返すリスポンス例になります。もしアプリケーションが OUTAGE (停止) の場合、サーブレットは 500 というステータスコードを返します。ヘルスチェックの結果を HTTP のステータスコードと関連付けると、アプリケーションの稼働状態に基づいて経路決定をするシステム(例 Consul )と統合可能になります。そうでなければ、アプリケーションはリクエストを処理できるので、サーブレットはステータスコード 200 を返します。この場合アプリケーションは、正常でないコードパスに依存する、重要度が低い機能をグレイスフルデグラデーションすることができます。

{
  "hostname": "pitz.local",
  "duration": 19,
  "condition": "OUTAGE",
  "dcStatus": "FAILOVER",
  "appname": "crm.api",
  "catalinaBase": "/var/local/tomcat",
  "leastRecentlyExecutedDate": "2015-02-24T22:48:37.782-0600",
  "leastRecentlyExecutedTimestamp": 1424839717782,
  "results": {
    "OUTAGE": [{
      "status": "OUTAGE",
      "description": "mysql",
      "errorMessage": "Exception thrown during ping",
      "timestamp": 1424839717782,
      "duration": 18,
      "lastKnownGoodTimestamp": 0,
      "period": 0,
      "id": "mysql",
      "urgency": "Required: Failure of this dependency would result in complete system outage",
      "documentationUrl": "http://www.mysql.com/",
      "thrown": {
        "exception": "RuntimeException",
        "message": "Failed to communicate with the following tables:
          user_authorities, oauth_code, oauth_approvals, oauth_client_token,
          oauth_refresh_token, oauth_client_details, oauth_access_token",
        "stack": [
          "io.github.jpitz.example.MySQLDependency.ping(MySQLDependency.java:68)",
          "com.indeed.status.core.PingableDependency.call(PingableDependency.java:59)",
          "com.indeed.status.core.PingableDependency.call(PingableDependency.java:15)",
          "java.util.concurrent.FutureTask.run(FutureTask.java:262)",
          "java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)",
          "java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)",
          "java.lang.Thread.run(Thread.java:745)"
        ]
      },
      "date": "2015-02-24T22:48:37.782-0600"
    }],
    "OK": [{
      "status": "OK",
      "description": "mongo",
      "errorMessage": "ok",
      "timestamp": 1424839717782,
      "duration": 0,
      "lastKnownGoodTimestamp": 0,
      "period": 0,
      "id": "mongo",
      "urgency": "Required: Failure of this dependency would result in complete system outage",
      "documentationUrl": "http://www.mongodb.org/",
      "date": "2015-02-24T22:48:37.782-0600"
    }]
  }
}

このレポートは、システムの動作状態と依存関係の状態を評価するのに役立つ、これらの主要なフィールドを含みます。

condition システム全体の現在の稼働状態を識別する。
leastRecentlyExecutedDate レポートが最終更新された日付と時間。

個別の依存性を調査するには、以下のフィールドを使用します。

status 現在の依存関係の状態を識別する。
thrown 依存関係に障害をもたらした例外 ( exception )。
duration 依存関係の状態を評価するのにかかった時間の長さ。 システムが依存関係の評価の結果をキャッシュしているので、この値は 0 にもなり得る。
urgency 依存関係の緊急度。 WEAK (弱) の緊急度をもつ依存関係は直ちに修復しなくてもよい。 REQUIRED (要) の緊急度を持つ依存関係は可能な限り早い修復が必要とされる。

Status をさらに知ろう

今後の Status ライブラリ使用法を特集する記事では、正常に稼働していないアプリケーションに対し、どのようにグレイスフルデグラデーションを行うかをお伝えする予定です。

 

status の使用を始めるには、クイックスタートガイドや、サンプルをご覧ください。

お困りの際は GitHub または Twitter からご質問いただけます。