最近看了一下go-kit,发现这个微服务框架的熔断器,也是使用sony开源的作为基础。 sony开源在 github 的熔断器 在源代头注释中发现,原来sony实现的是微软2015时公布的CircuitBreaker标准,果然微软才开源界的大神。

1 微软定义的 Circuit breaker

微软的原文件在此:https://msdn.microsoft.com/en-us/library/dn589784.aspx 名不知道怎么正确翻译,直观翻译,可能叫:环形熔断器(或叫:循环状态自动切换中断器)。 因为它是在下面3个状态循环切换 :

         Closed 
         /    \
 Half-Open <--> Open

初始状态是:Closed,指熔断器放行所有请求。
达到一定数量的错误计数,进入Open 状态,指熔断发生,下游出现错误,不能再放行请求。
经过一段Interval时间后,自动进入Half-Open状态,然后开始尝试对成功请求计数。
进入Half-Open后,根据成功/失败计数情况,会自动进入Closed或Open。

2 sony开源的go实现

// 从定义的错误来看,sony的应该增加了对连接数进行了限制 。
 var (
	// ErrTooManyRequests is returned when the CB state is half open and the requests count is over the cb maxRequests
	ErrTooManyRequests = errors.New("too many requests")
	// ErrOpenState is returned when the CB state is open
	ErrOpenState = errors.New("circuit breaker is open")
)

2.1  通过Settings的实现,了解可配置功能:

type Settings struct {
	Name          string
	MaxRequests   uint32        // 半开状态期最大允许放行请求:即进入Half-Open状态时,一个时间周期内允许最大同时请求数(如果还达不到切回closed状态条件,则不能再放行请求)。
	Interval      time.Duration // closed状态时,重置计数的时间周期;如果配为0,切入Open后永不切回Closed--有点暴力。
	Timeout       time.Duration // 进入Open状态后,多长时间会自动切成 Half-open,默认60s,不能配为0。

    // ReadyToTrip回调函数:进入Open状态的条件,比如默认是连接5次出错,即进入Open状态,即可对熔断条件进行配置。在fail计数发生后,回调一次。
	ReadyToTrip   func(counts Counts) bool 

	// 状态切换时的熔断器
	OnStateChange func(name string, from State, to State)
}

2.2 核心的*执行函数*实现

要把熔断器使用到工程中,只需要,实例化一个gobreaker,再使用这个Execute包一下原来的请求函数。

func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
	generation, err := cb.beforeRequest() // 
	if err != nil {
		return nil, err
	}

	defer func() {
		e := recover()
		if e != nil {
			cb.afterRequest(generation, false)
			panic(e) // 如果代码发生了panic,继续panic给上层调用者去recover。
		}
	}()

	result, err := req()
	cb.afterRequest(generation, err == nil)
	return result, err
}

2.3 关键 func beforeRequest()

函数做了几件事:

  1. 函数的核心功能:判断是否放行请求,计数或达到切换新条件刚切换。
  2. 判断是否Closed,如是,放行所有请求。
    • 并且判断时间是否达到Interval周期,从而清空计数,进入新周期,调用toNewGeneration()
  3. 如果是Open状态,返回ErrOpenState,—不放行所有请求。
    • 同样判断周期时间,到达则 同样调用 toNewGeneration(){清空计数}
  4. 如果是half-open状态,则判断是否已放行MaxRequests个请求,如未达到刚放行;否则返回:ErrTooManyRequests。
  5. 此函数一旦放行请求,就会对请求计数加1(conut.onRequest()),请求后到另一个关键函数 : afterRequest()。

2.4 关键 func afterRequest()

  1. 函数核心内容很简单,就对成功/失败进行计数,达到条件则切换状态。
  2. 与beforeRequest一样,会调用公共函数 currentState(now)
    • currentState(now) 先判断是否进入一个先的计数时间周期(Interval), 是则重置计数,改变熔断器状态,并返回新一代。
    • 如果request耗时大于Interval, 几本每次都会进入新的计数周期,熔断器就没什么意义了。

3 代码的核心内容

  1. 使用了一个generation的概念,每一个时间周期(Interval)的计数(count)状态称为一个generation。
  2. 在before/after的两个函数中,实现了两个状态自动切换的机制:
    • 在同一个generation(即时间)周期内,计数满足状态切换条件,即自动切换;
    • 超过一个generation时间周期的也会自动切换;
  3. 没有使用定时器,只在请求调用时,去检测当时状态与时间间隔。

 

4 如何使用(来自github)

4.1 Installation

go get github.com/sony/gobreaker

4.2 Usage

The struct CircuitBreaker is a state machine to prevent sending requests that are likely to fail. The function NewCircuitBreaker creates a new CircuitBreaker.

func NewCircuitBreaker(st Settings) *CircuitBreaker

You can configure CircuitBreaker by the struct Settings:

type Settings struct {
	Name          string
	MaxRequests   uint32
	Interval      time.Duration
	Timeout       time.Duration
	ReadyToTrip   func(counts Counts) bool
	OnStateChange func(name string, from State, to State)
	IsSuccessful  func(err error) bool
}
  • Name is the name of the CircuitBreaker.

  • MaxRequests is the maximum number of requests allowed to pass through when the CircuitBreaker is half-open. If MaxRequests is 0, CircuitBreaker allows only 1 request.

  • Interval is the cyclic period of the closed state for CircuitBreaker to clear the internal Counts, described later in this section. If Interval is 0, CircuitBreaker doesn't clear the internal Counts during the closed state.

  • Timeout is the period of the open state, after which the state of CircuitBreaker becomes half-open. If Timeout is 0, the timeout value of CircuitBreaker is set to 60 seconds.

  • ReadyToTrip is called with a copy of Counts whenever a request fails in the closed state. If ReadyToTrip returns true, CircuitBreaker will be placed into the open state. If ReadyToTrip is nil, default ReadyToTrip is used. Default ReadyToTrip returns true when the number of consecutive failures is more than 5.

  • OnStateChange is called whenever the state of CircuitBreaker changes.

  • IsSuccessful is called with the error returned from a request. If IsSuccessful returns true, the error is counted as a success. Otherwise the error is counted as a failure. If IsSuccessful is nil, default IsSuccessful is used, which returns false for all non-nil errors.

The struct Counts holds the numbers of requests and their successes/failures:

type Counts struct {
	Requests             uint32
	TotalSuccesses       uint32
	TotalFailures        uint32
	ConsecutiveSuccesses uint32
	ConsecutiveFailures  uint32
}

CircuitBreaker clears the internal Counts either on the change of the state or at the closed-state intervals. Counts ignores the results of the requests sent before clearing.

CircuitBreaker can wrap any function to send a request:

func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error)

The method Execute runs the given request if CircuitBreaker accepts it. Execute returns an error instantly if CircuitBreaker rejects the request. Otherwise, Execute returns the result of the request. If a panic occurs in the request, CircuitBreaker handles it as an error and causes the same panic again.

4.3 Example

var cb *breaker.CircuitBreaker

func Get(url string) ([]byte, error) {
	body, err := cb.Execute(func() (interface{}, error) {
		resp, err := http.Get(url)
		if err != nil {
			return nil, err
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}

		return body, nil
	})
	if err != nil {
		return nil, err
	}

	return body.([]byte), nil
}

See example for details.

 

参考来源

【https://bytemode.github.io/articles/sony-gobreaker/readme/】

【https://github.com/sony/gobreaker】

【https://blog.csdn.net/qq_33339479/article/details/109217221】

posted on 2022-04-25 15:46  巴黎河畔  阅读(433)  评论(0编辑  收藏  举报