跳至主要內容

服务熔断和服务降级分别指什么?

小白debug大约 8 分钟

服务熔断和服务降级分别指什么?

微服务领域里有个词叫服务熔断,你知道这是啥不?
故事要从我读大学那会说起。
因为功率问题,很多寝室都是不让用吹风筒和热水壶的。
但我那时候头铁,不仅用,而且还同时开了两个热水壶和一个吹风筒。直接给寝室电路来了个压测。
不出意外的出了意外,寝室直接停电。
一时间,隔壁寝室灯火通明,我们寝室一片漆黑。
作为本科专业电气工程的靓仔,我们意识到,这妥妥是电路过载导致断路器跳闸了。
于是我们趁社管阿姨不注意,偷偷摸进配电房,手动将断路器开关复位,寝室就来电了。

是真的有惊无险。
如果没有这个断路器,寝室总电路怕是得因为过载全部烧掉,我们几个妥妥会提前进入社会大学。
我能毕业,全靠这个断路器!
看到这里,我们知道了断路器的作用,就是在电路出问题的时候及时断开电路,避免过载,从而保护电路
微服务领域,我们也可以借鉴断路器的思路,引入了服务熔断的概念。

服务熔断是什么

服务熔断,也就是 Circuit Breaker,本质上是一种软件设计模式,用于在分布式系统中处理服务调用失败的情况。

假设有个 A 服务调用 B 服务的场景,如果 B 服务已经出现频繁失败的情况,A 继续调用只会加剧 B 服务的负担,严重的时候,有可能导致 B 服务崩溃,甚至出现 B 服务重启后立马被打崩的情况。因此,最好的做法是,在一段时间内先不要再频繁调用 B 服务

为了实现这个保护效果,我们可以在 A 和 B 之间加一个熔断器。当 B 服务频繁失败时,熔断器可以防止 A 继续频繁调用 B 服务,相当于阻断服务间的请求,并且还能在 B 服务恢复正常之后,恢复 AB 的调用。

熔断器的作用
熔断器的作用

工作原理也和上文提到的宿舍电路里的断路器类似。当服务调用失败的次数超过某个阈值时,熔断器会自动“打开”(Open),阻止进一步的服务调用,防止不断报错重试导致压垮被调用服务。

然后在在一段时间之后,熔断器开始尝试允许少量的请求通过,以检查服务是否已经恢复,也就是所谓的“半打开”(HalfOpen)。

如果这些请求成功,熔断器会“关闭”(Close),系统恢复正常的服务调用;但如果调用还是失败,那熔断器会继续再次回到“打开”(Open)状态。

上面提到的三个状态OpenHalfOpenClose是服务熔断中非常重要的三个状态。

  • Closed(关闭):这是熔断器的初始状态。在这种状态下,可以进行服务间调用,熔断器会跟踪服务调用的成功和失败情况。如果失败调用次数,到了某个配置的阈值,熔断器就会切换到 Open(打开)状态。
    熔断器关闭
  • HalfOpen(半开):保持 Open 状态一段时间后,熔断器会尝试进入 HalfOpen 状态。这个状态下,熔断器会尝试放几个请求通过,看下被调用服务是否已经恢复。如果这些请求成功,熔断器就会回到 Closed 状态;如果失败,那它会退回到 Open 状态。
    熔断器半打开
  • Open(打开):当熔断器检测到服务调用连续失败时,它会切换到 Open 状态。在这种状态下,熔断器会阻止所有对服务的调用,直到超时时间过后,或者在 HalfOpen 状态下的探测请求成功。
    熔断器打开

它们的状态流转关系就像下图这样。

熔断状态机
熔断状态机

服务降级是什么

看到这里问题就来了,如果服务熔断打开了,同时 A服务 api 依赖 B服务的响应,那 A服务 API 岂不是会一直处于报错的状态?
听起来确实是的。那有没有办法让 A服务的 API 不报错?
有。举个例子。
假设 B服务是获取推荐商品列表的 api 接口,A服务逻辑依赖 B服务的结果,目的是展示商品给买家。
如果 B服务报错了,A服务按理说只是拿不到 B服务的商品,但 A服务可以在服务缓存里预先放好一些固定的商品,作为 B服务报错时的备用,这样买家一样能看到商品列表,只不过是从千人千面的推荐商品,变成一批固定商品而已。

