一些场景下,为了保障服务稳定性会引入熔断机制。本文介绍了用 Go 语言自己实现熔断需要什么操作。

什么是熔断?

熔断是指在下游发生错误时上游主动关闭或限制对下游的请求。

原理

  1.  通常熔断器分为三个时期:CLOSED,OPEN,HALFOPEN
  2.  RPC 正常时,为 CLOSED;
  3.  当 RPC 错误增多时,熔断器会被触发,进入 OPEN;
  4.  OPEN 后经过一定的冷却时间,熔断器变为 HALFOPEN;
  5.  HALFOPEN 时会对下游进行一些有策略的访问,然后根据结果决定是变为 CLOSED,还是 OPEN;

总得来说三个状态的转换大致如下图:

Go 实现

IsAllowed 是否允许请求,根据当前状态判断

CLOSE 允许

OPEN

  •  在 CoolingTimeout 冷却时间内,不允许
  •  过了冷却时间,状态变为 HALFOPEN,允许访问

HALFOPEN

  •  在 DetectTimeout 检测时间内,允许访问
  •  否则不允许

atomic.StoreInt32((*int32)(&b.state), int32(HALFOPEN))

trip 判断是否达到熔断限额(可以自定义)

  1. type TripFunc func(Metricser) bool 
  •  ThresholdTripFunc 错误阈值
  •  ConsecutiveTripFunc 连续错误超过阈值
  •  RateTripFunc 根据最少访问数和错误率判断

Metricser 访问统计,包括成功数、失败数、超时数、错误率、采样数、连续错误数 

  1. type Metricser interface {  
  2.    Fail()    // records a failure  
  3.    Succeed() // records a success  
  4.    Timeout() // records a timeout  
  5.    Failures() int64    // return the number of failures  
  6.    Successes() int64   // return the number of successes  
  7.    Timeouts() int64    // return the number of timeouts  
  8.    ConseErrors() int64 // return the consecutive errors recently  
  9.    ErrorRate() float64 // rate = (timeouts + failures) / (timeouts + failures + successes)  
  10.    Samples() int64     // (timeouts + failures + successes)  
  11.    Counts() (successes, failures, timeouts int64)  
  12.    Reset()  

window 实现类 

  1. type window struct {  
  2.    sync.RWMutex  
  3.    oldest  int32     // oldest bucket index  
  4.    latest  int32     // latest bucket index  
  5.    buckets []bucket // buckets this window holds  
  6.    bucketTime time.Duration // time each bucket holds  
  7.    bucketNums int32         // the numbe of buckets  
  8.    inWindow   int32         // the number of buckets in the window  
  9.    allSuccess int64  
  10.    allFailure int64  
  11.    allTimeout int64  
  12.    conseErr int64  
  13. }  
  14. type bucket struct {  
  15.    failure int64  
  16.    success int64  
  17.    timeout int64  

用环形队列实现动态统计。把一个连续的时间切成多个小份,每一个 bucket 保存 BucketTime 的统计数据,BucketTime * BucketNums 是统计的时间区间。

每 BucketTime,会有一个 bucket 过期 

  1. if w.inWindow == w.bucketNums {  
  2.    // the lastest covered the oldest(latest == oldest)  
  3.    oldBucket := &w.buckets[w.oldest]  
  4.    atomic.AddInt64(&w.allSuccess, -oldBucket.Successes())  
  5.    atomic.AddInt64(&w.allFailure, -oldBucket.Failures())  
  6.    atomic.AddInt64(&w.allTimeout, -oldBucket.Timeouts())  
  7.    w.oldest++  
  8.    if w.oldest >= w.bucketNums {  
  9.       w.oldest = 0  
  10.    }  
  11. } else {  
  12.    w.inWindow++  
  13. }  
  14. w.latest++ 
  15. if w.latest >= w.bucketNums {  
  16.    w.latest = 0  
  17. }  
  18. (&w.buckets[w.latest]).Reset() 

Panel Metricser 的容器

PanelStateChangeHandler 熔断事件 

  1. type PanelStateChangeHandler func(key string, oldState, newState State, m Metricser) 

缺陷

  1.  所有 breaker 公用同一个 BucketTime,统计周期不支持更新
  2.  冷却时间不支持动态更新