當咱們談微服務,咱們在談什麼 (3) — 如何保障微服務的穩定性

當一個單體應用改形成多個微服務以後,在請求調用過程當中每每會出現更多的問題,通訊過程當中的每個環節均可能出現問題。而在出現問題以後,若是不加處理,還會出現鏈式反應致使服務雪崩。服務治理功能就是用來處理此類問題的。咱們將從微服務的三個角色:註冊中心、服務消費者以及服務提供者一一提及。java

註冊中心如何保障穩定性

註冊中心主要是負責節點狀態的維護,以及相應的變動探測與通知操做。一方面,註冊中心自身的穩定性是十分重要的。另外一方面,咱們也不能徹底依賴註冊中心,須要時常進行相似註冊中心徹底宕機後微服務如何正常運行的故障演練。算法

這一節,咱們着重講的並非註冊中心自身可用性保證,而更多的是與節點狀態相關的部分。緩存

節點信息的保障

咱們說過,當註冊中心徹底宕機後,微服務框架仍然須要有正常工做的能力。這得益於框架內處理節點狀態的一些機制。網絡

本機內存併發

首先服務消費者會將節點狀態保持在本機內存中。一方面因爲節點狀態不會變動得那麼頻繁,放在內存中能夠減小網絡開銷。另外一方面,當註冊中心宕機後,服務消費者仍能從本機內存中找到服務節點列表從而發起調用。負載均衡

本地快照框架

咱們說,註冊中心宕機後,服務消費者仍能從本機內存中找到服務節點列表。那麼若是服務消費者重啓了呢?這時候咱們就須要一份本地快照了,即咱們保存一份節點狀態到本地文件,每次重啓以後會恢復到本機內存中。異步

服務節點的摘除

如今不管註冊中心工做與否,咱們都能順利拿到服務節點了。可是不是全部的服務節點都是正確可用的呢?在實際應用中,這是須要打問號的。若是咱們不校驗服務節點的正確性,頗有可能就調用到了一個不正常的節點上。因此咱們須要進行必要的節點管理。jvm

對於節點管理來講,咱們有兩種手段,主要是去摘除不正確的服務節點。微服務

註冊中心摘除機制

一是經過註冊中心來進行摘除節點。服務提供者會與註冊中心保持心跳,而一旦超出必定時間收不到心跳包,註冊中心就認爲該節點出現了問題,會把節點從服務列表中摘除,並通知到服務消費者,這樣服務消費者就不會調用到有問題的節點上。

服務消費者摘除機制

二是在服務消費者這邊拆除節點。由於服務消費者自身是最知道節點是否可用的角色,因此在服務消費者這邊作判斷更合理,若是服務消費者調用出現網絡異常,就將該節點從內存緩存列表中摘除。固然調用失敗多少次以後才進行摘除,以及摘除恢復的時間等等細節,其實都和客戶端熔斷相似,能夠結合起來作。

通常來講,對於大流量應用,服務消費者摘除的敏感度會高於註冊中心摘除,二者之間也不用刻意作同步判斷,由於過一段時間後註冊中心摘除會自動覆蓋服務消費者摘除。

服務節點是能夠隨便摘除/變動的麼

上一節咱們講能夠摘除問題節點,從而避免流量調用到該節點上。但節點是能夠隨便摘除的麼?同時,這也包含"節點是能夠隨便更新的麼?"疑問。

頻繁變更

當網絡抖動的時候,註冊中心的節點就會不斷變更。這致使的後果就是變動消息會不斷通知到服務消費者,服務消費者不斷刷新本地緩存。若是一個服務提供者有100個節點,同時有100個服務消費者,那麼頻繁變更的效果可能就是100*100,引發帶寬打滿。

這時候,咱們能夠在註冊中心這邊作一些控制,例如通過一段時間間隔後才能進行變動消息通知,或者打開開關後直接屏蔽不進行通知,或者經過一個機率計算來判斷須要向哪些服務消費者通知。

增量更新

一樣是因爲頻繁變更可能引發的網絡風暴問題,一個可行的方案是進行增量更新,註冊中心只會推送那些變化的節點信息而不是所有,從而在頻繁變更的時候避免網絡風暴。

可用節點過少

當網絡抖動,並進行節點摘除事後,極可能出現可用節點過少的狀況。這時候過大的流量分配給過少的節點,致使剩下的節點難堪重負,罷工不幹,引發惡化。而實際上,可能節點大多數是可用的,只不過因爲網絡問題與註冊中心未能及時保持心跳而已。

