设为首页 加入收藏

TOP

微服务架构|go-zero 的自适应熔断器(一)
2023-09-09 10:25:25 】 浏览:309
Tags:go-zero 应熔断

原文链接: go-zero 的自适应熔断器

上篇文章我们介绍了微服务的限流,详细分析了计数器限流和令牌桶限流算法,这篇文章来说说熔断。

熔断和限流还不太一样,限流是控制请求速率,只要还能承受,那么都会处理,但熔断不是。

在一条调用链上,如果发现某个服务异常,比如响应超时。那么调用者为了避免过多请求导致资源消耗过大,最终引发系统雪崩,会直接返回错误,而不是疯狂调用这个服务。

本篇文章会介绍主流熔断器的工作原理,并且会借助 go-zero 源码,分析 googleBreaker 是如何通过滑动窗口来统计流量,并且最终执行熔断的。

工作原理

这部分主要介绍两种熔断器的工作原理,分别是 Netflix 开源的 Hystrix,其也是 Spring Cloud 默认的熔断组件,和 Google 的自适应的熔断器。

Hystrix is no longer in active development, and is currently in maintenance mode.

注意,Hystrix 官方已经宣布不再积极开发了,目前处在维护模式。

Hystrix 官方推荐替代的开源组件:Resilience4j,还有阿里开源的 Sentinel 也是不错的替代品。

hystrixBreaker

Hystrix 采用了熔断器模式,相当于电路中的保险丝,系统出现紧急问题,立刻禁止所有请求,已达到保护系统的作用。

系统需要维护三种状态,分别是:

  • 关闭: 默认状态,所有请求全部能够通过。当请求失败数量增加,失败率超过阈值时,会进入到断开状态。
  • 断开: 此状态下,所有请求都会被拦截。当经过一段超时时间后,会进入到半断开状态。
  • 半断开: 此状态下会允许一部分请求通过,并统计成功数量,当请求成功时,恢复到关闭状态,否则继续断开。

通过状态的变更,可以有效防止系统雪崩的问题。同时,在半断开状态下,又可以让系统进行自我修复。

googleBreaker

googleBreaker 实现了一种自适应的熔断模式,来看一下算法的计算公式,客户端请求被拒绝的概率

参数很少,也比较好理解:

  1. requests:请求数量
  2. accepts:后端接收的请求数量
  3. K:敏感度,一般推荐 1.5-2 之间

通过分析公式,我们可以得到下面几个结论,也就是产生熔断的实际原理:

  1. 正常情况下,requests 和 accepts 是相等的,拒绝的概率就是 0,没有产生熔断
  2. 当正常请求量,也就是 accepts 减少时,概率会逐渐增加,当概率大于 0 时,就会产生熔断。如果 accepts 等于 0 了,则完全熔断。
  3. 当服务恢复后,requests 和 accepts 的数量会同时增加,但由于 K * accepts 增长的更快,所以概率又会很快变回到 0,相当于关闭了熔断。

总的来说,googleBreaker 的实现方案更加优雅,而且参数也少,不用维护那么多的状态。

go-zero 就是采用了 googleBreaker 的方案,下面就来分析代码,看看到底是怎么实现的。

接口设计

接口定义这部分我个人感觉还是挺不好理解的,看了好多遍才理清了它们之间的关系。

其实看代码和看书是一样的,书越看越薄,代码会越看越短。刚开始看感觉代码很长,随着看懂的地方越来越多,明显感觉代码变短了。所以遇到不懂的代码不要怕,反复看,总会看懂的。

首先来看一下 breaker 部分的 UML 图,有了这张图,很多地方看起来还是相对清晰的,下面来详细分析。

这里用到了静态代理模式,也可以说是接口装饰器,接下来就看看到底是怎么定义的:

// core/breaker/breaker.go
internalThrottle interface {
    allow() (internalPromise, error)
    doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error
}

// core/breaker/googlebreaker.go
type googleBreaker struct {
    k     float64
    stat  *collection.RollingWindow
    proba *mathx.Proba
}

这个接口是最终实现熔断方法的接口,由 googleBreaker 结构体实现。

// core/breaker/breaker.go
throttle interface {
    allow() (Promise, error)
    doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error
}

type loggedThrottle struct {
    name string
    internalThrottle
    errWin *errorWindow
}

func newLoggedThrottle(name string, t internalThrottle) loggedThrottle {
    return loggedThrottle{
        name:             name,
        internalThrottle: t,
        errWin:           new(errorWindow),
    }
}

这个是实现了日志收集的结构体,首先它实现了 throttle 接口,然后它包含了一个字段 internalThrottle,相当于具体的熔断方法是代理给 internalThrottle 来做的。

// core/breaker/breaker.go
func (lt loggedThrottle) allow() (Promise, error) {
    promise, err := lt.internalThrottle.allow()
    return promiseWithReason{
        promise: promise,
        errWin:  lt.errWin,
    }, lt.logError(err)
}

func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
    return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
        accept := acceptable(err)
        if !accept && err != nil {
            lt.errWin.add(err.Error())
        }
        return accept
    }))
}

所以当它执行相应方法时,都是直接调用 internalThrottle 接口的方法,然后再加上自己的逻辑。

这也就是代理所起到的作用,在不改变原方法的基础上,扩展原方法的功能。

// core/breaker/breaker.go
circuitBreaker struct {
    name string
    throttle
}

// NewBreaker returns a Breaker object.
// opts can be used to customize the Breaker.
func NewBreaker(opts ...Option) Breaker {
    var b circuitBreaker
    for _, o
首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇完全可复制、经过验证的 Go 工具链 下一篇一文了解Validator库

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目