1. 程式人生 > 其它 >Go微服務架構實戰 中篇:6. 微服務治理策略

Go微服務架構實戰 中篇:6. 微服務治理策略

Go微服務架構實戰-【公粽號:堆疊future】

原文
Go微服務架構實戰目錄

1. 微服務架構上篇

1. grpc技術介紹

2. grpc+protobuf+閘道器實戰

3. etcd技術介紹

4. 基於etcd的服務發現與註冊

5. 基於etcd的分散式鎖實戰

2. 微服務架構中篇

1. k8s架構介紹

2. 基於k8s的容器化部署

3. 基於k8s的Deployment工作負載

4. 基於k8s的ingress實戰

5. 基於ingress和service實現灰度釋出

6. 常見的服務治理策略

乾貨:


服務容錯: 故障轉移/快速失敗/故障恢復等

流量控制:滑動時間視窗/令牌桶/分散式限流等


1. 服務容錯

通過一張圖一目瞭然:

容錯設計模式:

1. 斷路器模式

斷路器的基本思路是很簡單的,就是通過代理(斷路器物件)來一對一地(一個遠端服務對應一個斷路器物件)接管服務呼叫者的遠端請求。斷路器會持續監控並統計服務返回的成功、失敗、超時、拒絕等各種結果,當出現故障(失敗、超時、拒絕)的次數達到斷路器的閾值時,它狀態就自動變為“OPEN”,後續此斷路器代理的遠端訪問都將直接返回呼叫失敗,而不會發出真正的遠端服務請求。通過斷路器對遠端服務的熔斷,避免因持續的失敗或拒絕而消耗資源,因持續的超時而堆積請求,最終的目的就是避免雪崩效應的出現。由此可見,斷路器本質是一種快速失敗策略的實現方式,它的工作過程可以通過下面圖來表示:

從呼叫序列來看,斷路器就是一種有限狀態機,斷路器模式就是根據自身狀態變化自動調整代理請求策略的過程。一般要設定以下三種斷路器的狀態:

  • CLOSED:表示斷路器關閉,此時的遠端請求會真正傳送給服務提供者。斷路器剛剛建立時預設處於這種狀態,此後將持續監視遠端請求的數量和執行結果,決定是否要進入 OPEN 狀態。

  • OPEN:表示斷路器開啟,此時不會進行遠端請求,直接給服務呼叫者返回呼叫失敗的資訊,以實現快速失敗策略。

  • HALF OPEN:這是一種中間狀態。斷路器必須帶有自動的故障恢復能力,當進入 OPEN 狀態一段時間以後,將“自動”(一般是由下一次請求而不是計時器觸發的,所以這裡自動帶引號)切換到 HALF OPEN 狀態。該狀態下,會放行一次遠端呼叫,然後根據這次呼叫的結果成功與否,轉換為 CLOSED 或者 OPEN 狀態,以實現斷路器的彈性恢復。

這些狀態的轉換邏輯與條件如下圖所示:

OPEN 和 CLOSED 狀態的含義是十分清晰的,與我們日常生活中電路的斷路器並沒有什麼差別,值得討論的是這兩者的轉換條件是什麼?最簡單直接的方案是隻要遇到一次呼叫失敗,那就預設以後所有的呼叫都會接著失敗,斷路器直接進入 OPEN 狀態,但這樣做的效果是很差的,雖然避免了故障擴散和請求堆積,卻使得外部看來系統將表現極其不穩定。現實中,比較可行的辦法是在以下兩個條件同時滿足時,斷路器狀態轉變為 OPEN:

  • 一段時間(譬如 10 秒以內)內請求數量達到一定閾值(譬如 20 個請求)。這個條件的意思是如果請求本身就很少,那就用不著斷路器介入。

  • 一段時間(譬如 10 秒以內)內請求的故障率(發生失敗、超時、拒絕的統計比例)到達一定閾值(譬如 50%)。這個條件的意思是如果請求本身都能正確返回,也用不著斷路器介入。

以上兩個條件同時滿足時,斷路器就會轉變為 OPEN 狀態。

斷路器做的事情是自動進行服務熔斷,這是一種快速失敗的容錯策略的實現方法,也是一種典型的服務降級策略。舉個例子:你女朋友被前男友約出去了,你打她手機沒人接,你氣沖沖地的結束通話後(快速失敗),然後你有打了另外三個你女朋友閨蜜的手機號(故障轉移),都還是沒能找到你女朋友(重試超過閾值)。這時候你非常生氣地在微信上給她留言“三分鐘不回電話就分手”,以此來與你取得聯絡。原諒我這麼舉例,雖然不太吉利,但是你給你女朋友留言這個行為便是服務降級邏輯。

其他服務治理的工具,譬如Envoyistio等也同樣會包含有類似的設定。

2. 重試模式

