Slide 1

Slide 1 text

@ono_matope
 go-circuitbreakerのご紹介


Slide 2

Slide 2 text

● Yasuharu Goto
 ○ Backend Engineer ○ 前職:ヤフー:Goでオブジェクトストレージ開発 ● 株式会社メルペイ (2019年9月1日-)
 ○ ID Platformチーム所属 ○ 認証認可Microservicesの開発 
 小野マトペ (ono_matope)
 2

Slide 3

Slide 3 text

Go用 Circuit Breaker ライブラリ
 go-circuitbreaker
 を公開しました
 3

Slide 4

Slide 4 text

https://github.com/mercari/go-circuitbreaker


Slide 5

Slide 5 text

Background
 5

Slide 6

Slide 6 text

Failures in Microservices Architecture
 ● Microservices Architecture においては、依存サービスの障害は平常状態
 ● 下位サービスの障害時
 ○ リクエストのブロック時間が上位サービスに伝播 ○ リトライすると下位サービスへの負荷をさらに高める悪循環 6 Microsrervice B Microsrervice C 10s Latency 10s+ Latency リトライによる 過負荷 Fail! Microsrervice A

Slide 7

Slide 7 text

Failures in Microservices Architecture
 連鎖的な障害を防ぐため、障害サービスへのリクエストを遮断したい
 7 Microsrervice C Fail! Fail Fast Fail Fast Microsrervice B Microsrervice A

Slide 8

Slide 8 text

Circuit Breaker パターン
 Circuit Breaker = ブレーカー
 外部リクエストのエラーカウンタがしきい値を超えたら
 Circuit Openし、しばらくは処理を省略して
 即座にエラーを返す(Fail Fast)パターン
 
 ・障害時の連鎖的なレイテンシの増加を防げる
 ・リトライによる下位サービスの過負荷を緩和
 8 External Service Success Error (1) Error (2) Error (3) Trip Circuit Open! Trip Fail!

Slide 9

Slide 9 text

CLOSED Circuit Breaker
 ● CLOSED: リクエストが一定回数失敗したら OPEN に遷移
 ● OPEN: 一定の時間リクエストを止める (ブレーカーが落ちている状態)
 ● HALF-OPEN: リクエストが失敗したら OPEN に戻る。一定回数成功したら CLOSED に復帰。
 9 External Service HALF-OPEN OPEN CLOSED

Slide 10

Slide 10 text

Circuit Breaker
 =障害を検知して遮断するパターン


Slide 11

Slide 11 text

Circuit Breaker in Go
 11

Slide 12

Slide 12 text

Circuit Breaker packages
 ● github.com/rubyist/circuitbreaker
 ● github.com/sony/gobreaker
 12

Slide 13

Slide 13 text

github.com/sony/gobreaker
 13 ● Execute メソッドに外部呼び出し処理を渡す
 ○ 戻り値 error がnil → 成功カウンタ++
 ○ 戻り値 error がnon-nil → 失敗カウンタ++
 ○ Circuit Open時 → 関数が実行されず ErrOpenState がreturn


Slide 14

Slide 14 text

現実はもう少し複雑
 ● Goの関数が返すエラーにはいろいろな種類がある 
 ○ 障害起因のエラー ○ その他のエラー ● Goコード上のerrorをすべて 「失敗」とみなしていいのだろうか? 
 14

Slide 15

Slide 15 text

現実の例
 HTTPレスポンスボディ全体を読むコードを サーキットブレーカーで保護


Slide 16

Slide 16 text

リクエスト作成エラー:リクエストしてない。
 無視したい


Slide 17

Slide 17 text

HTTPリクエスト失敗:「失敗」
 リクエスト作成エラー:リクエストしてない。
 無視したい


Slide 18

Slide 18 text

HTTPリクエスト失敗:「失敗」
 HTTP 5xx系ステータス:「失敗」
 リクエスト作成エラー:リクエストしてない。
 無視したい


Slide 19

Slide 19 text

HTTPリクエスト失敗:「失敗」
 HTTP 5xx系ステータス:「失敗」
 HTTP 4xx系ステータス:サービス自体は生きてる。
 場合によるが「成功」とみなしたい
 リクエスト作成エラー:リクエストしてない。
 無視したい


Slide 20

Slide 20 text

レスポンスボディ読み込み失敗 => 「失敗」
 HTTPリクエスト失敗:「失敗」
 HTTP 5xx系ステータス:「失敗」
 HTTP 4xx系ステータス:サービス自体は生きてる。
 場合によるが「成功」とみなしたい
 リクエスト作成エラー:リクエストしてない。
 無視したい


Slide 21

Slide 21 text

context
 ・contextを使ってユーザーがリクエストをキャ ンセルしたり、タイムアウト時間を指定したりす ると、リクエストがエラー終了する。
 
 →障害とは無関係
 
 ・ユーザーが発火可能。失敗として扱うとユー ザーがサーキットブレイカーを落とすことが出 来てしまう。
 
 (gRPCではユーザーがキャンセルも
 タイムアウトも指定可能)
 https://www.irasutoya.com/2018/06/blog-post_972.html

Slide 22

Slide 22 text

