斷路器&&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檢測所有請求正常,斷路器進入關閉狀態;
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集成到具體的項目。
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() //.... }
- 命令執行情況截圖