介紹一下降級和熔斷的概念
什么是降級呢? 降級意味着多種方案,當系統出現問題的時候,你有一個備選方案可以馬上切換,比如有一個接口的功能是實時預測未來一個月某個商品的采購數量,突然間依賴的上游系統出現問題了,那么我們的接口就完全不可用了嗎?顯然這是不應該的,這時我接口就可以降級,返回昨天實時計算出來的結果,雖然准確性可能差一點,但系統能夠正常運轉,降級也分為自動降級和手動降級,前者是系統自動檢測到問題時自動切換,后者是系統檢測到問題報警,人為的切換,降級代表着系統相比降級之前其功能表現不如之前的完美(這個具體體現在功能准確性,可用性上等,如上面接口的例子)
什么是熔斷呢? 通俗來講,熔斷指的是遇到危險了,必須馬上停掉,比如生活中的電流過大,必須馬上切斷,否則就發生了火災了,熔斷之后就會導致斷電,完全不可用,在一個系統中,假設一個接口部署了10台機器(分布式),突然某一台機器的接口調用情況正確率降到90%,那么這台機器肯定出現問題了,這個時候就需要熔斷這台機器,把這台機器從整個集群中摘掉,從而保證用戶的請求100%的正確,再比如,一個系統中有很多功能,這些功能有些是核心功能,有些是非核心功能,那么在一些大促中,我們可能熔斷掉一些非核心功能,從而保證核心功能的流轉(登錄和注冊,登錄屬於核心,注冊是屬於非核心)
為什么需要降級和熔斷
不管是降級還是熔斷,都是為了保證了系統的穩定性,可用性。降級往往代表系統功能部分不可用,熔斷代表的是完全不可用,再舉一個簡單例子,注冊功能,降級可能出現的情況是手機號可以正常,郵箱不能注冊,而熔斷出現的情況是注冊功能完全不可用,所以說有時候熔斷是一種特殊的降級。這在整個系統設計編碼中都需要考慮到。
常用的降級和熔斷策略
在業務系統時,降級在編碼時需要考慮好備選方案,和業務確認方案的合理性,熔斷在編碼時需要分離核心功能和非核心功能,梳理上下游依賴關系,防止強依賴引起的系統的雪崩,這些是業務系統功能設計時需要經常考慮的。
所有業務系統都需要考慮的東西,就意味着可以優化,可以剝離,抽象出來,做成公共組件,中間件,形成通用性。
上面有提到,降級和熔斷的最終目的都是保證系統的穩定性,可靠性,保證核心服務可用,那么在形成中間件時具體措施是什么呢?
降級
- 超時降級(調用服務時超時返回默認值或者其它處理辦法)
- 失敗次數降級(服務可用率下降時降級)
- 限流也是降級的一種辦法
- 故障降級(依賴的外系統發生故障時降級)
- 拒絕服務降級
熔斷
- 系統攻擊熔斷(當某個服務遭遇流量攻擊時,可以熔斷這個服務)
- 涉及核心功能運行時的熔斷(下單和評論功能,關鍵時刻可以熔斷評論功能)
不管降級還是熔斷,在設計時都要考慮:降級熔斷算法,恢復機制,報警。這些是必備的,不能系統降級了或者熔斷了就無法回復之前的情況,也不能不報警,要不然開發人員都不知道,這還得了。
熔斷和降級的異樣性
- 兩者的目的相當
- 兩者的最終的表現的相同
- 粒度一樣,大多數都是服務級別的粒度,也有可能是方法級別的
- 自治性要求比較高(盡可能的智能化)
- 降級一般是客戶端處理,熔斷是在服務端處理的
設計方案
介紹一種的常見的方案,服務碼+配置中心,調用任何服務時都傳入必要參數服務碼和開關,默認關閉,當觸發某種條件時可打開開關,或者通過配置中心手動推送開關新的值,從而保護系統不被單個服務壓垮,別看這個簡單,很多系統都是這么做的。
func DowngradeAndFuse (ctx context.Context){
//業務碼
bizValue := ctx.Value("bizCode")
//熔斷降級標識
flag := ctx.Value("flag")
if bizValue == "指定業務" && flag {
//降級或者熔斷
return
}
}
Hystrix的原理
Hystrix有Java和Go版本的,Java版本的是Netflix公司開發並開源的,Go版本的是由afex(個人)創建的,代碼庫地址如下:
https://github.com/Netflix/Hystrix
https://github.com/afex/hystrix-go
Hystrix引入以下手段來保護系統:
- 資源隔離(線程池和信號量兩種手段的隔離)
- 限流
- 降級
- 熔斷(斷路器)
Hystrix如何設計實現這些手段呢?
- 使用命令模式將所有對外部服務(或依賴關系)的調用包裝在HystrixCommand或HystrixObservableCommand對象中,並將該對象放在單獨的線程中執行
- 每一個依賴都有自己對應的線程池或者信號量,線程池耗盡時,拒絕請求
- 維護請求的各種狀態(成功,失敗,超時的次數)
- 當錯誤率到達一定閾值時,進行熔斷,過一定的時間后又恢復
- 提供降級,失敗,成功,熔斷后的回調邏輯
- 實時的監控指標和配置信息的修改
用代碼實現一個hystrix-go的Demo,第一步寫在init初始化中,配置hystrix的一些參數,如果不配置的話,也會有默認參數。
func init() {
hystrix.ConfigureCommand("my_command", hystrix.CommandConfig{
//多長時間 超時
Timeout: 5000,
//最大並發數
MaxConcurrentRequests: 1,
//錯誤百分比,錯誤率達到這個數值開啟熔斷
ErrorPercentThreshold: 25,
//當熔斷器被打開后,SleepWindow的時間就是控制過多久后去嘗試服務是否可用了(毫秒)
SleepWindow: 10,
//最小請求數,只有到達這個數量后才判斷是否開啟熔斷
RequestVolumeThreshold: 10,
})
}
如何使用hystrix熔斷呢,總的來說分為4個步驟:
第一步:定義你調用的外部系統的服務
第二步:設置回調函數(當超時或者熔斷了會調用回調函數)
第三步:使用hystrix的api調用第一步定義好的服務
第四步:獲取最終結果(結果可能時正確的,也可能是一個err)
//異步調用
func HystrixAsyStudy() {
//第一步:
result := make(chan string, 1)
//定義依賴外部系統的函數
f1 := func() error {
// 處理業務系統(調用外部服務)
fmt.Println("處理業務邏輯")
result <- "處理結果"
return nil
}
//第二步:
//回調函數,只有 err不為空,才會執行回調函數(如果發生了超時,熔斷,
//限流,超時之后也會回調)
fallBack1 := func(err error) error {
fmt.Println("回調函數")
return err
}
//第三步:
errors := hystrix.Go("my_command", f1, fallBack1)
//第四步:
select {
case r := <-result:
fmt.Println(r)
case e := <-errors:
fmt.Println(e)
}
}
Hystrix更加詳細的文檔參考如下地址:
//Java版本的
https://github.com/Netflix/Hystrix/wiki/How-To-Use#Common-Patterns-FailFast
//Go版本的
https://github.com/afex/hystrix-go