Sony Gobreaker Circuit Breaker

Wrap failing dependencies with sony/gobreaker, opening the circuit after consecutive failures and probing recovery in half-open.

GoResilience

Code

snippet
package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/sony/gobreaker"
)

var errFlaky = errors.New("dependency flaky")

type flakyService struct{ failRate int }

func (s *flakyService) Call(n int) (string, error) {
	if n%s.failRate == 0 {
		return "", errFlaky
	}
	return "payload", nil
}

func newBreaker(name string) *gobreaker.CircuitBreaker {
	return gobreaker.NewCircuitBreaker(gobreaker.Settings{
		Name:        name,
		MaxRequests: 3,               // 半开态最多放行 3 个试探
		Interval:    10 * time.Second, // 闭合态计数窗口
		Timeout:     5 * time.Second,  // 打开态冷却时长
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			return counts.ConsecutiveFailures >= 5
		},
		OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
			fmt.Printf("breaker %s: %s -> %s\n", name, from, to)
		},
	})
}

func guardedCall(cb *gobreaker.CircuitBreaker, svc *flakyService, n int) (string, error) {
	out, err := cb.Execute(func() (interface{}, error) {
		return svc.Call(n)
	})
	if err != nil {
		return "", err
	}
	return out.(string), nil
}

func main() {
	cb := newBreaker("flaky-api")
	svc := &flakyService{failRate: 3}

	for i := 1; i <= 12; i++ {
		v, err := guardedCall(cb, svc, i)
		fmt.Printf("call %02d -> value=%q err=%v\n", i, v, err)
	}
}