服务降级
但服务依然可用,像这种,在服务熔断或报错时,为系统提供兜底方案的能力,其实就是所谓的服务降级
这其实是个很常用的操作,可以大大提升接口可用性,我知道你不是很关心接口可用性,但你的老板真的很关心!具体还有哪些使用场景,大家可以结合自己的代码发散下思路。

怎么使用熔断器?

可以看出,熔断器的逻辑其实很简单,而且这么通用的功能,必然有现成的库可以直接拿来用。
比如阿里开源的sentinel-golang
使用也比较简单。只需要三步。

1.引入 circuitbreaker 库

"github.com/alibaba/sentinel-golang/core/circuitbreaker"

2.声明熔断规则

通过circuitbreaker.LoadRules加载对应的熔断规则。

	_, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{
		// Statistic time span=5s, recoveryTimeout=3s, maxErrorRatio=40%
		{
			Resource:                     "api_url",
			Strategy:                     circuitbreaker.ErrorRatio,
			RetryTimeoutMs:               3000,
			MinRequestAmount:             10,
			StatIntervalMs:               5000,
			StatSlidingWindowBucketCount: 10,
			Threshold:                    0.4,
		},
	})

这里面有几个需要注意的地方:

  • Resource 是想要保护的资源名称,也就是上面提到的 B 服务,这里可以直接使用被调用方的 url。
  • Strategy 是指熔断策略,示例代码里展示的是错误率,也就是说服务达到 xx 比例的错误率时就会触发熔断。同时这里还支持填其他策略,比如从错误率换成错误次数或者是慢调用的比例个数。
  • RetryTimeoutMs 是指熔断器打开后经过多长时间后进行重试。在熔断器 Open 期间,请求会被直接拒绝,不会发送到后端 Resource(B 服务)。在指定的超时时间之后,熔断器将尝试发送一个请求以检查后端资源的可用性。
  • MinRequestAmount 表示在进行熔断之前必须满足的最小请求数量。只有当请求的数量达到或超过这个阈值时,熔断器才会生效。这个参数可以用来避免在系统启动时就触发熔断。
  • StatIntervalMs 表示统计信息的时间间隔,以毫秒为单位。在这个时间间隔内,熔断器将收集请求的统计信息,用于计算错误率。
  • Threshold:表示错误率的阈值。当错误率超过这个阈值时,熔断器将触发熔断,停止发送请求到后端资源。

3.加入熔断保护

在需要进行熔断保护的地方,加入下面的代码:

	e, b := sentinel.Entry("api_url")
	if b == nil {
		// 通过检测,不需要熔断,直接执行api调用
		err := api_call()
		if err != nil {
			sentinel.TraceError(e, err)
			// 这里也可以加入降级逻辑,比如当api_call出现错误时
			// fallback() // 服务降级的备用逻辑
		}
		// 保证执行完之后退出资源
		e.Exit()
	} else {
		// 检测到需要熔断,执行服务降级逻辑
		fallback() // 服务降级的备用逻辑
	}

上面的 sentinel.Entry()方法内部会自动检测"api_url"这个资源是否需要打开熔断器,如果 api 调用报错了,可以通过 sentinel.TraceError 记录下来,sentinel 内部会根据报错去计算报错率,自动判断要不要熔断。如果触发熔断,则走 fallback() 逻辑,进行服务降级

到这里,就算使用上熔断器的能力啦。

总结

  • 服务熔断是一种软件设计模式,用于分布式系统中处理服务调用失败的情况,可以防止被调用服务因为频繁失败被压垮。它借鉴了电路中的断路器原理,通过监控服务调用的失败率等条件来决定是否阻止进一步的调用,以保护系统免受过载。
  • 服务熔断器有三个主要状态:关闭(Closed)半开(HalfOpen)和打开(Open),分别对应不同的保护策略。当服务调用失败次数超过阈值时,熔断器打开,阻止服务调用。在一定时间后,熔断器尝试半开状态,允许少量请求通过以测试服务恢复情况。如果服务恢复,熔断器关闭;如果失败,熔断器保持打开状态。
  • 在服务熔断或报错时,为系统提供兜底方案的能力,其实就是所谓的服务降级
  • 在 go 语言里可以使用 sentinel-golang 库实现熔断功能。

参考文章: