微服務架構(gòu)下請求調(diào)用失敗了怎么辦?
微服務帶來的不安因素
相比單體架構(gòu),微服務架構(gòu)下的服務調(diào)用從同一機器內(nèi)的本地調(diào)用變成不同機器間遠程調(diào)用,由此也帶來如下不確定因素:
- 調(diào)用的執(zhí)行是服務提供者,即使服務消費者本身正常,服務提供者也可能因CPU、網(wǎng)絡I/O、磁盤、內(nèi)存、網(wǎng)卡等各種原因調(diào)用失敗,還可能因本身程序執(zhí)行問題比如GC暫停導致失敗
- 調(diào)用發(fā)生在兩臺機器間,所以要經(jīng)過網(wǎng)絡傳輸,而網(wǎng)絡不可控,丟包、延遲或抖動都可能導致調(diào)用失敗。
所以針對服務調(diào)用失敗需特殊處理。
超時
微服務下的一次用戶調(diào)用可能會被拆成多系統(tǒng)間服務調(diào)用,任一次服務調(diào)用若發(fā)生問題都可能導致用戶調(diào)用最終失敗。
一個系統(tǒng)的問題會影響所有調(diào)用這個系統(tǒng)所提供服務的服務消費者,導致服務雪崩。
所以針對服務調(diào)用都要設置超時時間,避免所依賴服務一直未返回結(jié)果,將服務消費者阻死。
超時時間的設定
- 太短,可能有些服務調(diào)用還未及時執(zhí)行完成就被丟棄
- 太長,可能導致服務消費者被拖死
需按服務提供者線上真實的服務水平,取99.9%或99.99%的調(diào)用都在多少ms內(nèi)返回為準。
重試
雖設超時時間可及時止損,但服務調(diào)用結(jié)果畢竟失敗。大部分情況調(diào)用失敗都因網(wǎng)絡問題或個別服務提供者節(jié)點有問題,若能換個節(jié)點再次訪問說不定就成功。
假如一次服務調(diào)用失敗概率1%,那連續(xù)兩次服務調(diào)用失敗概率0.01%,失敗率降低到原來1%。所以經(jīng)常還要設置一個服務調(diào)用超時后的重試次數(shù)。
假如某服務調(diào)用超時時間設為100ms,重試次數(shù)設為1,當服務調(diào)用超過100ms后,服務消費者就會立即發(fā)起第二次服務調(diào)用,而不會再等待第一次調(diào)用返回結(jié)果。
雙發(fā)
假如一次調(diào)用不成功概率1%,那連續(xù)兩次調(diào)用都不成功的概率就是0.01%,一個簡單的提高服務調(diào)用成功率的辦法就是每次服務消費者要發(fā)起服務調(diào)用的時候,都同時發(fā)起兩次服務調(diào)用,可
- 提高調(diào)用的成功率
- 兩次服務調(diào)用,哪個先返回就采用哪次返回結(jié)果,平均響應時間也要比一次調(diào)用更快
這就是雙發(fā)。
但這樣一次調(diào)用會給后端服務兩倍壓力,要消耗的資源也加倍,所以“魯莽”雙發(fā)不可取。
更為聰明的雙發(fā),即
“備份請求”(Backup Requests)
服務消費者發(fā)起一次服務調(diào)用后,在給定的時間內(nèi)如果沒有返回請求結(jié)果,那么服務消費者就立刻發(fā)起另一次服務調(diào)用。
注意該設定時間通常要比超時時間短得多,比如超時時間取P999,那么備份請求時間取的可能是P99或P90,因為若在P99或者P90時間內(nèi)調(diào)用還沒有返回結(jié)果,那么大概率可以認為這次請求屬于慢請求,再次發(fā)起調(diào)用理論上返回要更快。
在實際線上服務運行時,P999由于長尾請求時間較長的緣故,可能要遠遠大于P99和P90。
比如一個服務的P999是1s,而P99只有200ms、P90只有50ms,這樣的話,如果備份請求時間取的是P90,那么第二次請求等待的時間只有50ms。
備份請求要設置一個最大重試比例,以避免在服務端出現(xiàn)問題時,大部分請求響應時間都會超過P90,導致請求量幾乎翻倍,給服務提供者造成更大的壓力。
可設置成15%
- 盡量體現(xiàn)備份請求的優(yōu)勢
- 不會給服務提供者額外增加太大的壓力
熔斷
前面手段在服務提供者偶發(fā)異常時很有效,但若服務提供者故障,短時間內(nèi)無法恢復,都不能提高服務調(diào)用成功率,還會因重試給服務提供者帶來更大的壓力而加劇故障。
就需服務消費者能夠探測到服務提供者發(fā)生故障,短時間內(nèi)停止請求,給服務提供者故障恢復時間,待服務提供者恢復后,再繼續(xù)請求。
原理
把客戶端的每次服務調(diào)用用斷路器封裝,通過斷路器監(jiān)控每次服務調(diào)用。
若某段時間內(nèi),服務調(diào)用失敗次數(shù)達到一定閾值,斷路器就會被觸發(fā),后續(xù)服務調(diào)用直接返回,不會再向服務提供者發(fā)起請求。
熔斷后,一旦服務提供者恢復
服務調(diào)用如何恢復
Hystrix的斷路器包含三種狀態(tài):關(guān)閉、打開、半打開
- Closed態(tài)
正常情況下的斷路器處關(guān)閉狀態(tài),偶發(fā)的調(diào)用失敗也不影響
- Open態(tài)
當服務調(diào)用失敗次數(shù)達到閾值,斷路器就會處開啟狀態(tài),后續(xù)服務調(diào)用直接返回,不會向服務提供者發(fā)起請求
- Half Open態(tài)
當斷路器開啟,每隔一段時間,會進入半打開態(tài),這時會向服務提供者發(fā)起探測調(diào)用,以確定服務提供者是否恢復正常。
若調(diào)用成功,斷路器就關(guān)閉
若失敗,斷路器繼續(xù)保持開啟態(tài),并等待下個周期重新進入半打開態(tài)。
Hystrix會把每次服務調(diào)用都用HystrixCommand封裝,實時記錄每次服務調(diào)用狀態(tài),包括成功、失敗、超時還是被線程拒絕。
當一段時間內(nèi)服務調(diào)用的失敗率高于閾值,Hystrix的斷路器就會進入進入打開態(tài),新的服務調(diào)用直接返回,不會向服務提供者發(fā)起調(diào)用。
再等待設定時間間隔后,Hystrix的斷路器又會進入半打,新的服務調(diào)用又可重新發(fā)給服務提供者。若一段時間內(nèi)服務調(diào)用失敗率依然高于閾值,斷路器會重新進入打開態(tài),否則,重置為關(guān)閉態(tài)。
決定斷路器是否打開失敗率閾值通過如下參數(shù):
- HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
斷路器何時進入半打開態(tài)時間間隔通過如下參數(shù):
- HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
斷路器關(guān)鍵在于
統(tǒng)計一段時間內(nèi)服務調(diào)用的失敗率
滑動窗口算法

