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]<-------+ 34 ^ | ^ 35 | v | 36 + | detect fail 37 | | | 38 | cooling timeout | 39 ^ | ^ 40 | v | 41 +--- detect succeed --<-[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.