課題
 ● 現実のコードには様々な error が
 ○ 障害起因のerror ■ 失敗として扱いたい ○ 障害と関係ないerror ■ 「失敗」として扱うとFalse-Positive ■ 「成功」として扱うとFalse-Negativeのケースも ○ context起因のerror ■ contexに対応した処理は、ユーザーがエラー終了させる可能性 ● これらをCircuit Breakerに正しく伝えることは難しい
 22

Slide 23

Slide 23 text

go-circuitbreaker
 23 https://github.com/mercari/go-circuitbreaker


Slide 24

Slide 24 text

Doメソッド
 24 ● Doメソッドが保護対象関数とともにcontext.Context を受け取る 
 ● 基本的にはgobreakerと同じ 
 ○ 戻り値 error が nil : 成功 ○ 戻り値 error が non-nil : 失敗 ○ ブレーカーが落ちたら関数は実行されずエラー

Slide 25

Slide 25 text

特徴1: 特定のエラーを無視・成功扱いに指定できる
 25 func Ignore(err) error 
 func MarkAsSuccess(err) error 
 ラップしたエラーをサーキットブレーカに 「無視」させる。無視されたエラーは失敗 にも成功にもカウントされない。 
 ラップしたエラーをサーキットブレーカー に「成功」としてカウントさせる。 
 どちらもfuncの外にはアンラップしてreturnさ れる


Slide 26

Slide 26 text

特徴2: context のキャンセル・タイムアウトを無視
 26 ● オプションで変更可能 
 ● returnしたエラーはそのままDoの外側で受け取れる 


Slide 27

Slide 27 text

外部リクエストのタイムアウトを失敗とみなす方法
 27 ● Do() の内側でcontextを派生してください。 
 ● 内側のcontextがtimeoutしても外側はtimeoutしない→失敗としてカウント 


Slide 28

Slide 28 text

実装上の工夫
 28

Slide 29

Slide 29 text

29 https://docs.microsoft.com/ja-jp/azure/architecture/patterns/circuit-breaker ● Circuit Breakerは成功・失敗カウンターと ク ローズ, オープン, ハーフオープン と3つの内 部状態を持つ
 ● 単純な実装では分岐が多く複雑になる


Slide 30

Slide 30 text

State パターン in Go
 CBの内部状態 state を interface 定義。
 30 type state interface { onEntry(cb *CircuitBreaker) // 状態開始時の処理 onExit(cb *CircuitBreaker) // 状態終了時の処理 onSuccess(cb *CircuitBreaker) // 成功時の処理
 onFail(cb *CircuitBreaker) // 失敗時の処理
 ready() bool // 処理を開始できるかの問い合わせ // ... } state.go (抜粋)

Slide 31

Slide 31 text

State パターン in Go
 CircuitBreaker は state を保持し、
 特有の処理を移譲する。
 (カウンタの増分は共通処理なので移譲していない)
 31 func (cb *CircuitBreaker) Success() { cb.mu.Lock() defer cb.mu.Unlock() cb.cnt.incrementSuccesses() cb.state.onSuccess(cb) } func (cb *CircuitBreaker) Fail() { cb.mu.Lock() defer cb.mu.Unlock() cb.cnt.incrementFailures() cb.state.onFail(cb) } breaker.go (抜粋) type CircuitBreaker struct { state state cnt Counters // ... } breaker.go (抜粋)

Slide 32

Slide 32 text

state.go (抜粋)
 func (st *stateClosed) onEntry(cb *CircuitBreaker) { cb.cnt.resetFailures() // ... } func (st *stateClosed) onSuccess(cb *CircuitBreaker) {} func (st *stateClosed) onFail(cb *CircuitBreaker) { if cb.shouldTrip(&cb.cnt) { cb.setState(&stateOpen{}) } } 32 https://docs.microsoft.com/ja-jp/azure/architecture/patt erns/circuit-breaker Closed


Slide 33

Slide 33 text

func (st *stateOpen) onEntry(cb *CircuitBreaker) { // ... cb.clock.AfterFunc(timeout, cb.setStateWithLock(&stateHalfOpen{})) } func (st *stateOpen) onSuccess(cb *CircuitBreaker) {} func (st *stateOpen) onFail(cb *CircuitBreaker) {} 33 https://docs.microsoft.com/ja-jp/azure/architecture/patterns/circuit-b reaker Open
 state.go (抜粋)


Slide 34

Slide 34 text

func (st *stateHalfOpen) onEntry(cb *CircuitBreaker) { cb.cnt.resetSuccesses() } func (st *stateHalfOpen) onSuccess(cb *CircuitBreaker) { if cb.cnt.Successes >= cb.halfOpenMaxSuccesses { cb.setState(&stateClosed{}) } } func (st *stateHalfOpen) onFail(cb *CircuitBreaker) { cb.setState(&stateOpen{}) } 34 https://docs.microsoft.com/ja-jp/azure/architecture/patterns/circuit-b reaker Half Open
 state.go (抜粋)


Slide 35

Slide 35 text

State デザインパターン
 ・内部状態を型として表現
 ・分岐の少ない保守性の高いコードに
 35

Slide 36

Slide 36 text

まとめ
 36

Slide 37

Slide 37 text

Go用Circuit Breakerを作りました
 既存のアプリケーションに組み込みやすいはず
 使ってみてください!
 01 02 03 37

Slide 38

Slide 38 text

Thank you!
 38

Slide 39

Slide 39 text

39