マイクロサービスにおける障害と Failurewall

# マイクロサービスにおける障害と Failurewall

こちらは [Scala Advent Calendar 2015](http://www.adventar.org/calendars/904) 25日目の記事です。
Scalist の皆さん、一年間お疲れさまでした。

当記事の目的はマイクロサービス(に限らないですが)における失敗の怖さについて説明することと、それを克服するためのライブラリ、[Failurewall](https://github.com/failurewall/failurewall) を紹介することです。

## 障害は怖い

### 障害の連鎖

現代的な Web アプリケーションは多くの依存を抱えています。MySQL のようなデータベースに依存することもあれば、Twitter API のような外部サービスに依存することもあるでしょう。マイクロサービスを実践している環境では社内 Web API に依存していたり、あるいは他のシステムが自分のシステムに依存していたりするかもしれません。

このように優秀なミドルウェアや Web API を組み合わせることで、アプリケーションは高い性能を発揮したり、豊かな機能を提供したり、よりよいアーキテクチャを実現したりすることができます。

![dependencies.png](https://qiita-image-store.s3.amazonaws.com/0/17398/d0edec9d-9cf9-537d-33c3-68d81038c45a.png)!

とはいえ、ミドルウェアや外部サービスの API はいつか壊れます。例えば、全文検索を提供するミドルウェアが応答を返せなくなったとしましょう。この場合、検索機能を提供するサービスへのリクエストは、全文検索エンジンへのクエリがタイムアウトするまで待たされることとなるかもしれません。

![down-search-engine.png](https://qiita-image-store.s3.amazonaws.com/0/17398/242c12bd-3701-abbf-121b-98a40b84be5c.png)

ナイーブな実装だと、検索アプリケーションそのものが壊れてしまうかもしれませんね。例えば、検索クエリにアプリケーションのスレッドがすべて食いつぶされる、というのは想像しやすいシチュエーションではないでしょうか。

![down-search-service.png](https://qiita-image-store.s3.amazonaws.com/0/17398/9af336a8-a9e8-3eda-7dd8-6e8329c1b042.png)

マイクロサービスな環境では、被害はさらに拡大する可能性があります。

![down-all.png](https://qiita-image-store.s3.amazonaws.com/0/17398/3b620ebf-9152-dbfc-5bbb-c489e49dc386.png)

## Hystrix

今流行りの Netflix は、このような問題に対処するために [Hystrix](https://github.com/Netflix/Hystrix) というライブラリを開発しました。Hystrix は以下の要領で、依存リソースの障害に対応します。

### スレッドプールの分離

Hystrix は依存リソースの呼び出しをコマンドパターンで記述するインターフェースとなっています。そうして実装されたコマンドは、依存リソースごとに異なるスレッドプールを持つこととなります。

![thread-pool.png](https://qiita-image-store.s3.amazonaws.com/0/17398/aa8b7e8e-a8ed-dc98-a370-a00fefa1f6d8.png)

こうすることで、例えば Twitter API を呼び出すスレッドでブロックが多発するような自体に陥っても、その他の処理が動作するスレッドが健在であることを保証できます。

### サーキットブレーカー

スレッドプールを分離しても、ユーザーがリクエストする限り、依存リソースへのアクセスは続きます。ダウンした依存リソースへのアクセスはレイテンシの増加を引き起こし、滞留したリクエストは余分なリソースを消費し、そしてユーザーは長時間レスポンスを待つ必要が出てきます。

持続的な障害に対し、速やかにエラーを返す仕組みを導入するのが [Circuit Breaker Pattern](https://msdn.microsoft.com/ja-jp/library/dn589784.aspx)です。

サーキットブレーカーはおおまかには以下のように動作します。

1. 依存リソースが返すエラー数を記録
2. エラーの数が閾値を超えると fail-fast モードへ
3. fail-fast モードに移行すると、依存リソースへのアクセスを諦めてすぐにエラーを返すようになる
4. fail-fast モードに移行してしばらく経つと、正常モードに戻ろうとする

![circuit-breaker.png](https://qiita-image-store.s3.amazonaws.com/0/17398/7b4445e2-6c0a-dff6-e0c3-558812a2447a.png)

## Failurewall

[Failurewall](https://github.com/failurewall/failurewall) は Hystrix のようなエラー処理を、Scala でシンプルに実現することを目的として作られたプロジェクトです。

以下の二点を重視して作りました。

* 標準ライブラリの `scala.concurrent.Future` に特化したシンプルな API の提供
* エラーハンドリングを必要な分だけ容易に合成できる仕組みの提供

### シンプルなインターフェース

`Failurewall` の使い方は簡単、ハンドリングしたい処理を `call` メソッドでラップするだけです。

```scala
val failurewall: Failurewall[Response, Response] = ???
failurewall.call(httpClient.get("https://github.com/okumin"))
```

このメソッドを実装することで、新たな機能を持った `Failurewall` を作成できます。

```scala
class RetryFailurewall[A](max: Int)(implicit executor: ExecutionContext) extends Failurewall[A, A] {
  override def call(body: => Future[A]): Future[A] = {
    def retry(i: Int): Future[A] = {
      body.recoverWith {
        case e if i == max => Future.failed(e)
        case _ => retry(i + 1)
      }
    }
    retry(1)
  }
}
```

Future API に乗っかっているので、実行コンテキストの指定やフォールバック処理の指定もお手のものです。

```scala
val mysqlExecutor: ExecutionContext = ???
failurewall.call(Future(sql.execute("SELECT * FROM `tweets`"))(mysqlExecutor))
```

### Failurewall の合成

`compose` メソッドを用いると、Failurewall の持つ能力を合成することができます。

```scala
val wallSina: Failurewall[Int, String] = ???
val wallRose: Failurewall[Int, Int] = ???
val wallMaria: Failurewall[Double, Int] = ???

val humanField: Failurewall[Double, String] = wallSina compose wallRose compose wallMaria
```

試しにリトライパターンとサーキットブレーカーパターンを組み合わせてみましょう。こうすることで、以下のように動作する Failurewall を作ることが可能です。

* 一時的なエラーはリトライでリカバリー
* 持続的な障害にはサーキットブレーカーで対応

```scala
val retryFailurewall: Failurewall[Response, Response] = new RetryFailurewall[Response](max = 10)
val circuitBreakerFailurewall: Failurewall[Response, Response] = ???

val apiFailurewall: Failurewall[Response, Response] = circuitBreakerFailurewall compose retryFailurewall
```

合成機能により、セマフォによる同時アクセス数の制御、フォールバック機能、モニタリング機能、チャットやメールによるエラー通知機能など、必要な仕組みを手軽にデコレートしていくことが可能です。

### その他

導入方法やデフォルトで提供されている Failurewall(e.g. セマフォ、サーキットブレーカー、リトライ) について詳しく知りたい方は [github レポジトリ](https://github.com/failurewall/failurewall)をご覧ください。

## まとめ

以上、障害の話と [Failurewall](https://github.com/failurewall/failurewall) の紹介でした。

今年も日本 Scala 界は大盛り上がりでしたね。来年は年明け早々 [ScalaMatsuri](http://scalamatsuri.org/) もあることですし、さらなる飛躍が期待できるのではないでしょうか。私も日ごろの感謝をこめて、忍者スポンサーに申し込みました。

みんなで Scala 界を盛り上げていきましょう！
Happy Hacking!