這時候,就須要在服務消費者這邊設置一個開關比例閾值,當註冊中心通知節點摘除,但緩存列表中剩下的節點數低於必定比例後(與以前一段時間相比),再也不進行摘除,從而保證有足夠的節點提供正常服務。

這個值其實能夠設置的高一些,例如百分之70,由於正常狀況下不會有頻繁的網絡抖動。固然,若是開發者確實須要下線多數節點,能夠關閉該開關。

服務消費者如何保障穩定性

一個請求失敗了,最直接影響到的是服務消費者,那麼在服務消費者這邊,有什麼能夠作的呢?

超時

若是調用一個接口,但遲遲沒有返回響應的時候,咱們每每須要設置一個超時時間,以防本身被遠程調用拖死。超時時間的設置也是有講究的,設置的太長起的做用就小,本身被拖垮的風險就大,設置的過短又有可能誤判一些正常請求,大幅提高錯誤率。

在實際使用中,咱們能夠取該應用一段時間內的P999的值,或者取p95的值*2。具體狀況須要自行定奪。

在超時設置的時候,對於同步與異步的接口也是有區分的。對於同步接口,超時設置的值不只須要考慮到下游接口,還須要考慮上游接口。而對於異步來講,因爲接口已經快速返回,能夠不用考慮上游接口,只需考慮自身在異步線程裏的阻塞時長,因此超時時間也放得更寬一些。

容錯機制

請求調用永遠不能保證成功,那麼當請求失敗時候,服務消費者能夠如何進行容錯呢?一般容錯機制分爲如下這些:

  • FailTry:失敗重試。就是指最多見的重試機制,當請求失敗後視圖再次發起請求進行重試。這樣從機率上講,失敗率會呈指數降低。對於重試次數來講,也須要選擇一個恰當的值,若是重試次數太多,就有可能引發服務惡化。另外,結合超時時間來講,對於性能有要求的服務,能夠在超時時間到達前的一段提早量就發起重試,從而在機率上優化請求調用。固然,重試的前提是冪等操做。
  • FailOver:失敗切換。和上面的策略相似,只不過FailTry會在當前實例上重試。而FailOver會從新在可用節點列表中根據負載均衡算法選擇一個節點進行重試。
  • FailFast:快速失敗。請求失敗了就直接報一個錯,或者記錄在錯誤日誌中,這沒什麼好說的。

另外,還有不少形形色色的容錯機制,大可能是基於本身的業務特性定製的,主要是在重試上作文章,例如每次重試等待時間都呈指數增加等。

第三方框架也都會內置默認的容錯機制,例如Ribbon的容錯機制就是由retry以及retry next組成,即重試當前實例與重試下一個實例。這裏要多說一句,ribbon的重試次數與重試下一個實例次數是以笛卡爾乘積的方式提供的噢!

熔斷

上一節將的容錯機制,主要是一些重試機制,對於偶然因素致使的錯誤比較有效,例如網絡緣由。但若是錯誤的緣由是服務提供者自身的故障,那麼重試機制反而會引發服務惡化。這時候咱們須要引入一種熔斷的機制,即在必定時間內再也不發起調用,給予服務提供者必定的恢復時間,等服務提供者恢復正常後再發起調用。這種保護機制大大下降了鏈式異常引發的服務雪崩的可能性。

在實際應用中,熔斷器每每分爲三種狀態,打開、半開以及關閉。引用一張martinfowler畫的原理圖:

在普通狀況下,斷路器處於關閉狀態,請求能夠正常調用。當請求失敗達到必定閾值條件時,則打開斷路器,禁止向服務提供者發起調用。當斷路器打開後一段時間,會進入一個半開的狀態,此狀態下的請求若是調用成功了則關閉斷路器,若是沒有成功則從新打開斷路器,等待下一次半開狀態週期。

斷路器的實現中比較重要的一點是失敗閾值的設置。能夠根據業務需求設置失敗的條件爲連續失敗的調用次數,也能夠是時間窗口內的失敗比率,失敗比率經過必定的滑動窗口算法進行計算。另外,針對斷路器的半開狀態週期也能夠作一些花樣,一種常見的計算方法是週期長度隨着失敗次數呈指數增加。

具體的實現方式能夠根據具體業務指定,也能夠選擇第三方框架例如Hystrix。

隔離

