Cicuit Breakerは、マイクロサービスで一部のサービスが落ちたときに、障害が連鎖しないようアクセスを遮断して切り離すための仕組み。
Circuit Breakerに関して、詳しくはここ
今回はHystrixをSpringから使ってみます。
環境
Java 8
Spring Boot 1.5.7.RELEASE
まずやってみる
サンプルアプリの概要
クライアントからFrontendAPI(localhost:8090/hello
)を叩くと現在時刻が返ってくるシンプルなAPIです。
FrontendAPIは内部でさらにBackendAPI(localhost:8080/time
)を叩いて現在時刻を取得する仕組みです。
今回はここにHystrixを導入して、BackendAPIに障害が発生したときも、FrontendAPIに障害が伝搬せずに継続できるような仕組みを作ってみます。
- Backend API
- 概要:現在時刻を返すだけ
- ホスト:
localhost:8080
- エンドポイント:
/time
- Frontend API
- 概要:アプリケーション1を叩いて返ってきた内容をそのまま返す
- app1が落ちている or 3秒以上レスポンスが返ってこなかったら、保持しておいた最後に成功したときのレスポンスを返す (ここにHystrixを使う)
- ホスト:
localhost:8090
- エンドポイント:
/hello
- 概要:アプリケーション1を叩いて返ってきた内容をそのまま返す
作る
まずBackendAPI。
Spring InitializerでWebにチェックを入れてプロジェクト作成。
BackendControllerを下記の内容で作成。
@RestController
@RequestMapping("/time")
public class BackendController {
@GetMapping
public String app1() throws InterruptedException {
String now = LocalDateTime.now().toString();
return now;
}
}
次にFrontendAPI。
Spring InitializerでWeb, lombok, Hystrixにチェックを入れてプロジェクト作成。
FrontendServiceを以下の内容で作成。
オプションなどの説明はコメントに書いた。
@Service
public class FrontendService {
private String lastResponse = "error";
@HystrixCommand(
// エラー時、もしくはサーキットがOPEN時に代わりに呼ばれるメソッド
fallbackMethod = "fallback",
commandProperties = {
// サーキットがオープンになるエラー回数。デフォルトは20
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),
// サーキットがオープンになった後、指定したミリ秒待って再度リクエストを受け付ける。再度リクエストして成功すればサーキットがクローズする。デフォルトは5000
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000"),
// 指定した時間レスポンスが返ってこなかったらエラーとして処理する
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
// サーキットがオープンになるエラー率。デフォルトは50%.
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
}
)
public String get() {
System.out.println("call time api.");
RestTemplate restTemplate = new RestTemplate();
URI uri = URI.create("http://localhost:8080/time");
String response = restTemplate.getForObject(uri, String.class);
// lastResponseフィールドにレスポンスを保持する
lastResponse = response;
return response;
}
public String fallback() {
// 最後に成功したときのレスポンスを返す
return lastResponse;
}
}
FrontendControllerを下記の内容で作成
@RestController
@RequestMapping("/hello")
@RequiredArgsConstructor
@EnableCircuitBreaker
public class FrontendController {
private final FrontendService service;
@GetMapping
public String hello() {
return service.get();
}
}
動かしてみる
両方のAPIを動かした状態で、FrontendAPIを叩く
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
現在時刻が返ってくる。
アプリケーションのコンソールには下記のログが出ている。
call time api.
では、BackendAPIを落とした状態で10回くらい叩いてみる
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
$ curl "http://localhost:8090/hello"
2017-12-02T15:53:02.345
10回叩いても同じ時刻が返ってきている。BackendAPIが落ちているので、fallback
メソッドが呼ばれて、最後に成功したときのレスポンスが返っている。
ログを見てみるとBackendAPIを5回叩いた形跡が見られる。
call time api.
fallback.
call time api.
fallback.
call time api.
fallback.
call time api.
fallback.
call time api.
fallback.
fallback.
fallback.
fallback.
fallback.
fallback.
これは@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"),
で設定したとおり、5回連続でエラーが返ったその時点でサーキットがオープンになり、その後はBackendAPIは叩かれずにfallback
が呼ばれている。
今度はBackendAPIに3秒間のスリープ処理(Thread.sleep(3000))を入れて、わざとタイムアウトするようにしてみます。
また10回ほど叩いてみます。
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.385
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.385
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.385
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.745
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.745
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.745
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.745
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.745
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.745
$ curl "http://localhost:8090/hello"
2017-12-02T15:55:02.745
1~3回目までは最後に成功したときの時間が返っています。
4回目は、ちょうど1回目に送ったリクエストが返ってきて、キャッシュの値が更新されたのか、若干時刻が新しくなっています。
ログを見てみると先程と同様に6回目からBackendAPIを叩かなくなっています。
call time api.
fallback.
call time api.
fallback.
call time api.
fallback.
call time api.
fallback.
call time api.
fallback.
fallback.
fallback.
fallback.
fallback.
fallback.
所感
- オプション等はここが詳しい
- Hystrixの設定、application.ymlに書きたい