circuit breaker pattern をGoで実装したいので、いくつかのライブラリを試してみる。
ライブラリ
metricsもとれる https://github.com/cep21/circuit/ https://github.com/afex/hystrix-go 古い
bulk headなどcircuit breaker以外の機能もある https://github.com/slok/goresilience metricsとれる https://github.com/eapache/go-resiliency
circuit breakerのみ https://github.com/mercari/go-circuitbreaker https://github.com/sony/gobreaker https://github.com/streadway/handy/tree/master/breaker https://github.com/rubyist/circuitbreaker
Hystrix dashboard も使ってみると、サーキットの状態を見れておもしろい
cep21/circuit
- Hystrixライクなサーキットブレーカーを提供するライブラリ
- 設定値は https://github.com/Netflix/Hystrix/wiki/Configuration を参考にできる
- メトリクスを取れる
- Prometheus で収集できる
- メモリアロケーションのコストが低い、ベンチマークがいい
- Hystrix dashboard をサポートしている
Prometheus で収集するにはこちらのライブラリを使う https://github.com/jiacai2050/prometrics これによって、サーキットのオープン回数、実行回数などがわかる
実装例
package main
import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"time"
"github.com/cep21/circuit/closers/hystrix"
"github.com/cep21/circuit/v3"
"github.com/jiacai2050/prometrics"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type result struct {
err error
msg string
}
func newCircuitBreaker() *circuit.Circuit {
// circuit breakerの設定
hystrixConf := hystrix.Factory{
ConfigureOpener: hystrix.ConfigureOpener{
// サーキットをオープンするエラー率
ErrorThresholdPercentage: 50,
// 10回エラーになるまではサーキットをオープンしない
RequestVolumeThreshold: 10,
},
ConfigureCloser: hystrix.ConfigureCloser{
// サーキットがオープンになってからハーフオープンになるまでの時間
SleepWindow: 2000 * time.Millisecond,
},
}
// Prometheusで収集できるようにする
prom := prometrics.GetFactory(prometheus.DefaultRegisterer)
h := circuit.Manager{
DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{
hystrixConf.Configure,
prom.CommandProperties,
},
}
// This circuit will inherit the configuration from the example
c := h.MustCreateCircuit("hystrix-circuit")
fmt.Println("This is a hystrix configured circuit", c.Name())
return c
}
func main() {
c := newCircuitBreaker()
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}))
go func() {
http.ListenAndServe(":8080", mux)
}()
results := make(chan result)
// Run a infinite loop executing using our runner.
go func() {
for {
time.Sleep(200 * time.Millisecond)
// Execute concurrently.
go func() {
// Execute our call to the service.
var msg string
// Call the circuit
err := c.Execute(context.Background(), func(ctx context.Context) error {
now := time.Now()
// If minute is mod 3 return error directly
if now.Second()%10 == 0 {
return fmt.Errorf("huge system error")
}
var err error
switch time.Now().UnixMilli() % 10 {
case 0:
msg = "ok"
case 2, 9:
time.Sleep(750 * time.Millisecond)
err = fmt.Errorf("a error")
case 7:
time.Sleep(5 * time.Second)
msg = "ok"
default:
time.Sleep(20 * time.Millisecond)
if rand.Intn(1000)%2 == 0 {
msg = "ok"
} else {
err = fmt.Errorf("another error")
}
}
return err
}, func(ctx context.Context, err error) error {
var ce circuit.Error
if errors.As(err, &ce) {
fmt.Println("circuit is open")
return nil
} else
return err
}
})
// Send the result to our receiver outside this infinite loop.
results <- result{
err: err,
msg: msg,
}
}()
}
}()
// Process the received executions.
for res := range results {
if res.err != nil {
fmt.Printf("[!] fallback because err received: %s\n", res.err)
} else {
fmt.Printf("[*] all ok: %s\n", res.msg)
}
}
}
go run main.go
http://localhost:8080/metrics
を開くとgo_gcなど標準のメトリクスに加えてcircuitの状態が取得できるようになっている
# HELP circuit_failure_duration_seconds Duration of failed func run
# TYPE circuit_failure_duration_seconds histogram
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="0.005"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="0.01"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="0.025"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="0.05"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="0.1"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="0.25"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="0.5"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="1"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="2.5"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="5"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="10"} 2
circuit_failure_duration_seconds_bucket{func="fallback",name="hystrix-circuit",le="+Inf"} 2
circuit_failure_duration_seconds_sum{func="fallback",name="hystrix-circuit"} 1.6e-05
circuit_failure_duration_seconds_count{func="fallback",name="hystrix-circuit"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="0.005"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="0.01"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="0.025"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="0.05"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="0.1"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="0.25"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="0.5"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="1"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="2.5"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="5"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="10"} 2
circuit_failure_duration_seconds_bucket{func="run",name="hystrix-circuit",le="+Inf"} 2
circuit_failure_duration_seconds_sum{func="run",name="hystrix-circuit"} 0.000570084
circuit_failure_duration_seconds_count{func="run",name="hystrix-circuit"} 2
afex/hystrix-go
Hystrix と名前に入っている通り、JavaのHystrixと同じように使用できる goroutineで実行される メトリクスを取る仕組みは組み込まれていない 最終コミットは2018年
output := make(chan bool, 1)
errors := hystrix.Go("my_command", func() error {
// talk to other services
output <- true
return nil
}, nil)
select {
case out := <-output:
// success
case err := <-errors:
// failure
}
slok/goresilience
サーキットブレーカーに限らず、resilliencyを高めるためのパターンをいくつか提供している Hystrixライク Prometheusで収集できる