隔離每每和熔斷結合在一塊兒使用,仍是以Hystrix爲例,它提供了兩種隔離方式:

  • 信號量隔離:使用信號量來控制隔離線程,你能夠爲不一樣的資源設置不一樣的信號量以控制併發,並相互隔離。固然實際上,使用原子計數器也沒什麼不同。
  • 線程池隔離:經過提供相互隔離的線程池的方式來隔離資源,相對來講消耗資源更多,但能夠更好地應對突發流量。

降級

降級一樣大多和熔斷結合在一塊兒使用,當服務調用者這方斷路器打開後,沒法再對服務提供者發起調用了,這時候能夠經過返回降級數據來避免熔斷形成的影響。

降級每每用於那些錯誤容忍度較高的業務。同時降級的數據如何設置也是一門學問。一種方法是爲每一個接口預先設置好可接受的降級數據,但這種靜態降級的方法適用性較窄。還有一種方法,是去線上日誌系統/流量錄製系統中撈取上一次正確的返回數據做爲本次降級數據,但這種方法的關鍵是提供可供穩定抓取請求的日誌系統或者流量採樣錄製系統。

另外,針對降級咱們每每還會設置操做開關,對於一些影響不大的採起自動降級,而對於一些影響較大的則需進行人爲干預降級。

服務提供者如何保障穩定性

限流

限流就是限制服務請求流量,服務提供者能夠根據自身狀況(容量)給請求設置一個閾值,當超過這個閾值後就丟棄請求,這樣就保證了自身服務的正常運行。

閾值的設置能夠針對兩個方面考慮,一是QPS即每秒請求數,二是併發線程數。從實踐來看,咱們每每會選擇後者,由於QPS高每每是因爲處理能力高,並不能反映出系統"不堪重負"。

除此以外,咱們還有許多針對限流的算法。例如令牌桶算法以及漏桶算法,主要針對突發流量的情況作了優化。第三方的實現中例如guava rateLimiter就實現了令牌桶算法。在此就不就細節展開了。

重啓與回滾

限流更多的起到一種保障的做用,但若是服務提供者已經出現問題了,這時候該怎麼辦呢?

這時候就會出現兩種情況。一是自己代碼有bug,這時候一方面須要服務消費者作好熔斷降級等操做,一方面服務提供者這邊結合DevOps須要有快速回滾到上一個正確版本的能力。

更多的時候,咱們可能僅僅碰到了與代碼無強關聯的單機故障,一個簡單粗暴的辦法就是自動重啓。例如觀察到某個接口的平均耗時超出了正常範圍必定程度,就將該實例進行自動重啓。固然自動重啓須要有不少注意事項,例如重啓時間是否放在晚上,以及自動重啓引發的與上述節點摘除同樣的問題,都須要考慮和處理。

在過後覆盤的時候,若是當時沒有保護現場,就很難定位到問題緣由。因此每每在一鍵回滾或者自動重啓以前,咱們每每須要進行現場保護。現場保護能夠是自動的,例如一開始就給jvm加上打印gc日誌的參數-XX:+PrintGCDetails,或者輸出oom文件-XX:+HeapDumpOnOutOfMemoryError,也能夠配合DevOps自動腳本完成,固然手動也能夠。通常來講咱們會以下操做:

  • 打印堆棧信息,jstak -l 'java進程PID'
  • 打印內存鏡像,jmap -dump:format=b,file=hprof 'java進程PID'
  • 保留gc日誌,保留業務日誌

調度流量

除了以上這些措施,經過調度流量來避免調用到問題節點上也是很是經常使用的手段。

當服務提供者中的一臺機器出現問題,而其餘機器正常時,咱們能夠結合負載均衡算法迅速調整該機器的權重至0,避免流量流入,再去機器上進行慢慢排查,而不用着急第一時間重啓。

若是服務提供者分了不一樣集羣/分組,當其中一個集羣出現問題時,咱們也能夠經過路由算法將流量路由到正常的集羣中。這時候一個集羣就是一個微服務分組。

而當機房炸了、光纜被偷了等IDC故障時,咱們又部署了多IDC,也能夠經過一些方式將流量切換到正常的IDC,以供服務繼續正常運行。切換流量一樣能夠經過微服務的路由實現,但這時候一個IDC對應一個微服務分組了。除此以外,使用DNS解析進行流量切換也是能夠的,將對外域名的VIP從一個IDC切換到另外一個IDC。

相關文章
相關標籤/搜索