浅析hystrix断路器
断路器&&hystrix简介
- 断路器代理了服务调用方对提供方的请求。监控最近请求的失败和超时次数,在下游服务因为过载或者故障无法提供响应时,断路器中请求失败率会大大提升,超过一定阈值后,断路器会打开,切断服务调用方和提供方的联系,此时调用者会执行失败逻辑或者直接返回异常。同时断路器还有检测恢复机制,允许服务调用者尝试调用服务提供者以检测它是否恢复正常,若恢复正常则关闭断路器,恢复正常调用。
- 断路器的三态
- 关闭状态:程序正常运行时,大多数都处于此状态,服务调用者正常访问服务提供者。断路器会统计周期时间内的请求总次数和失败数的比例。
- 打开状态:最近失败频率超过了预设的阈值以后,断路器进入打开状态,服务调用者对服务提供者的调用失效,服务调用进入失败逻辑或者返回异常。
- 半开状态:断路器在进入打开状态时候会启动一个超时定时器,在定时器到达时,它会进入到半开状态,此时执行“恢复检测机制”,即调用者尝试对服务提供者发起少量调用请求。如果这些请求都成功执行,那么断路器就认为服务提供者已恢复正常,断路器则关闭,失败计数器复位。如果这些请求失败,断路器返回到打开状态,并重新启动超时定时器,重新进行检测恢复。
- hystrix
- hystrix是一个延迟和容错的库。作用于隔离三方系统,服务,第三方库之间的调用,防止级联故障。并且在分布式系统中实现故障出现后的复原能力。git地址:https://pkg.go.dev/github.com/afex/hystrix-go/hystrix
hystrix执行流程
hystrix使用实例
- 准备两个http server的 service
- 通过配置hystrix的命令,验证断路器的熔断,检测恢复功能
- 示例代码中demo1,配置service1的请求并发数量大于100时,触发断路器打开,配置service2的并发请求数量大于5时,触发断路器打开。两个服务同时用50个goroutine去并发请求,可以看到,请求service2会打印“”失败回滚日志;
- 示例代码demo2中,service1 先正常执行,然后关闭五秒后,让hystrix因为服务请求失败进入到打开状态;因为配置了5秒的超时窗口时间,继而在五秒窗口时间后进入到半开状态,此时service1保持关闭,hystrix将会重新打开;最后,等再过五秒窗口时间,hystrix进入到半开状态,此时service1打开,使所有请求正常,hystrix检测所有请求正常,断路器进入关闭状态;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // Service1代码 2 package main 3 4 import ( 5 "github.com/gin-gonic/gin" 6 ) 7 8 func main() { 9 r := gin.Default() 10 r.GET("/ping", func(c *gin.Context) { 11 c.JSON(200, gin.H{ 12 "message": "this is service1", 13 }) 14 }) 15 r.Run("127.0.0.1:11012") 16 } 17 // Service2代码 18 package main 19 20 import ( 21 "github.com/gin-gonic/gin" 22 ) 23 24 func main() { 25 r := gin.Default() 26 r.GET("/ping", func(c *gin.Context) { 27 c.JSON(200, gin.H{ 28 "message": "this is service2", 29 }) 30 }) 31 r.Run("127.0.0.1:11013") 32 } 33 34 // hystrix使用示例代码 35 package main 36 37 import ( 38 "fmt" 39 "io/ioutil" 40 "net/http" 41 "sync" 42 "time" 43 44 "github.com/afex/hystrix-go/hystrix" 45 ) 46 47 const ( 48 service1Command = "service1_command" 49 service2Command = "service2_command" 50 service1Url = "http://127.0.0.1:11012/ping" 51 service2Url = "http://127.0.0.1:11013/ping" 52 ) 53 54 func main() { 55 //demo1() 56 demo2() 57 } 58 59 // 并发数超过配置最大请求数,hystrix直接打开进入失败逻辑 60 func demo1() { 61 62 //hystrix 基础配置 63 hystrix.ConfigureCommand(service1Command, hystrix.CommandConfig{ 64 Timeout: 1000, //命令执行超时时间 65 MaxConcurrentRequests: 100, //最大并发请求数量,命令最大执行的并发goroutine,同种hystrix命令超出该值后,请求直接进入失败回滚逻辑 66 RequestVolumeThreshold: 1, //最小请求阈值,只有窗口时间内请求数量超过该值,断路器才执行对应的判断逻辑 67 SleepWindow: 5000, //超时窗口时间,断路器打开多久时长后进入半开状态,重新允许远程调用发生,试探下游服务是否正常。如果接下来请求都成功,断路器将关闭 68 ErrorPercentThreshold: 25, //错误比例阈值,当滑动窗口时间内的错误请求频率超过该值时,断路器将打开 69 }) 70 71 hystrix.ConfigureCommand(service2Command, hystrix.CommandConfig{ 72 Timeout: 1000, 73 MaxConcurrentRequests: 5, 74 RequestVolumeThreshold: 1, 75 SleepWindow: 5000, 76 ErrorPercentThreshold: 25, 77 }) 78 wg1 := sync.WaitGroup{} 79 wg1.Add(50) 80 wg2 := sync.WaitGroup{} 81 wg2.Add(50) 82 //开50个goroutine去跑 83 for i := 0; i < 50; i++ { 84 go RequestService1(&wg1) 85 go RequestService2(&wg2) 86 } 87 wg1.Wait() 88 wg2.Wait() 89 fmt.Println("main process done") 90 } 91 92 // service1 正常执行五秒后关闭,让hystrix进入到打开状态 93 //继而在五秒窗口时间进入到半开状态,此时service1保持关闭,hystrix重新打开。 94 //再过五秒窗口时间,hystrix进入到半开状态,此时service1打开,使所有请求正常,hystrix检测所有请求正常,进入关闭状态 95 func demo2() { 96 //hystrix 基础配置 97 hystrix.ConfigureCommand(service1Command, hystrix.CommandConfig{ 98 Timeout: 1000, //命令执行超时时间 99 MaxConcurrentRequests: 100, //最大并发请求数量,命令最大执行的并发goroutine,同种hystrix命令超出该值后,请求直接进入失败回滚逻辑 100 RequestVolumeThreshold: 1, //最小请求阈值,只有窗口时间内请求数量超过该值,断路器才执行对应的判断逻辑 101 SleepWindow: 5000, //超时窗口时间,断路器打开多久时长后进入半开状态,重新允许远程调用发生,试探下游服务是否正常。如果接下来请求都成功,断路器将关闭 102 ErrorPercentThreshold: 10, //错误比例阈值,当滑动窗口时间内的错误请求频率超过该值时,断路器将打开 103 }) 104 wg1 := sync.WaitGroup{} 105 wg1.Add(5000) 106 start := time.Now() // 获取当前时间 107 for i := 0; i < 5000; i++ { 108 time.Sleep(time.Second) 109 RequestService1(&wg1) 110 } 111 wg1.Wait() 112 elapsed := time.Since(start) 113 fmt.Println("该函数执行完成耗时:", elapsed) 114 fmt.Println("main process done") 115 } 116 117 func RequestService1(wg *sync.WaitGroup) { 118 // 请求service1 119 defer wg.Done() 120 err := hystrix.Do(service1Command, func() error { 121 client := http.Client{} 122 resp, err := client.Get(service1Url) 123 if err != nil { 124 return err 125 } 126 defer resp.Body.Close() 127 if resp.StatusCode != 200 { 128 return fmt.Errorf("request err status code is %v \n", resp.StatusCode) 129 } 130 connect, err := ioutil.ReadAll(resp.Body) 131 if err != nil { 132 return fmt.Errorf("connect err %v \n", err) 133 } 134 fmt.Printf("service1 resp : %s \n", string(connect)) 135 return nil 136 }, func(err error) error { 137 // 失败回滚方法 138 fmt.Printf("service1_command exec err : %v \n", err) 139 return nil 140 }) 141 if err != nil { 142 fmt.Printf("hystrix do service1_command fail : %v \n", err) 143 } 144 } 145 146 func RequestService2(wg *sync.WaitGroup) { 147 // 请求service2 148 defer wg.Done() 149 err := hystrix.Do(service2Command, func() error { 150 client := http.Client{} 151 resp, err := client.Get(service2Url) 152 if err != nil { 153 return err 154 } 155 defer resp.Body.Close() 156 if resp.StatusCode != 200 { 157 return fmt.Errorf("request err status code is %v \n", resp.StatusCode) 158 } 159 connect, err := ioutil.ReadAll(resp.Body) 160 if err != nil { 161 return fmt.Errorf("connect err %v \n", err) 162 } 163 fmt.Printf("service2 resp : %s \n", string(connect)) 164 return nil 165 }, func(err error) error { 166 // 失败回滚方法 167 fmt.Printf("service2_command exec err : %v \n", err) 168 return nil 169 }) 170 if err != nil { 171 fmt.Printf("hystrix do service2_command fail : %v \n", err) 172 } 173 }
demo1执行响应图,请求sevice2因为并发量超过hystrix配置断路器打开,请求service1正常
demo2执行响应图
hystrix集成到项目(网关)
断路器一般会作用在BFF层或者一些网关服务,在针对某些下游服务的请求并发量过大时做熔断处理,下面代码,示例如何将hystrix集成到具体的项目。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "net/http" 8 "net/http/httputil" 9 "strings" 10 "sync" 11 12 "github.com/afex/hystrix-go/hystrix" 13 ) 14 15 // HystrixHandler Hystrix的实际应用 16 type HystrixHandler struct { 17 18 // 记录hystrix是否已配置 19 hystrixs map[string]bool 20 hystrixsMutex *sync.Mutex 21 // 服务的名称或者域名 22 serviceName string 23 // 服务的端口 24 serviceHost int 25 logger *log.Logger 26 } 27 28 func NewHystrixHandler(serviceName string, serviceHost int, logger *log.Logger) *HystrixHandler { 29 30 return &HystrixHandler{ 31 logger: logger, 32 hystrixs: make(map[string]bool), 33 hystrixsMutex: &sync.Mutex{}, 34 serviceName: serviceName, 35 serviceHost: serviceHost, 36 } 37 38 } 39 40 func (hystrixHandler *HystrixHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 41 42 // 假设服务请求路径约定为 http://localhost:10098/oauth/token?grant_type=password 43 reqPath := req.URL.Path 44 if reqPath == "" { 45 return 46 } 47 //按照分隔符'/'对路径进行分解,获取服务名称serviceName 48 pathArray := strings.Split(reqPath, "/") 49 serviceName := pathArray[1] 50 51 if serviceName == "" { 52 // 路径不存在 53 rw.WriteHeader(404) 54 return 55 } 56 57 if _, ok := hystrixHandler.hystrixs[serviceName]; !ok { 58 hystrixHandler.hystrixsMutex.Lock() 59 if _, ok := hystrixHandler.hystrixs[serviceName]; !ok { 60 //把serviceName作为 hystrix 命令命名 61 hystrix.ConfigureCommand(serviceName, hystrix.CommandConfig{ 62 // 进行 hystrix 命令自定义 63 }) 64 hystrixHandler.hystrixs[serviceName] = true 65 } 66 hystrixHandler.hystrixsMutex.Unlock() 67 } 68 69 err := hystrix.Do(serviceName, func() error { 70 //创建Director 71 director := func(req *http.Request) { 72 73 //重新组织请求路径,去掉服务名称部分 74 destPath := strings.Join(pathArray[2:], "/") 75 76 hystrixHandler.logger.Println("service name ", hystrixHandler.serviceName) 77 78 //设置代理服务地址信息 79 req.URL.Scheme = "http" 80 req.URL.Host = fmt.Sprintf("%s:%d", hystrixHandler.serviceName, hystrixHandler.serviceHost) 81 req.URL.Path = "/" + destPath 82 } 83 var proxyError error 84 // 返回代理异常,用于记录 hystrix.Do 执行失败 85 errorHandler := func(ew http.ResponseWriter, er *http.Request, err error) { 86 proxyError = err 87 } 88 proxy := &httputil.ReverseProxy{ 89 Director: director, 90 ErrorHandler: errorHandler, 91 } 92 93 proxy.ServeHTTP(rw, req) 94 // 将执行异常反馈 hystrix 95 return proxyError 96 97 }, func(e error) error { 98 hystrixHandler.logger.Println("proxy error ", e) 99 return errors.New("fallback excute") 100 }) 101 102 // hystrix.Do 返回执行异常 103 if err != nil { 104 rw.WriteHeader(500) 105 rw.Write([]byte(err.Error())) 106 } 107 108 }
启用 Metrics dashboard
- hystrix可以添加Metrics控制器,上报hystrix命令的状态信息。步骤如下:
- 下载docker镜像hystrix-dashboard:docker pull mlabouardy/hystrix-dashboard
- 启动hystrix-dashboard:docker run -d -p 8080:9002 --name hystrix-dashboard mlabouardy/hystrix-dashboard:latest
- 填写断路器数据上报指标的地址(局域网ip和端口以及路由)
func main() { //... //添加Metrics控制器 上报hystrix命令的状态信息 hystrixStreamHandler := hystrix.NewStreamHandler() hystrixStreamHandler.Start() go http.ListenAndServe(net.JoinHostPort("", "81"), hystrixStreamHandler) demo2() //.... }
- 命令执行情况截图