斯诺克直播吧

保姆级教程golang熔断实践

  不得不说,我现在已经从「周更」变成「随机更」了,我自己都不知道哪天能更新,工作实在太忙了。

  好了快速步入正题,最近团队里的一个重点工作是增加系统的稳定性和可用性,因此避不开的话题就是熔断、降级、限流。

  这三个概念我在之前写的分布式系统系列中也有提及,有兴趣的可以在文末移步到之前的文章中阅读。

  熔断是一种通用能力,可以在服务端做也可以在客户端做。我们的项目中大多数都基于 go-zero 框架实现,而使用 go-zero 框架实现的项目自带服务端熔断能力,所以本文的目的是阐述如何在客户端侧实现熔断机制。

  由于 go-zero 内置熔断器能力,因此我们优先想到的是能否直接用 go-zero 框架内的熔断器组件,若能满足需求的话,也避免了增加额外的外部依赖。

  扒开 go-zero 的源码就能找到它的熔断器使用,以下是在使用 go-zero 构建 http 的服务端时,其通过 AOP 的方式利用 Handler 来注入熔断器的代码。这部分代码现在不用深究,等看完本篇文章,你再回头来看很容易知道它写的是什么意思。

  通过以上代码继续深入源码,我们得知了go-zero框架中的熔断器模块()其底层使用了 google 的熔断器思路来实现。

  可能说起熔断器,很多人脑子里第一印象是 netflix的 hystrix 。但是我认为 google 的思路更棒一些,两者的区别从效果来说就是 google 的方案自适应能力更强。因为 hystrix 中使用三种状态来控制,当状态为 open 期间,所有请求都会直接被拦截,相对更粗暴一些。为便于理解两者的不同,我用“水管”来比喻画了一张图。

  这里就不展开了,hystrix 的熔断器思路在我之前写的文章《 》中也有提到。如果对 google 的熔断器思路感兴趣的话,可以看这篇文章:

  直接使用非实例的 Do() 函数,需要定义一个标识 name,这个 name 就是熔断器的唯一标识。

  以上示例代码中的 Do() 函数中的 func() error,就是需要在熔断器的保护下执行的具体代码。

  以上的输出内容不是固定的,每次运行的结果都不同(为什么不同后面会提到原因)。其中“func circuit breaker is open”表示 Do()函数中的 func() error 直接被熔断器拦截了,没有实际执行。

  上面是最基本的使用方式,除此之外,go-zero 封装的 breaker 还提供以下几个能力:

  前面的三种使用方式中,Do() 函数的作用是将需要执行的代码放到熔断器内执行,而有时候我们可能不便将代码放到熔断器内,但是也想实现熔断的能力可以吗?当然可以。

  前提是,你得使用前两种持有 breaker 实例的方式。 go-zero 实现服务端熔断的 BreakerHandler 是利用这个机制来实现的,根据返回的 HttpCode 决定请求算成功还是失败(前面贴的第一段代码中的 17~21 行)。

  如果你使用熔断器的方式是前面提到的方式二和方式三,那就能通过调用下面的函数,将「池」中的 breaker 实例移除。这样的话,下次申请获取相同 name 的熔断器时会重新实例化一个新的 breaker,因此间接达到了清空计数器数字的效果。

  在前面熔断器生效的代码基础上,增加三行代码,就能看到不会再出现“func circuit breaker is open”了。

  在讲自定义计数规则之前先得了解一下 googleBreaker 的实现原理。googleBreaker 的底层实现基于一个「客户端请求拒绝概率」的公式:

  K: 一般建议该值在1.1~2之间。 数字越小触发熔断的概率越高,反之则越低。 如果K=2,意味着我们认为每接受 10 个请求,后端正常情况下最多只会拒绝 5 个请求,如果发现拒绝了6个,就触发熔断。

  在 go-zero 提供的 breaker 实现中,基于上面的公式增加了两处微调。

  第一处是,为了避免极端情况下发起第一次请求就出现失败而导致触发熔断,在 go-zero 的代码中针对上面公式中的「分子」增加了一个 protection 常量,该值固定为 5,因此分子部分实际在代码中是 requests - protection - K * accepts。

  第二处是,当公式计算的结果 0 时,不会直接触发熔断,而是会与一个半开半闭区间 [0.0,1.0) 的伪随机数对比,如果大于这个伪随机数则该次请求触发熔断。

  有时候,有些 error 我们可能不希望将其视作「不可用」的信号,因此,我们能够最终靠使用以下函数代替 Do(req func() error) error

  该函数多了一个 Acceptable 对象,该对象是一个函数,用于判断 error 是否是可忽略的:

  当某次 func() 的执行被熔断器拦截时,允许触发回调(callback)函数,以便外部调用方感知到这个事件,并基于此做一些其它的事情。比如使用降级方案来代替原 func() 的实现。

  该函数多了一个 fallback 的 func()。当某次请求由于触发熔断器导致被拦截时会被触发。触发方式是 sync 的,且 fallback 函数中返回的 err 即为调用方接收到 DoWithFallback 函数的返回值。直接上源码可能更好理解:

  可能有些想更进一步的小伙伴会问,熔断器的触发策略除了计数规则之外,其它的规则可以自定义吗?

  很遗憾,目前框架没有暴露相关的参数出来,都是在代码中固定写死的常量。除了前面提到的 protection ,还有 3 个常量与熔断器的触发策略相关。

  K 的含义前面有提到过,主要讲一下 window 和 buckets 变量的作用。

  googleBreaker 的底层使用了滑动窗口算法,这两个变量是用来定义滑动窗口的:

  含义是,将滑动窗口分为 40 个区间,每个区间对 250ms 内的请求进行计数。

  今天呢,Z 哥带你深入剖析了一下 go-zero 框架中的熔断器,以及教你怎么样去使用它。

  这篇文章比较干,如果你之前对熔断器了解不多的话,在大多数情况下要多花点时间仔细阅读几遍,消化一下。

  原创不易,如果你觉得这篇文章还不错,就「点赞」或者「在看」一下吧,鼓励我的创作 :)

  特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

  神奇的2-0!世界第57再遇葡萄牙,赢2-1=就送C罗出局,CCTV5直播

  广东跌1.5%,江苏近5000亿!31省市2024年1-5月财政收入排行出炉

  双胞胎兄弟体重相差150斤左右,家长拍兄弟俩吃饭视频反差特别大.你能猜到哪个是哥哥 哪个是弟弟吗

  当“留洋大小姐”遇上“世家小姐”,两个小朋友都好可爱啊!“你已春色摇曳我仍一身旧雪”

  妈妈背着孩子偷偷出门,爸爸举着床单打配合奶奶看到笑个不停,爸爸居然还举着床单原路返回