一、服务熔断 介绍
1.1 有什么用
服务熔断是指调用方访问服务时,通过熔断器做代理来进行访问,熔断器会持续观察服务返回的成功、失败的状态,当失败次数超过设置的阙值时,熔断器断开,请求就不能访问到下游服务了。
作用:
当所依赖的服务不稳定时,能够起到快速失败的目的
快速失败后,能够用一定的算法动态探测所依赖对象是否恢复
具体场景
如一个微服务化的产品线上,每一个服务都专注于自己的业务,并对外提供相应的服务接口,或者依赖于外部服务的某个逻辑接口,就像下面这样。
假设我们当前是 服务A
,有部分逻辑依赖于 服务C
,服务C
又依赖于 服务E
, 当前微服务之间进行 rpc 或者 http 通信,假设此时 服务C
调用 服务E
失败,比如由于网络波动导致超时或者服务E
由于过载,服务E
已经 down 掉了。
调用失败,一般会有失败重试等机制。但是再想想,假设服务E
已然不可用的情况下,此时新的调用不断产生,同时伴随着调用等待和失败重试,会导致 服务C
对服务E
的调用而产生大量的积压,慢慢会耗尽服务C
的资源,进而导致服务C
也 down 掉,这样恶性循环下,会影响到整个微服务体系,产生雪崩效应。
虽然导致雪崩的发生不仅仅这一种,但是我们需要采取一定的措施,来保证不让这个噩梦发生。而 hystrix-go 就很好的提供了 熔断和降级的措施。它的主要思想在于,设置一些阀值,比如最大并发数 (当并发数大于设置的并发数,拦截),错误率百分比 (请求数量大于等于设置 的阀值,并且错误率达到设置的百分比时,触发熔断) 以及熔断尝试恢复时间等 。
1.2 三种状态
熔断器实现主要是设置一个阙值 ,这个阙值可以是最大并发数,请求错误率百分比等,超过 这个阙值就进行熔断 。还会设置一个尝试恢复 时间。
熔断器有3种状态:
CLOSED :默认状态。熔断器计算数(最大请求数、请求错误百分比等)没有达到设置的阙值,熔断器就认为被代理服务状态良好。
OPEN :熔断器计算数计算数(最大请求数、请求错误百分比等)已经达到阙值,熔断器就认为被代理的服务已经故障了,打开熔断器开关,请求不再代理服务,而是快速失败。
HALF OPEN :熔断器打开后,为了能自动恢复被代理服务的访问,会切换到半开放状态,去尝试请求被代理服务以查看服务故障是否已经恢复了。如果恢复了,会转为 CLOSED 状态;否则转到 OPEN 状态。
熔断器需要考虑的一些问题:
熔断时长的设置,超过这个时长后切换到 HALF OPEN 进行重试。
重试时,要注意业务是否允许这样做。
不同的异常,需要定义熔断后不同处理逻辑。
记录请求失败日志,供以后人工处理使用。
二、熔断框架 hystrix-go
https://github.com/afex/hystrix-go
1 go get github.com/afex/hystrix-go/hystrix
hystrix-go 是一个延迟和容错的库,作用是隔离系统调用、服务和第三方调用等,阻止级联故障,并在故障不可避免的复杂分布式系统中能够实现恢复的能力。它是基于 Netflix 的同名项目:https://github.com/Netflix/Hystrix 。原理文档
2.1 快速开始
hystrix-go 的使用非常简单,你可以调用它的 Go 或者 Do 方法,只是 Go 方法是异步的方式。而 Do 方法是同步方式。我们从一个简单的例子开启。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package mainimport ( "context" "errors" "time" "github.com/afex/hystrix-go/hystrix" "github.com/gw-gong/gwkit-go/concurrency/hystrix_tools" "github.com/gw-gong/gwkit-go/log" common_utils "github.com/gw-gong/gwkit-go/utils/common" )func main () { syncFn, err := log.InitGlobalLogger(log.NewDefaultLoggerConfig()) common_utils.ExitOnErr(context.Background(), err) defer syncFn() hystrix.SetLogger(&hystrix_tools.SugarLogger{}) hystrix.ConfigureCommand("my_command" , hystrix.CommandConfig{ Timeout: 5000 , MaxConcurrentRequests: 10 , SleepWindow: 5000 , RequestVolumeThreshold: 10 , ErrorPercentThreshold: 30 , }) _ = hystrix.Do("my_command" , func () error { log.Info("talk to other services" ) time.Sleep(1 * time.Second) return errors.New("test error" ) }, func (err error ) error { if err == hystrix.ErrTimeout { log.Error("handle timeout error:%v" , log.Err(err)) return nil } log.Error("handle error:%v" , log.Err(err)) return nil }) }
Do 函数需要三个参数,第一个参数 commmand 名称,用于区分不同的服务调用或操作,第二个参数是处理正常的逻辑,比如 http 调用服务,返回参数是 err。如果处理或调用失败,那么就执行第三个参数逻辑, 我们称为保底操作(fallback)。由于服务错误率过高导致熔断器开启,那么之后的请求也直接回调此函数。
2.2 设置
既然熔断器是按照配置的规则而进行是否开启的操作,那么我们当然可以设置我们想要的值。可以通过 ConfigureCommand
函数为特定的 command 设置参数。
command有以下作用:(同一个command会 共享 熔断机制)
唯一标识 :用于标识和区分不同的熔断器实例
配置关联 :将特定的熔断器配置与对应的 command 关联
指标收集 :用于收集和报告该 command 的执行指标和健康状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "github.com/afex/hystrix-go/hystrix" )func main () { _ = hystrix.Do("my_command" , func () error { fmt.Println("talk to other services" ) return nil }, func (err error ) error { fmt.Printf("handle error:%v\n" , err) return nil }) }
上面配置的值含义:
Timeout : 执行 command 的超时时间(单位:毫秒)。(不会直接导致熔断,但是会增加错误)
MaxConcurrentRequests :command 的最大并发量 。(不会直接导致熔断,但是会增加错误)
SleepWindow :当熔断器被打开后,SleepWindow 的时间就是控制过多久后去尝试服务是否可用了。(和导致熔断的原因那个无关)
RequestVolumeThreshold : 一个统计窗口 10 秒内请求数量 。达到这个请求数量后才去判断是否要开启熔断
ErrorPercentThreshold :错误百分比,请求数量大于等于 RequestVolumeThreshold 并且错误率到达这个百分比后就会启动熔断
未指定的话使用默认配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var ( DefaultTimeout = 1000 DefaultMaxConcurrent = 10 DefaultVolumeThreshold = 20 DefaultSleepWindow = 5000 DefaultErrorPercentThreshold = 50 DefaultLogger = NoopLogger{} )
设置logger要实现接口
1 2 3 type logger interface { Printf(format string , items ...interface {}) }
设置:(全局默认值)
1 hystrix.SetLogger(logger)
2.3 fallback函数
如果fallback函数的err != nil,Do()函数的返回的err != nill
不管是什么错误,只要发生了就会经过fallback函数。
1 2 3 4 5 6 7 8 var ( ErrMaxConcurrency = CircuitError{Message: "max concurrency" } ErrCircuitOpen = CircuitError{Message: "circuit open" } ErrTimeout = CircuitError{Message: "timeout" } )
可以区分错误来源,上面是hystrix的错误定义
2.4 测试熔断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package mainimport ( "context" "errors" "net/http" "time" "github.com/afex/hystrix-go/hystrix" "github.com/gw-gong/gwkit-go/concurrency/hystrix_tools" "github.com/gw-gong/gwkit-go/log" common_utils "github.com/gw-gong/gwkit-go/utils/common" )func accessBaidu () error { resp, err := http.Get("https://baidu.com" ) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { return errors.New("failed to access baidu.com, status: " + resp.Status) } log.Info("access baidu.com success" ) return nil }func hystrixDemo () { _ = hystrix.Do("my_command" , func () error { return accessBaidu() }, func (err error ) error { if err == hystrix.ErrTimeout { log.Error("handle timeout error:%v" , log.Err(err)) return nil } log.Error("handle error:%v" , log.Err(err)) return nil }) }func main () { syncFn, err := log.InitGlobalLogger(log.NewDefaultLoggerConfig()) common_utils.ExitOnErr(context.Background(), err) defer syncFn() hystrix.SetLogger(&hystrix_tools.SugarLogger{}) hystrix.ConfigureCommand("my_command" , hystrix.CommandConfig{ Timeout: 5000 , MaxConcurrentRequests: 10 , SleepWindow: 5000 , RequestVolumeThreshold: 10 , ErrorPercentThreshold: 30 , }) tasks := make (chan struct {}, 10 ) go func () { for { select { case <-tasks: hystrixDemo() } } }() for { tasks <- struct {}{} time.Sleep(200 * time.Millisecond) } }
正常联网 -> 断网 -> 联网
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 2025-08-13T10:35:42.627+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:42.974+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.091+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.196+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.370+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.456+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.538+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.654+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.747+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.839+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:43.922+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:44.005+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:44.094+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:44.171+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:44.373+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:44.561+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:49.673+0800 ERROR demo/main.go:33 handle timeout error:%v {"error": "hystrix: timeout"} 2025-08-13T10:35:49.680+0800 ERROR demo/main.go:36 handle error:%v {"error": "Get \"https://baidu.com\": dial tcp: lookup baidu.com: no such host"} 2025-08-13T10:35:49.682+0800 ERROR demo/main.go:36 handle error:%v {"error": "Get \"https://baidu.com\": dial tcp: lookup baidu.com: no such host"} 2025-08-13T10:35:49.684+0800 ERROR demo/main.go:36 handle error:%v {"error": "Get \"https://baidu.com\": dial tcp: lookup baidu.com: no such host"} 2025-08-13T10:35:49.686+0800 ERROR demo/main.go:36 handle error:%v {"error": "Get \"https://baidu.com\": dial tcp: lookup baidu.com: no such host"} 2025-08-13T10:35:49.687+0800 ERROR demo/main.go:36 handle error:%v {"error": "Get \"https://baidu.com\": dial tcp: lookup baidu.com: no such host"} 2025-08-13T10:35:49.689+0800 ERROR demo/main.go:36 handle error:%v {"error": "Get \"https://baidu.com\": dial tcp: lookup baidu.com: no such host"} 2025-08-13T10:35:49.689+0800 INFO hystrix_tools/logger.go:27 [hystrix] hystrix-go: opening circuit my_command 2025-08-13T10:35:49.689+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:49.689+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:49.689+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:49.689+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:49.689+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:49.877+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:50.078+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:50.280+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:50.482+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:50.687+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:50.890+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:51.093+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:51.294+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:51.495+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:51.697+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:51.898+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:52.098+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:52.299+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:52.501+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:52.702+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:52.902+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:53.103+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:53.305+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:53.509+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:53.710+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:53.912+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:54.113+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:54.316+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:54.516+0800 ERROR demo/main.go:36 handle error:%v {"error": "hystrix: circuit open"} 2025-08-13T10:35:54.716+0800 INFO hystrix_tools/logger.go:27 [hystrix] hystrix-go: allowing single test to possibly close circuit my_command 2025-08-13T10:35:55.004+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:55.005+0800 INFO hystrix_tools/logger.go:27 [hystrix] hystrix-go: closing circuit my_command 2025-08-13T10:35:55.125+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:55.205+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:55.412+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:55.609+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:55.802+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:56.018+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:56.150+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:56.342+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:56.517+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:56.643+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:56.933+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:57.148+0800 INFO demo/main.go:24 access baidu.com success 2025-08-13T10:35:57.239+0800 INFO demo/main.go:24 access baidu.com success
分析:
可以看到第23行后触发熔断,第23行往前的10s事件窗口中,也就是1-23行中,发送的错误有7个(6/22 <30%, 7/23 > 30%),所以这个点就发生了熔断。熔断后,sleep 5s,然后开始尝试,尝试成功后,关闭熔断。
2.5 异步熔断 Go()
特性
Go() 函数
Do() 函数
执行方式
异步,立即返回
同步,阻塞等待
返回值
chan error
error
使用场景
需要并发处理多个请求
简单的同步调用
错误处理
通过 channel 接收
直接返回错误
可以不学,感觉没必要,异步自己处理就好了。
摘抄:
https://www.cnblogs.com/jiujuan/p/17327387.html
https://learnku.com/articles/53019