circuitbreaker

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 16, 2022 License: MIT Imports: 7 Imported by: 17

README

go-circuitbreaker

GoDoc lint unittests

go-circuitbreaker is a Circuit Breaker pattern implementation in Go.

  • Provides natural code flow.
  • Ignore errors occurred by request cancellation from request callers (in default).
  • Ignore(err) and MarkAsSuccess(err) wrappers enable you to receive non-nil error from wrapped operations without counting it as a failure.

What is circuit breaker?

See: Circuit Breaker pattern - Cloud Design Patterns | Microsoft Docs

Initializing the circuit breaker

This library is using the functional options pattern to instance a new circuit breaker. The functions are the following:

You can use them like the next example:

cb := circuitbreaker.New(
    circuitbreaker.WithClock(clock.New()),
    circuitbreaker.WithFailOnContextCancel(true),
    circuitbreaker.WithFailOnContextDeadline(true),
    circuitbreaker.WithHalfOpenMaxSuccesses(10),
    circuitbreaker.WithOpenTimeoutBackOff(backoff.NewExponentialBackOff()),
    circuitbreaker.WithOpenTimeout(10*time.Second),
    circuitbreaker.WithCounterResetInterval(10*time.Second),
    // we also have NewTripFuncThreshold and NewTripFuncConsecutiveFailures
    circuitbreaker.WithTripFunc(circuitbreaker.NewTripFuncFailureRate(10, 0.4)),
    circuitbreaker.WithOnStateChangeHookFn(func(from, to circuitbreaker.State) {
      log.Printf("state changed from %s to %s\n", from, to)
    }),
)

Simple Examples with Do.

Wrapping your code block with Do() protects your process with Circuit Breaker.

var cb = circuitbreaker.New()

u, err := cb.Do(ctx, func() (interface{}, error) {
  return fetchUserInfo(name)
})
user, _ := u.(*User) // Casting interface{} into *User safely.

Example using Done.

The following example using Ready() and Done() is exactly equals to the above one. Since this style enables you to protect your processes without wrapping it and using type-unsafe interface{}, it would make it easy to implement CB to your existing logic.

var cb = circuitbreaker.New()

func getUserInfo(ctx context.Context, name string) (_ *User,err error) {
  if !cb.Ready() {
    return nil, circuitbreaker.ErrOpened
  }
  defer func() { err = cb.Done(ctx, err) }

  return fetchUserInfo(ctx, name)
}

Smart handling for context.Canceled and context.DeadlineExceeded

In microservices architectures, Circuit Breaker is essential to protect services you depend on from cascading failures and keep your services latency low during long-lasting failures. And for microservices written in Go, cancel request by propagating context is also a good convention. But there are some pitfall when you combinate context and circuit breaker.

Your microservices users are able to cancel your requests. The cancellation will be propagated through the context to your operation which is protected by CB. If the CB mark the canceled execution as a fail, your CB may open even though there is no problem in your infrastructure. It is a false-positive CB-open. It would prevent your other clients from doing tasks successfully because the CB is shared by all clients. Otherwise, if the CB mark the canceled execution as a success, the CB may back to closed unexpectedly. (it's a false-negative). In order to avoid those unexpected issue, go-circuitbreaker provides an option to ignore the errors caused by the context cancellation.

The same for timeouts configuration. Especially on gRPC, clients are able to set a timeout to the server's context. It means that clients with too short timeout are able to make other clients operation fail. For this issue, go-circuitbreaker provides an option to an option to ignore deadline exceeded errors .

Ignore and MarkAsSuccess wrappers

Sometimes, we would like to receive an error from a protected operation but don't want to mark the request as a failure. For example, a case that protected HTTP request responded 404 NotFound. This application-level error is obviously not caused by a failure, so that we'd like to return nil error, but want to receive non-nil error. Because go-circuitbreaker is able to receive errors from protected operations without making them as failures, you don't need to write complicated error handlings in order to achieve your goal.

cb := circuitbreaker.New(nil)

data, err := cb.Do(context.Background(), func() (interface{}, error) {
  u, err := fetchUserInfo("john")
  if err == errUserNotFound {
    return u, circuitbreaker.Ignore(err) // cb does not treat the err as a failure.
  }
  return u, err
})
if err != nil {
  // Here, you can get errUserNotFound
  log.Fatal(err)
}

Installation

go get github.com/mercari/go-circuitbreaker

Documentation

Index

Examples

Constants

View Source
const (
	DefaultInterval             = 1 * time.Second
	DefaultHalfOpenMaxSuccesses = 4
)

Default setting parameters.

Variables

View Source
var (
	// ErrOpen is an error to signify that the CB is open and executing
	// operations are not allowed.
	ErrOpen = errors.New("circuit breaker open")

	// DefaultTripFunc is used when Options.ShouldTrip is nil.
	DefaultTripFunc = NewTripFuncThreshold(10)
)

Functions

func DefaultOpenBackOff

func DefaultOpenBackOff() backoff.BackOff

DefaultOpenBackOff returns defaultly used BackOff.

func Ignore

func Ignore(err error) error

Ignore wraps the given err in a *IgnorableError.

func MarkAsSuccess

func MarkAsSuccess(err error) error

MarkAsSuccess wraps the given err in a *SuccessMarkableError.

Types

type BreakerOption

type BreakerOption interface {
	// contains filtered or unexported methods
}

BreakerOption interface for applying configuration in the constructor

func WithClock

func WithClock(clock clock.Clock) BreakerOption

WithClock Set the clock

func WithCounterResetInterval

func WithCounterResetInterval(interval time.Duration) BreakerOption

WithCounterResetInterval Set the interval of the circuit breaker, which is the cyclic time period to reset the internal counters

func WithFailOnContextCancel

func WithFailOnContextCancel(failOnContextCancel bool) BreakerOption

WithFailOnContextCancel Set if the context should fail on cancel

func WithFailOnContextDeadline

func WithFailOnContextDeadline(failOnContextDeadline bool) BreakerOption

WithFailOnContextDeadline Set if the context should fail on deadline

func WithHalfOpenMaxSuccesses

func WithHalfOpenMaxSuccesses(maxSuccesses int64) BreakerOption

WithHalfOpenMaxSuccesses Set the number of half open successes

func WithOnStateChangeHookFn

func WithOnStateChangeHookFn(hookFn StateChangeHook) BreakerOption

WithOnStateChangeHookFn set a hook function that trigger if the condition of the StateChangeHook is true

func WithOpenTimeout

func WithOpenTimeout(timeout time.Duration) BreakerOption

WithOpenTimeout Set the timeout of the circuit breaker

func WithOpenTimeoutBackOff

func WithOpenTimeoutBackOff(backoff backoff.BackOff) BreakerOption

WithOpenTimeoutBackOff Set the time backoff

func WithTripFunc

func WithTripFunc(tripFunc TripFunc) BreakerOption

WithTripFunc Set the function for counter

type CircuitBreaker

type CircuitBreaker struct {
	// contains filtered or unexported fields
}

CircuitBreaker provides circuit breaker pattern.

Example
cb := circuitbreaker.New(nil)
ctx := context.Background()

data, err := cb.Do(context.Background(), func() (interface{}, error) {
	user, err := fetchUserInfo(ctx, "太郎")
	if err != nil && err.Error() == "UserNoFound" {
		// If you received a application level error, wrap it with Ignore to
		// avoid false-positive circuit open.
		return nil, circuitbreaker.Ignore(err)
	}
	return user, err
})

if err != nil {
	log.Fatalf("failed to fetch user:%s\n", err.Error())
}
log.Printf("fetched user:%+v\n", data.(*user))
Output:

func New

func New(opts ...BreakerOption) *CircuitBreaker

New returns a new CircuitBreaker The constructor will be instanced using the functional options pattern. When creating a new circuit breaker we should pass or left it blank if we want to use its default options. An example of the constructor would be like this:

cb := circuitbreaker.New(

    circuitbreaker.WithClock(clock.New()),
    circuitbreaker.WithFailOnContextCancel(true),
    circuitbreaker.WithFailOnContextDeadline(true),
    circuitbreaker.WithHalfOpenMaxSuccesses(10),
    circuitbreaker.WithOpenTimeoutBackOff(backoff.NewExponentialBackOff()),
    circuitbreaker.WithOpenTimeout(10*time.Second),
    circuitbreaker.WithCounterResetInterval(10*time.Second),
    // we also have NewTripFuncThreshold and NewTripFuncConsecutiveFailures
    circuitbreaker.WithTripFunc(circuitbreaker.NewTripFuncFailureRate(10, 0.4)),
    circuitbreaker.WithOnStateChangeHookFn(func(from, to circuitbreaker.State) {
      log.Printf("state changed from %s to %s\n", from, to)
	}),

)

The default options are described in the defaultOptions function

func (*CircuitBreaker) Counters

func (cb *CircuitBreaker) Counters() Counters

Counters returns internal counters. If current status is not StateClosed, returns zero value.

func (*CircuitBreaker) Do

func (cb *CircuitBreaker) Do(ctx context.Context, o Operation) (interface{}, error)

Do executes the Operation o and returns the return values if cb.Ready() is true. If not ready, cb doesn't execute f and returns ErrOpen.

If o returns a nil-error, cb counts the execution of Operation as a success. Otherwise, cb count it as a failure.

If o returns a *IgnorableError, Do() ignores the result of operation and returns the wrapped error.

If o returns a *SuccessMarkableError, Do() count it as a success and returns the wrapped error.

If given Options' FailOnContextCancel is false (default), cb.Do doesn't mark the Operation's error as a failure if ctx.Err() returns context.Canceled.

If given Options' FailOnContextDeadline is false (default), cb.Do doesn't mark the Operation's error as a failure if ctx.Err() returns context.DeadlineExceeded.

func (*CircuitBreaker) Done

func (cb *CircuitBreaker) Done(ctx context.Context, err error) error

Done is a helper function to finish the protected operation. If err is nil, Done calls Success and returns nil. If err is a SuccessMarkableError or IgnorableError, Done returns wrapped error. Otherwise, Done calls FailWithContext internally.

func (*CircuitBreaker) Fail

func (cb *CircuitBreaker) Fail()

Fail signals that an execution of operation has been failed to cb.

func (*CircuitBreaker) FailWithContext

func (cb *CircuitBreaker) FailWithContext(ctx context.Context)

FailWithContext calls Fail internally. But if FailOnContextCancel is false and ctx is done with context.Canceled error, no Fail() called. Similarly, if FailOnContextDeadline is false and ctx is done with context.DeadlineExceeded error, no Fail() called.

func (*CircuitBreaker) Ready

func (cb *CircuitBreaker) Ready() bool

Ready reports if cb is ready to execute an operation. Ready does not give any change to cb.

func (*CircuitBreaker) Reset

func (cb *CircuitBreaker) Reset()

Reset resets cb's state with StateClosed.

func (*CircuitBreaker) SetState

func (cb *CircuitBreaker) SetState(st State)

SetState set state of cb to st.

func (*CircuitBreaker) State

func (cb *CircuitBreaker) State() State

State reports the curent State of cb.

func (*CircuitBreaker) Success

func (cb *CircuitBreaker) Success()

Success signals that an execution of operation has been completed successfully to cb.

type Counters

type Counters struct {
	Successes            int64
	Failures             int64
	ConsecutiveSuccesses int64
	ConsecutiveFailures  int64
}

Counters holds internal counter(s) of CircuitBreaker.

type IgnorableError

type IgnorableError struct {
	// contains filtered or unexported fields
}

IgnorableError signals that the operation should not be marked as a failure.

func (*IgnorableError) Error

func (e *IgnorableError) Error() string

func (*IgnorableError) Unwrap

func (e *IgnorableError) Unwrap() error

Unwrap unwraps e.

type Operation

type Operation func() (interface{}, error)

An Operation is executed by Do().

type State

type State string

State represents the internal state of CB.

const (
	StateClosed   State = "closed"
	StateOpen     State = "open"
	StateHalfOpen State = "half-open"
)

State constants.

type StateChangeHook

type StateChangeHook func(oldState, newState State)

StateChangeHook is a function which will be invoked when the state is changed.

type SuccessMarkableError

type SuccessMarkableError struct {
	// contains filtered or unexported fields
}

SuccessMarkableError signals that the operation should be mark as success.

func (*SuccessMarkableError) Error

func (e *SuccessMarkableError) Error() string

func (*SuccessMarkableError) Unwrap

func (e *SuccessMarkableError) Unwrap() error

Unwrap unwraps e.

type TripFunc

type TripFunc func(*Counters) bool

TripFunc is a function to determine if CircuitBreaker should open (trip) or not. TripFunc is called when cb.Fail was called and the state was StateClosed. If TripFunc returns true, the cb's state goes to StateOpen.

func NewTripFuncConsecutiveFailures

func NewTripFuncConsecutiveFailures(threshold int64) TripFunc

NewTripFuncConsecutiveFailures provides a TripFunc that returns true if the consecutive failures is larger than or equals to threshold.

func NewTripFuncFailureRate

func NewTripFuncFailureRate(min int64, rate float64) TripFunc

NewTripFuncFailureRate provides a TripFunc that returns true if the failure rate is higher or equals to rate. If the samples are fewer than min, always returns false.

func NewTripFuncThreshold

func NewTripFuncThreshold(threshold int64) TripFunc

NewTripFuncThreshold provides a TripFunc. It returns true if the Failures counter is larger than or equals to threshold.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL