熔断器是当依赖的服务已经出现故障时,为了保证自身服务的正常运行不在访问依赖的服务,防止雪崩效应
使用场景举个例子:
服务A调用服务B时,B的故障会导致A的故障。因为A调用B然后一直阻塞没有响应,资源被占用得不到释放。
两种分布式高可用的做法
- 限流器:服务B使用限流器限制A的请求量,从而导致过多的请求导致处理不过来导致宕机。【作用于被调用者(B)】
- 熔断器:服务A调用B的时候,如果B已经挂了,虽然B可能使用限流器什么的做到高可用,但是万一停电了或者其他的原因反正就是不可用了。A调用得到错误时就要使用熔断器,阻断自己的请求。【作用于调用者(A)】
熔断器工作原理
在熔断器中有三种状态:
- 关闭:让请求通过的默认状态。如果请求成功/失败但低于阈值,则状态保持不变。可能出现的错误是超过最大并发数和超时错误。
- 打开:当熔断器打开的时候,所有的请求都会被标记为失败;这是故障快速失败机制,而不需要等待超时时间完成。
- 半开:定期的尝试发起请求来确认系统是否恢复。如果恢复了,熔断器将转为关闭状态或者保持打开
这三个状态是怎么维持并转换的:
需要服务调用者去统计一段时间内调用被调用者【也就是系统中的其他服务】的失败率(超时、或者500、或者不符合你的预期都可以算失败)。需要一个数据结构去统计保存这个状态,并且每个周期的开始清空这个状态。
如何手动实现一个熔断器:
大体分为三步
- 划分时间窗口,设置判断条件
- 请求进来判断是否满足熔断条件【并且拒绝or处理】
- 请求处理完后,统计时间窗口内请求失败率、延迟不达标率、请求数等指标
package simplebreaker
import (
"log"
"sync/atomic"
"time"
)
// 1.
type Counter int64
func (c *Counter) Add() int64 {
return atomic.AddInt64((*int64)(c), 1)
}
func (c *Counter) Load() int64 {
return atomic.LoadInt64((*int64)(c))
}
func (c *Counter) Reset() {
atomic.StoreInt64((*int64)(c), 0)
}
// 2.
type CircuitBreaker struct {
totalCounter Counter
failCounter Counter
duration int64
latencyLimit int64
totalLimit int64
failRateLimit int64
recoverFailRate int64
lastTime int64
allow int64
}
type CBOption func(cb *CircuitBreaker)
const (
minDuration = 100
minTotal = 1000
minFailRate = 2
)
func WithDuration(duration int64) CBOption {
return func(cb *CircuitBreaker) {
cb.duration = duration
}
}
func WithLatencyLimit(latencyLimit int64) CBOption {
return func(cb *CircuitBreaker) {
cb.latencyLimit = latencyLimit
}
}
func WithFailsLimit(failsRateLimit int64) CBOption {
return func(cb *CircuitBreaker) {
cb.failRateLimit = failsRateLimit
}
}
func WithTotalLimit(totalLimit int64) CBOption {
return func(cb *CircuitBreaker) {
cb.totalLimit = totalLimit
}
}
func NewCircuitBreaker(opts ...CBOption) *CircuitBreaker {
cb := &CircuitBreaker{
totalCounter: 0,
failCounter: 0,
duration: 0,
latencyLimit: 0,
totalLimit: 0,
failRateLimit: 0,
recoverFailRate: 0,
lastTime: 0,
allow: 1,
}
for _, opt := range opts {
opt(cb)
}
if cb.duration < minDuration {
cb.duration = minDuration
}
if cb.totalLimit < minTotal {
cb.totalLimit = minTotal
}
if cb.failRateLimit < minFailRate {
cb.failRateLimit = minFailRate
}
cb.recoverFailRate = cb.failRateLimit / 2
return cb
}
// 3.
func (cb *CircuitBreaker) Allow(f func() bool) bool {
fail := cb.failCounter.Load()
total := cb.totalCounter.Load()
start := time.Now().UnixNano() / int64(time.Millisecond)
if start > cb.lastTime+cb.duration {
atomic.StoreInt64(&cb.lastTime, start)
cb.failCounter.Reset()
cb.totalCounter.Reset()
atomic.StoreInt64(&cb.allow, 1)
}
cb.totalCounter.Add()
allow := !(total > 0 && fail*100/cb.failRateLimit >= total || total >= cb.totalLimit)
if atomic.LoadInt64(&cb.allow) == 0 {
if fail*100/cb.recoverFailRate > total {
allow = false
} else if allow {
atomic.StoreInt64(&cb.allow, 1)
}
} else if !allow {
atomic.StoreInt64(&cb.allow, 0)
}
if !allow {
log.Fatalln("not allowed")
return false
}
ok := f()
end := time.Now().UnixNano()/int64(time.Millisecond)
if (cb.latencyLimit > 0 && end-start >= cb.latencyLimit) || !ok {
cb.failCounter.Add()
}
return true
}
go-zero中的熔断器实现
1. 理解go-zero面向对象设计
breakers调用了Breaker实现功能,实现了Breaker的复用。不用每次都去New新的Breaker。
breaker定义了支持包的所有的接口、结构体
googlebreaker是熔断器的具体实现
// 这个函数是理解go-zero熔断实现核心的函数
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()
if acceptable(err) {
b.markSuccess()
} else {
b.markFailure()
}
return err
}
2. 理解算法实现
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
}
if b.proba.TrueOnProba(dropRatio) {
return ErrServiceUnavailable
}
return nil
}
Golang里面的熔断器:
https://github.com/tal-tech/go-zero/tree/master/core/breaker
https://github.com/go-kit/kit/tree/master/circuitbreaker
https://github.com/asim/go-micro/tree/master/plugins/wrapper/breaker
https://github.com/go-kratos/kratos/tree/master/pkg/net/netutil/breaker
Java里面的熔断器:
https://github.com/Netflix/Hystrix
https://github.com/alibaba/Sentinel