github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/cloud/circuitbreaker/README_ZH.MD (about)

     1  # Circuitbreaker
     2  
     3  ## 熔断器简单介绍
     4  ### 熔断器作用
     5  在进行RPC调用时, 下游服务难免会出错;
     6  
     7  当下游出现问题时, 如果上游继续对其进行调用, 既妨碍了下游的恢复, 也浪费了上游的资源;
     8  
     9  为了解决这个问题, 你可以设置一些动态开关, 当下游出错时, 手动的关闭对下游的调用;
    10  
    11  然而更好的办法是使用熔断器, 自动化的解决这个问题.
    12  
    13  这里是一篇更详细的[熔断器介绍](https://msdn.microsoft.com/zh-cn/library/dn589784.aspx).
    14  
    15  比较出名的熔断器当属hystrix了, 这里是他的[设计文档](https://github.com/Netflix/Hystrix/wiki).
    16  
    17  ### 熔断思路
    18  **熔断器的思路很简单: 根据RPC的成功失败情况, 限制对下游的访问;**
    19  
    20  通常熔断器分为三个时期: CLOSED, OPEN, HALFOPEN;
    21  
    22  RPC正常时, 为CLOSED;
    23  
    24  当RPC错误增多时, 熔断器会被触发, 进入OPEN;
    25  
    26  OPEN后经过一定的冷却时间, 熔断器变为HALFOPEN;
    27  
    28  HALFOPEN时会对下游进行一些有策略的访问, 然后根据结果决定是变为CLOSED, 还是OPEN;
    29  
    30  总得来说三个状态的转换大致如下图:
    31  
    32  <pre>
    33   [CLOSED] -->- tripped ----> [OPEN]&lt;-------+
    34      ^                          |           ^
    35      |                          v           |
    36      +                          |      detect fail
    37      |                          |           |
    38      |                    cooling timeout   |
    39      ^                          |           ^
    40      |                          v           |
    41      +--- detect succeed --&lt;-[HALFOPEN]-->--+
    42  </pre>
    43  
    44  ## 该包的使用
    45  
    46  ### 基本使用
    47  该包将RPC的各个情况分为三类: Succeed, Fail, Timeout, 并维护了一定时间窗口内三者的计数;
    48  
    49  在每次RPC前, 你应该调用IsAllowed()来决定是否发起RPC;
    50  
    51  并在调用完成后根据结果, 调用Succeed(), Fail(), Timeout()来反馈;
    52  
    53  同时该包还进行了并发数控制, 你在每次RPC完成后, 还必须调用Done();
    54  
    55  下面是一段例子:
    56  <pre>
    57  var p *Panel
    58  
    59  func init() {
    60      var err error
    61      p, err = NewPanel(nil, Options{
    62      	CoolingTimeout: time.Minute,
    63      	DetectTimeout:  time.Minute,
    64      	ShouldTrip:     ThresholdTripFunc(100),
    65      })
    66      if err != nil {
    67      	panic(err)
    68      }
    69  }
    70  
    71  func DoRPC() error {
    72      key := "remote::rpc::method"
    73      if p.IsAllowed(key) == false {
    74          return Err("Not allowed by circuitbreaker")
    75      }
    76  
    77      err := doRPC()
    78      if err == nil {
    79          p.Succeed(key)
    80      } else if IsFailErr(err) {
    81          p.Fail(key)
    82      } else if IsTimeout(err) {
    83          p.Timeout(key)
    84      }
    85      return err
    86  }
    87  
    88  func main() {
    89      ...
    90      for ... {
    91          DoRPC()
    92      }
    93      p.Close()
    94  }
    95  </pre>
    96  
    97  ### 熔断触发策略
    98  该包提供了三个基本的熔断触发策略:
    99  + 连续错误数达到阈值(ConsecutiveTripFunc)
   100  + 错误数达到阈值(ThresholdTripFunc)
   101  + 错误率达到阈值(RateTripFunc)
   102  
   103  当然, 你可以通过实现TripFunc函数来写自己的熔断触发策略;
   104  
   105  Circuitbreaker会在每次Fail或者Timeout时, 去调用TripFunc, 来决定是否触发熔断;
   106  
   107  ### 熔断冷却策略
   108  进入OPEN状态后, 熔断器会冷却一段时间, 默认是10秒, 当然该参数可配置(CoolingTimeout);
   109  
   110  在这段时期内, 所有的IsAllowed()请求将会被返回false;
   111  
   112  冷却完毕后进入HALFOPEN;
   113  
   114  ### 半打开时策略
   115  在HALFOPEN时, 熔断器每隔"一段时间"便会放过一个请求, 当连续成功"若干数目"的请求后, 熔断器将变为CLOSED; 如果其中有任意一个失败, 则将变为OPEN;
   116  
   117  该过程是一个逐渐试探下游, 并打开的过程;
   118  
   119  上述的"一段时间"(DetectTimeout)和"若干数目"(DEFAULT_HALFOPEN_SUCCESSES)都是可以配置的;
   120  
   121  ### 并发控制
   122  该熔断还进行了并发控制, 参数为MaxConcurrency;
   123  
   124  当并发数达到上限时, IsAllowed将会返回false;
   125  
   126  ### 统计
   127  ##### 默认参数
   128  熔断器会统计一段时间窗口内的成功, 失败和超时, 默认窗口大小是10S;
   129  
   130  时间窗口可以通过两个参数设置, 不过通常情况下你可以不用关心.
   131  
   132  ##### 统计方法
   133  统计方法是将该段时间窗口分为若干个桶, 每个桶记录一定固定时长内的数据;
   134  
   135  比如统计10秒内的数据, 于是可以将10秒的时间段分散到100个桶, 每个桶统计100ms时间段内的数据;
   136  
   137  Options中的BucketTime和BucketNums, 就分别对应了每个桶维护的时间段, 和桶的个数;
   138  
   139  如将BucketTime设置为100ms, 将BucketNums设置为100, 则对应了10秒的时间窗口;
   140  
   141  ##### 抖动
   142  随着时间的移动, 窗口内最老的那个桶会过期, 当最后那个桶过期时, 则会出现了抖动;
   143  
   144  举个例子:
   145  + 你将10秒分为了10个桶, 0号桶对应了[0S, 1S)的时间, 1号桶对应[1S, 2S), ..., 9号桶对应[9S, 10S);
   146  + 在10.1S时, 执行一次Succ, 则circuitbreaker内会发生下述的操作;
   147  + (1)检测到0号桶已经过期, 将其丢弃; (2)创建新的10号桶, 对应[10S, 11S); (3)将该次Succ放入10号桶内;
   148  + 在10.2S时, 你执行Successes()查询窗口内成功数, 则你得到的实际统计值是[1S, 10.2S)的数据, 而不是[0.2S, 10.2S);
   149  
   150  如果使用分桶计数的办法, 这样的抖动是无法避免的, 比较折中的一个办法是将桶的个数增多, 可以降低抖动的影响;
   151  
   152  如划分2000个桶, 则抖动对整体的数据的影响最多也就1/2000;
   153  
   154  在该包中, 默认的桶个数也是100, 桶时间为100ms, 总体窗口为10S;
   155  
   156  当时曾想过多种技术办法来避免这种问题, 但是都会引入更多其他的问题, 如果你有好的思路, 请issue或者MR.