内容简介:在分布式环境中,各个微服务相互调用,当某些情况下,比如后端中间件服务故障、第三方服务中断导致某个服务无限期不可用,短时间无法恢复,则可能会导致连锁故障,最终影响压垮整个业务集群断路器模式不同于重试模式,重试模式是使应用程序可以重试操作以期望它会成功,而断路器模式是防止应用程序执行一个可能失败的操作,减少执行可能失败操作的CPU、内存、线程等资源的浪费,从而保证服务的整体可用断路器相当于一个请求操作执行的代理,托管请求操作的执行
断路器背景
微服务连锁故障场景
image
在分布式环境中,各个微服务相互调用,当某些情况下,比如后端中间件服务故障、第三方服务中断导致某个服务无限期不可用,短时间无法恢复,则可能会导致连锁故障,最终影响压垮整个业务集群
断路器与重试
断路器模式不同于重试模式,重试模式是使应用程序可以重试操作以期望它会成功,而断路器模式是防止应用程序执行一个可能失败的操作,减少执行可能失败操作的CPU、内存、线程等资源的浪费,从而保证服务的整体可用
断路器设计解析
基于代理模式的断路器
image
断路器相当于一个请求操作执行的代理,托管请求操作的执行
实现原理流程:
- 拦截服务执行的请求,通过当前状态决定是否直接返回,如果否则执行后续操作
- 尝试执行操作,并获取返回结果
- 根据返回结果和当前统计信息,决定当前断路器的状态,修改状态
- 返回执行结果
断路器状态机
image
断路器状态机实现上有三种状态:Closed(断路器关闭)、Open(开放)、HalfOpen(半开放)
| 状态 | 说明 | 备注 |
|---|---|---|
| Closed | 关闭 | 断路器关闭正常执行操作 |
| Open | 打开 | 断路器开放,所有请求直接返回错误,不执行任何请求 |
| HalfOpen | 半开放 | 允许有限数量的请求通过,如果执行成功,恢复到关闭状态,如果仍然失败,则恢复到开放,然后重新启动超时定时器 |
断路器实现
实现原理图解
image
断路器实现实现主要分为三部分:状态统计、状态转移、请求执行
状态统计:统计已经执行的请求的成功失败的数量,以确定是否需要进行状态转移
状态转移:根据当前统计信息和当前状态来进行目标状态的确定及转移操作
请求执行:代理前端任务的执行,如果当前状态不需要进行尝试执行,就直接返回错误,避免资源浪费
Golang里面已经有开源的实现, https://github.com/sony/gobreaker/blob/ , 接下来救市剖析它的实现
状态统计-计数器Counts
Counts就是一个计数器,记录当前请求成功和失败的数量
type Counts struct {
Requests uint32 // 请求数
TotalSuccesses uint32 // 成功
TotalFailures uint32 // 失败
ConsecutiveSuccesses uint32 // 连续成功
ConsecutiveFailures uint32 // 连续失败
}
计数器完成对应请求状态的次数,为后续状态转移提供数据, Counts提供了onRequest、onSuccess、onFailure、clear几个辅助接口用于实现对应请求状态的操作,感兴趣可以看下
状态机- CircuitBreaker
type CircuitBreaker struct {
name string
// maxRequests限制half-open状态下最大的请求数,避免海量请求将在恢复过程中的服务再次失败
maxRequests uint32
// interval用于在closed状态下,断路器多久清除一次Counts信息,如果设置为0则在closed状态下不会清除Counts
interval time.Duration
// timeout进入open状态下,多长时间切换到half-open状态,默认60s
timeout time.Duration
// readyToTrip熔断条件,当执行失败后,会根据readyToTrip决定是否进入Open状态
readyToTrip func(counts Counts) bool
// onStateChange断路器状态变更回调函数
onStateChange func(name string, from State, to State)
mutex sync.Mutex
//. state 断路器状态
state State
// generation 是一个递增值,相当于当前断路器状态切换的次数, 为了避免状态切换后,未完成请求对新状态的统计的影响,如果发现一个请求的generation同当前的generation不同,则不会进行统计计数
generation uint64
// Counts 统计
counts Counts
// expiry 超时过期用于open状态到half-open状态的切换,当超时后,会从open状态切换到half-open状态
expiry time.Time
}
核心流程
CircuitBreaker.Execute
请求执行,对外开放的请求执行接口
func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
// 执行请求钩子,会根据当前状态,来返回当前的generation和err(如果位于open和half-open则不为nil), 通过err来进行判断是否直接返回
generation, err := cb.beforeRequest()
if err != nil {
return nil, err
}
// 捕获panic,避免应用函数错误造成断路器panic
defer func() {
e := recover()
if e != nil {
cb.afterRequest(generation, false)
panic(e)
}
}()
// 执行请求
result, err := req()
// 根据结果来进行对应状态的统计, 同时传递generation
cb.afterRequest(generation, err == nil)
return result, err
}
CircuitBreaker.beforeRequest
func (cb *CircuitBreaker) beforeRequest() (uint64, error) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
// 获取当前的状态
now := time.Now()
state, generation := cb.currentState(now)
// open和half-open状态则直接返回
if state == StateOpen {
return generation, ErrOpenState
} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {
// 避免海量请求对处于恢复服务的影响,这里有一个限流的操作,避免请求数超过最大请求数
return generation, ErrTooManyRequests
}
// 统计状态
cb.counts.onRequest()
return generation, nil
}
CircuitBreaker.afterRequest
func (cb *CircuitBreaker) afterRequest(before uint64, success bool) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
// 重新获取状态
now := time.Now()
state, generation := cb.currentState(now)
// 如果前后状态不一致,则不计数
if generation != before {
return
}
// 根据状态计数
if success {
cb.onSuccess(state, now)
} else {
cb.onFailure(state, now)
}
}
CircuitBreaker.currentState
func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) {
switch cb.state {
case StateClosed:
// 如果当前当前是closed状态,并且有设置expiry,则递增Generation到新一轮统计计数
if !cb.expiry.IsZero() && cb.expiry.Before(now) {
cb.toNewGeneration(now)
}
case StateOpen:
// 如果是Open状态,并且超时,则尝试到半打开状态
if cb.expiry.Before(now) {
cb.setState(StateHalfOpen, now)
}
}
return cb.state, cb.generation
}
CircuitBreaker.toNewgeneration
func (cb *CircuitBreaker) toNewGeneration(now time.Time) {
// 递增generation, 清除状态
cb.generation++
cb.counts.clear()
// 设置超时时间
var zero time.Time
switch cb.state {
case StateClosed:
if cb.interval == 0 {
cb.expiry = zero
} else {
cb.expiry = now.Add(cb.interval)
}
case StateOpen:
cb.expiry = now.Add(cb.timeout)
default: // StateHalfOpen
cb.expiry = zero
}
}
总结
断路器黄金链路
image
- beforeRequest :完成当前请求是否可以执行请求,状态超时切换,同时返回当前的genenration
- req: 执行请求
- afterRequest: 完成请求状态统计,决定状态切换
断路器的优缺点
断路器比较适合针对远程服务或者第三方服务的调用,如果该操作极有可能会失败,则断路器可以尽可能的减小失败对应用的影响,避免资源浪费
但缺点也显而易见,断路器本身相当于一层代理,在应用程序执行进行统计和控制,本身就有一定的资源消耗,同时内部基于synx.Mutex锁来实现,高并发下肯定会有锁争用问题,可能需要根据业务来使用多个断路器,来分散这种锁争用,同时应该避免在断路器req函数内,去执行重试和过长时间的超时等待,因为断路器核心是快速失败
更多文章可以访问 http://www.sreguide.com/
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design for Hackers
David Kadavy / Wiley / 2011-10-18 / USD 39.99
Discover the techniques behind beautiful design?by deconstructing designs to understand them The term ?hacker? has been redefined to consist of anyone who has an insatiable curiosity as to how thin......一起来看看 《Design for Hackers》 这本书的介绍吧!
RGB转16进制工具
RGB HEX 互转工具
XML 在线格式化
在线 XML 格式化压缩工具