一、什麼是熔斷器
要理解熔斷器,可以先看看電路中使用的保險絲。
保險絲(fuse)也被稱爲電流保險絲,IEC127 標準將它定義爲“熔斷體(fuse-link)”。保險絲是一種保證電路安全運行的電子元器件,作用就是在電流異常升高到一定的高度和熱度的時候,自身熔斷切斷電流,這樣可以保護電路安全運行。
保險絲是一種自我保護裝置,保護整個電路線路安全。保護整個電路線路安全是說一戶家庭用戶用電太高或異常保險絲燒斷,隻影響這家用戶用電情況,不會影響線路上的其它家庭用電情況。
- 那微服務治理中的熔斷器呢?
它也是一種保護裝置,用來保護服務,在流量過高時,保護其它服務能安全運行。
在微服務架構中,系統是由很多服務組成的,一個服務功能可能依賴多個服務,如下簡圖,服務 C 依賴服務 E 和 服務 F,服務 B 也依賴服務 E。這還是一個小例子圖。看看 Uber 的微服務依賴全圖,依賴關係更加複雜,相互依賴關係密密麻麻。
當一個服務遇到訪問異常流量時,影響的不僅是自身服務,還會影響到與之依賴的服務,依賴的服務又影響它依賴的服務,子子孫孫無窮匱,這樣有可能引起系統崩潰,這就是雪崩效應。
要怎麼辦?
可以使用熔斷器爲微服務調用提供保護機制。熔斷器像“保險絲”一樣,它作爲一個開關,遇到異常流量,可以打開熔斷器斷開服務;服務恢復後,又可以關閉熔斷器,重新調用服務。
- 服務治理中熔斷器:
服務熔斷是指調用方訪問服務時,通過熔斷器做代理來進行訪問,熔斷器會持續觀察服務返回的成功、失敗的狀態,當失敗次數超過設置的闕值時,熔斷器斷開,請求就不能訪問到下游服務了。
它的作用:1. 當所依賴的服務不穩定時,能夠起到快速失敗的目的
2. 快速失敗後,能夠用一定的算法動態探測所依賴對象是否恢復
二、熔斷器的3種狀態
熔斷器實現主要是設置一個闕值,這個闕值可以是最大併發數,請求錯誤率百分比等,超過這個闕值就進行熔斷。還會設置一個嘗試恢復時間。
熔斷器有3種狀態:
- CLOSED:默認狀態。熔斷器計算數(最大請求數、請求錯誤百分比等)沒有達到設置的闕值,熔斷器就認爲被代理服務狀態良好。
- OPEN:熔斷器計算數計算數(最大請求數、請求錯誤百分比等)已經達到闕值,熔斷器就認爲被代理的服務已經故障了,打開熔斷器開關,請求不再代理服務,而是快速失敗。
- HALF OPEN:熔斷器打開後,爲了能自動恢復被代理服務的訪問,會切換到半開放狀態,去嘗試請求被代理服務以查看服務故障是否已經恢復了。如果恢復了,會轉爲 CLOSED 狀態;否則轉到 OPEN 狀態。
熔斷器需要考慮的一些問題:
- 熔斷時長的設置,超過這個時長後切換到 HALF OPEN 進行重試。
- 重試時,要注意業務是否允許這樣做。
- 不同的異常,需要定義熔斷後不同處理邏輯。
- 記錄請求失敗日誌,供以後人工處理使用。
三、熔斷框架 hystrix-go
hystrix-go 介紹
hystrix-go 是一個用 Go 語言開發的熔斷框架。
hystrix-go 是一個延遲和容錯的庫,作用是隔離系統調用、服務和第三方調用等,阻止級聯故障,並在故障不可避免的複雜分佈式系統中能夠實現恢復的能力。它是基於 Netflix 的同名項目:https://github.com/Netflix/Hystrix 。
可以看看 Netflix 的 Hystrix 是如何工作,對了解 hystrix-go 有很大幫助,https://github.com/Netflix/Hystrix/wiki/How-it-Works :
- Construct a
HystrixCommand
orHystrixObservableCommand
Object- Execute the Command
- Is the Response Cached?
- Is the Circuit Open?
- Is the Thread Pool/Queue/Semaphore Full?
HystrixObservableCommand.construct()
orHystrixCommand.run()
- Calculate Circuit Health
- Get the Fallback
- Return the Successful Response
使用方法介紹
hystrix-go 的調用方法有 2 個:
- Do:同步調用
func Do(name string, run runFunc, fallback fallbackFunc)
- Go:異步調用
func Go(name string, run runFunc, fallback fallbackFunc)
- 將代碼作爲 hystrix-go 的命令執行
hystrix.Go("my_command", func() error {
// talk to other services
return nil
}, nil)
定義一個依賴外部系統的應用程序邏輯,然後將這個函數傳遞給 Go。當系統沒有問題時,將會執行這個程序邏輯。
- 定義失敗後執行的業務邏輯
hystrix.Go("my_command", func() error {
// talk to other services
return nil
}, func(err error) error {
// do this when services are down,調用服務失敗後,可以在這裏執行一些邏輯
return nil
})
如果想在服務中斷期間執行一些業務邏輯,可以傳入第三個參數,這個參數也是一個匿名函數。這第三個參數作用就是在第二個參數(匿名函數)的代碼調用服務返回錯誤時,執行的一些業務邏輯。
hystrix.Go(
"my_command",
func() error {
// talk to other services
_, err := http.Get("https://google.com")
if err != nil {
fmt.Println("Get baidu err: %v", err)
return err
}
return nil
},
func(err error) error {
// do this when services are down
fmt.Println("上面匿名函數中代碼調用服務錯誤時,運行這裏的代碼")
return nil
},
)
- 等待輸出
可以選擇監控的輸出,如下:
output := make(chan bool, 1)
errors := hystrix.Go("my_command", func() error {
// talk to other services
output <- true
return nil
}, nil)
select {
case out := <-output:
// success
case err := <-errors:
// failure
}
- 配置設置
func Configure(cmds map[string]CommandConfig)
func ConfigureCommand(name string, config CommandConfig)
Configure
方法內部也是調用的 ConfigureCommand
方法。
hystrix-go 有一個默認配置。
也可以設置配置,如下:
hystrix.ConfigureCommand(
"my_command", // 熔斷器的名字,一個名字對應一個熔斷器
hystrix.CommandConfig{
Timeout: 1000, //超時時間,單位毫秒 ms。默認 1000ms
MaxConcurrentRequests: 100, // 最大併發數,超過這個設置就返回錯誤。默認 10
ErrorPercentThreshold: 25, // 設置錯誤數量統計百分比闕值,超過這個闕值,就開啓熔斷。默認 50
RequestVolumeThreshold: 4, // 一個窗口10秒內請求的數量闕值,達到這個闕值就開啓熔斷
SleepWindow: 1000, // 熔斷器被激活後,多久重試服務是否可用,單位毫秒。默認 5000ms
})
- RequestVolumeThreshold:一個窗口10秒內請求的數量闕值,判斷熔斷開關的條件之一。請求數量大於等於這個設置的數量闕值後,且錯誤百分比也達到設置的闕值,就開啓熔斷
- ErrorPercentThreshold:錯誤數量統計百分比闕值,判斷熔斷開關的條件之一。請求數量大於等於 RequestVolumeThreshold 並且錯誤百分比達到這個百分比後就會開啓熔斷
- 在dashboard中顯示hystrix上報信息
main.go 程序中,在 http 的一個端口上註冊一個事件流並且在 goroutine 中啓動它,就可以在這個http端口上查看這個上報流。如果你在 Hystrix Dashboard 配置了這個事件流,那麼事件信息就可以自動顯示在 dashboard 上。
hystrixStreamHandler := hystrix.NewStreamHandler()
hystrixStreamHandler.Start()
go http.ListenAndServe(net.JoinHostPort("", "81"), hystrixStreamHandler)
上面的代碼開啓了 dashboard,在 http 端口 81 上可以查看 hystrix 上報的信息,http://localhost:81。
- 可以向 Statsd 發送 circuit metric
c, err := plugins.InitializeStatsdCollector(&plugins.StatsdCollectorConfig{
StatsdAddr: "localhost:8125",
Prefix: "myapp.hystrix",
})
if err != nil {
log.Fatalf("could not initialize statsd client: %v", err)
}
metricCollector.Registry.Register(c.NewStatsdCollector)
代碼示例
一個簡單使用 hystrix-go 示例代碼:
package main
import (
"fmt"
"net/http"
"github.com/afex/hystrix-go/hystrix"
)
func main() {
hystrix.ConfigureCommand("my_command_name", hystrix.CommandConfig{
Timeout: 1000, // 超時時間1000ms
MaxConcurrentRequests: 40, // 最大併發數40
RequestVolumeThreshold: 20, // 請求數量闕值20,達到這個闕值纔可能觸發熔斷
ErrorPercentThreshold: 20, // 錯誤百分比例闕值 20%
})
client := http.Client{}
doRequest := func() error {
req, err := http.NewRequest("GET", "https://www.bin.com", nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println("doRequest: ", resp.Status)
return nil
}
err := hystrix.Do("my_command_name", func() error {
return doRequest()
}, nil)
if err != nil {
fmt.Println("end: ", err)
}
}
創建一個 http 客戶端,封裝一個 doRequest 請求函數。
然後調用 hystrix.Do 函數,把封裝好的 doRequest 函數放在 Do 函數的第二個參數中執行,Do 函數會監視請求的執行情況,會根據超時時間、併發數、錯誤百分比闕值等來判斷是否允許請求執行,當達到某個闕值時,將會觸發熔斷器,並執行熔斷器的錯誤執行邏輯,也就是 Do 函數第三個參數,在這段代碼中直接設置爲 nil ,沒有編寫錯誤邏輯代碼,也就不執行。