在一个系统中,一个服务通常会依赖许多其他的服务,并且服务在某些时候失败是不可避免的。
如果我们失败的服务变得无响应,所有依赖它的服务也有变得无响应的风险。这就是所谓的 级联故障。
相关推荐
golang 实现时间滑动窗口_wangxiaoangg的博客-CSDN博客
应对过载-客户端限流算法_wangxiaoangg的博客-CSDN博客
一 熔断器原理
熔断器的基本思想非常简单。熔断器通过包装对目标服务的调用,来持续监控故障率。一旦故障达到某个阈值(阈值的设定可以参考文章:应对过载-客户端限流算法),熔断器将打开,并且所有进一步调用都返回故障或错误。
当一个服务变得无响应时,依赖它的服务应该停止等待,并开始处理失败逻辑。通过防止单个服务的故障导致整个系统中发生级联故障。
熔断器有三种状态:
-
closed:熔断器关闭。请求会被传递到目标服务端。熔断器继续监控错误率、请求数和超时等指标。当这些指标超过特定阈值时,断路器将跳闸并转换为
open
状态。 -
open:熔断器打开。请求不传递到目标服务端,而是
fallback
调用逻辑(由开发人员自行设定)来处理失败。熔断器会在open
调用状态保持一段时间sleeping window
,之后断路器可以从open
过渡到half-open
。 -
half-open:在此状态下,将有限数量的请求传递给目标服务端,用了探测目标服务器的可用状态是否恢复。如果目标服务可以成功响应,则
reset
中断返回closed
状态。否则熔断器将转换回open
状态。
二 go-zero中的熔断器实现
go zero中熔断器的实现参考了Google Sre过载保护算法;代码路径为go-zero/core/breaker。
1.googleBreaker结构体
const (
// 250ms for bucket duration
window = time.Second * 10
buckets = 40
k = 1.5
protection = 5
)
// googleBreaker is a netflixBreaker pattern from google.
// see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/
type googleBreaker struct {
k float64 //k用于调节 熔断器敏感度,默认1.5
stat *collection.RollingWindow //时间滑动窗口,用于记录过去一段时间,请求数和接受数
proba *mathx.Proba //随机数
}
- 属性K,用于调节 熔断器敏感度,默认1.5 。K值越小 熔断器越敏感
- stat,时间滑动窗口,用于记录过去一段时间 系统的请求数和成功数。(滑动窗口实现可参考golang 实现时间滑动窗口_wangxiaoangg的博客-CSDN博客)
默认 滑动窗口时间10s,样本窗口时间250ms
2. accept()
accept()方法用于判断是否要要丢弃当前请求
func (b *googleBreaker) accept() error {
//获取当前总的发送数和接受数
accepts, total := b.history()
weightedAccepts := b.k * float64(accepts)
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
if dropRatio <= 0 {
return nil
}
//dropRatio丢弃请求的概率,当生成的随机数小于 dropRatio时,则不发送请求到服务端
if b.proba.TrueOnProba(dropRatio) {
return ErrServiceUnavailable
}
return nil
}
3.doReq()
发送请求到服务端。
首先通过accept校验是否触发熔断,触发熔断时,直接调用失败逻辑。
func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
//判断是否要丢弃请求
if err := b.accept(); err != nil {
if fallback != nil {
return fallback(err)
}
return err
}
defer func() {
if e := recover(); e != nil {
b.markFailure()
panic(e)
}
}()
//发送请求
err := req()
//请求成功 滑动窗口accepts +1
if acceptable(err) {
b.markSuccess()
} else {
b.markFailure()
}
return err
}
4.计数相关
markSuccess:总的请求数+1,accepts +1
markFailure:总的请求数+1,accepts +0
history:获取窗口内总的请求数,和成功的请求数
func (b *googleBreaker) markSuccess() {
b.stat.Add(1)
}
func (b *googleBreaker) markFailure() {
b.stat.Add(0)
}
func (b *googleBreaker) history() (accepts, total int64) {
b.stat.Reduce(func(b *collection.Bucket) {
accepts += int64(b.Sum)
total += b.Count
})
return
}