重試模式適合解決系統中的瞬時故障,簡單的說就是有可能自己恢復(Resilient,稱為自愈,也叫做回彈性)的臨時性失靈,網路抖動、服務的臨時過載(典型的如返回了 503 Bad Gateway 錯誤)這些都屬於瞬時故障。重試模式實現並不困難,即使完全不考慮框架的支援,靠程式設計師自己編寫十幾行程式碼也能夠完成。在實踐中,重試模式面臨的風險反而大多來源於太過簡單而導致的濫用。我們判斷是否應該且是否能夠對一個服務進行重試時,應同時滿足以下幾個前提條件:

  • 僅在主路邏輯的關鍵服務上進行同步的重試,不是關鍵的服務,一般不把重試作為首選容錯方案,尤其不該進行同步重試。

  • 僅對具備冪等性的服務進行重試。

  • 重試必須有明確的終止條件,常用的終止條件有兩種:

  1. 超時終止

  2. 次數終止

由於重試模式可以在網路鏈路的多個環節中去實現,比如客戶端發起呼叫時自動重試,閘道器中自動重試、負載均衡器中自動重試,等等,而且現在的微服務框架都足夠便捷,只需設定一兩個開關引數就可以開啟對某個服務甚至全部服務的重試機制。

2. 流量控制

與容錯模式類似,對於如何進行限流,也有一些常見的設計模式可以參考使用,本節將介紹滑動時間窗、令牌桶以及分散式限流三種限流設計模式。

1. 滑動時間視窗

有一個滑動時間視窗演算法,在電腦科學的很多領域中都有成功的應用,比如著名的TCP協議的流量控制等都是使用這個演算法實現流控的。具體實現大概可以想象下就是在不斷向前流淌的時間軸上,漂浮著一個固定大小的視窗,視窗與時間一起平滑地向前滾動。任何時刻靜態地通過視窗內觀察到的資訊,都等價於一段長度與視窗大小相等、動態流動中時間片段的資訊。由於視窗觀察的目標都是時間軸,所以它被稱為形象地稱為“滑動時間窗模式”。

滑動時間視窗模式可以保證任意時間片段內,只需經過簡單的呼叫計數比較,就能控制住請求次數一定不會超過限流的閾值,在單機限流或者分散式服務單點閘道器中的限流中很常用。不過,這種限流也有其缺點,它通常只適用於否決式限流,超過閾值的流量就必須強制失敗或降級,很難進行阻塞等待處理,也就很難在細粒度上對流量曲線進行整形,起不到削峰填谷的作用

2. 令牌桶

假設我們要限制系統在 X 秒內最大請求次數不超過 Y,那就每間隔 X/Y 時間就往桶中放一個令牌,當有請求進來時,首先要從桶中取得一個准入的令牌,然後才能進入系統處理。任何時候,一旦請求進入桶中卻發現沒有令牌可取了,就應該馬上失敗或進入服務降級邏輯。與此同時令牌桶也有最大容量限制,這意味著當系統比較空閒時,桶中令牌累積到一定程度就不再無限增加,預存在桶中的令牌便是請求最大緩衝的餘量。上面這段話,可以轉化為以下步驟:

  • 讓系統以一個由限流目標決定的速率向桶中注入令牌,比如要控制系統的訪問不超過 100 次,速率即設定為 1/100=10 毫秒。

  • 桶中最多可以存放 N 個令牌,N 的具體數量是由超時時間和服務處理能力共同決定的。如果桶已滿,第 N+1 個進入的令牌會被丟棄掉。

  • 請求到時先從桶中取走 1 個令牌,如果桶已空就進入降級邏輯。

桶:bucket 速率:rate

令牌桶模式的實現看似比較複雜,每間隔固定時間就要放新的令牌到桶中,但其實並不需要真的用一個專用執行緒或者定時器來做這件事情,只要在令牌中增加一個時間戳記錄,每次獲取令牌前,比較一下時間戳與當前時間,就可以輕易計算出這段時間需要放多少令牌進去,然後一次性放入即可,所以真正編碼並不會顯得很複雜。

3. 分散式限流

一種常見的簡單分散式限流方法是將所有服務的統計結果都存入集中式快取(如 Redis)中,以實現在叢集內的共享,並通過分散式鎖、訊號量等機制,解決這些資料的讀寫訪問時併發控制的問題。在可以共享統計資料的前提下,原本用於單機的限流模式理論上也是可以應用於分散式環境中的,可是其代價也顯而易見:每次服務呼叫都必須要額外增加一次網路開銷,所以這種方法的效率肯定是不高的,流量壓力大時,限流本身反倒會顯著降低系統的處理能力。

anyway,出現問題解決它就好了,但是你不能因為它一兩個缺點就不用,我覺得這不是一個程式設計師良好的做事風格,能扛事的人一般都是能成大事的人。

3. 小結

服務治理的話題隨便拿出一個來研究就能讓你增長不少見識,所以說大家下去有時間可以專門研究下相關的服務或者產品,比如我們提到的envoy以及istio等,看下它們是如何做到全部或者部分治理的,博採眾長,才能脫胎換骨,加油。

公粽號:堆疊future

使很多處於迷茫階段的coder能從這裡找到光明,堆疊創世,功在當代,利在千秋