Rust製WebサーバにCircuitBreakerを入れてみる
Rust製WebサーバにCircuitBreakerを導入してみました。
最近だと、proxy(e.g. envoy)にCircuitBreakerを入れるパターンもあるかと思いますが、今回はアプリケーションに入れるパターンです。
使用するライブラリ
Rocket: RustでWebFrameworkといえば定番になるかと思います。
failsafe-rs: Rust circuitbreaker
で検索したときに一番最初に出てきました。
実装
CircuitBreakerのstateに応じてレスポンスを分ける。
match state.circuit.call(|| hello(query.name)) { Err(Error::Inner(_)) => { eprintln!("fail"); return Err(Status::InternalServerError) }, Err(Error::Rejected) => { eprintln!("rejected"); return Err(Status::ServiceUnavailable) }, Ok(x) => { return Ok(x) } }
CircuitBreakerの設定をどこで定義するかに悩みました。
Golangであれば、Globalの変数を定義してStatefulに管理しようとしましたが、Rustは変数のscopeを厳密にする必要があるため同じことができませんでした。
軽く調べた限り(issue)、 Heapへのallocationは開発者が直接操作できず、runtimeでしか行われない。
NGパターン
static mut bbo :Exponential = backoff::exponential(Duration::from_secs(3), Duration::from_secs(30)); static mut pl :ConsecutiveFailures<failsafe::backoff::Exponential> = failure_policy::consecutive_failures(3, bbo); static mut cb :StateMachine<failsafe::failure_policy::ConsecutiveFailures<failsafe::backoff::Exponential>, ()>= Config::new() .failure_policy(pl) .build();
エラーメッセージ:
calls in statics are limited to constant functions, tuple structs and tuple variants
至極まっとうなメッセージでした。
rocketのStateを使って解決
struct RocketState { circuit : StateMachine<ConsecutiveFailures<Exponential>, ()> } #[launch] fn rocket() -> _ { let back_off = backoff::exponential(Duration::from_secs(30), Duration::from_secs(60)); let policy = failure_policy::consecutive_failures(3, back_off); let circuit_breaker = Config::new() .failure_policy(policy) .build(); let hystrix_conf = RocketState{circuit: circuit_breaker}; rocket::build() .manage(hystrix_conf) . . .
動作確認
│rocket_circuitbreaker_trial on main └─> cargo run . . . 🛰 Routes: >> (api_hello) GET /hello?<query..> 📡 Fairings: # 別ターミナルで └─> curl -I -XGET 'http://localhost:8000/hello?name=hello' HTTP/1.1 200 OK content-type: text/plain; charset=utf-8 server: Rocket permissions-policy: interest-cohort=() x-content-type-options: nosniff x-frame-options: SAMEORIGIN content-length: 5 date: Tue, 26 Oct 2021 05:35:59 GMT ┌───────────────────> │~ └─> curl -I -XGET 'http://localhost:8000/hello?name=error' HTTP/1.1 500 Internal Server Error content-type: text/html; charset=utf-8 server: Rocket permissions-policy: interest-cohort=() x-content-type-options: nosniff x-frame-options: SAMEORIGIN content-length: 436 date: Tue, 26 Oct 2021 05:36:06 GMT ┌───────────────────> │~ └─> curl -I -XGET 'http://localhost:8000/hello?name=error' HTTP/1.1 500 Internal Server Error content-type: text/html; charset=utf-8 server: Rocket permissions-policy: interest-cohort=() x-content-type-options: nosniff x-frame-options: SAMEORIGIN content-length: 436 date: Tue, 26 Oct 2021 05:36:06 GMT ┌───────────────────> │~ └─> curl -I -XGET 'http://localhost:8000/hello?name=error' HTTP/1.1 500 Internal Server Error content-type: text/html; charset=utf-8 server: Rocket permissions-policy: interest-cohort=() x-content-type-options: nosniff x-frame-options: SAMEORIGIN content-length: 436 date: Tue, 26 Oct 2021 05:36:09 GMT # num_failures(3)を超えたので 503エラーに ┌───────────────────> │~ └─> curl -I -XGET 'http://localhost:8000/hello?name=error' HTTP/1.1 503 Service Unavailable content-type: text/html; charset=utf-8 server: Rocket permissions-policy: interest-cohort=() x-content-type-options: nosniff x-frame-options: SAMEORIGIN content-length: 397 date: Tue, 26 Oct 2021 05:36:10 GMT # stateがclosedとなっているので、503エラーに ┌───────────────────> │~ └─> curl -I -XGET 'http://localhost:8000/hello?name=hello' HTTP/1.1 503 Service Unavailable content-type: text/html; charset=utf-8 server: Rocket permissions-policy: interest-cohort=() x-content-type-options: nosniff x-frame-options: SAMEORIGIN content-length: 397 date: Tue, 26 Oct 2021 05:36:17 GMT # stateがhalf openとなったことで、200 OKに ┌───────────────────> │~ └─> curl -I -XGET 'http://localhost:8000/hello?name=hello' HTTP/1.1 200 OK content-type: text/plain; charset=utf-8 server: Rocket permissions-policy: interest-cohort=() x-content-type-options: nosniff x-frame-options: SAMEORIGIN content-length: 5 date: Tue, 26 Oct 2021 05:36:24 GMT
今のままだと、全APIでひとつのCircuitBreakerのStateに左右されてしまうので、 RocketState
にAPIごとのCircuitBreakerの設定フィールドを用意するなど工夫が必要かと思います。
ご指摘あれば、細かいところでも教えていただけると助かります。