默認情況下,滑動窗口包含10個桶,每個桶時間寬度1s,每桶記錄這1s所有服務調(diào)用成功、失敗、超時的及被線程拒絕的次數(shù)。當新1s到來,滑動窗口就往前滑動,丟棄最舊桶,把最新桶包進來。
任意時刻,Hystrix都會取滑動窗口內(nèi)所有服務調(diào)用的失敗率作為斷路器開關(guān)狀態(tài)的判斷依據(jù),這10個桶內(nèi)記錄的所有失敗的、超時的、被線程拒絕的調(diào)用次數(shù)之和除以總的調(diào)用次數(shù)就是滑動窗口內(nèi)所有服務的調(diào)用的失敗率。
總結(jié)
對非冪等,即同一服務調(diào)用重復多次返回結(jié)果不同的,不可重試,比如大部分上行請求都是非冪等。
雙發(fā)是在重試基礎(chǔ)上進行一定優(yōu)化,減少超時等待時間,對于長尾請求場景很有效。采用雙發(fā)后,服務調(diào)用的P999能大幅減少,是提高服務調(diào)用成功率的有效手段。
熔斷能很好解決依賴服務故障引起的連鎖反應,對于線上存在大規(guī)模服務調(diào)用尤其是對非關(guān)鍵路徑的調(diào)用,即使調(diào)用失敗也對最終結(jié)果影響不大的情況下,更應引入熔斷。
參考
- https://martinfowler.com/bliki/CircuitBreaker.html
- https://github.com/Netflix/Hystrix/wiki/How-To-Use
本文轉(zhuǎn)載自微信公眾號「 JavaEdge」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 JavaEdge公眾號。