微服務 – 如何進行服務限流和熔斷

一、服務雪崩

​微服務架構是將單個應用程式被劃分成各種小而連接的服務,每一個服務完成一個單一的業務功能。相對於傳統的單體服務,微服務具有隔離性、技術異構性、可擴展性以及簡化部署等優點。通常一個應用由多個微服務組成,微服務之間的數據交互需要通過遠過程調用的方式完成。

​下圖是一個微服務之間互相調用的場景:

​微服務A調用微服務B、C和D,微服務C又調用微服務E。假設某一時刻,微服務E變為不可用。微服務C需要等待微服務E返回結果,於是請求就會逐漸堆積在微服務C,形成阻塞。隨著微服務C堆積的請求不斷增加,微服務A也會隨之慢慢阻塞。因為伺服器所能支撐的並發數有限,所以最終會耗盡伺服器資源,從而導致調用鏈條上更多的微服務不可用,形成雪崩效應。這種由一個服務崩潰導致整條服務鏈崩潰的情況,我們就稱之為服務雪崩。

​ 在微服務架構中,通常有兩種情況會導致服務雪崩:

  1. 突發性的訪問量劇增,超出了服務處理極限

  2. 調用鏈條上某個服務出現故障或者響應慢(延遲)

    針對以上這兩種情況,產生了對應的解決方案:服務限流和服務熔斷。

二、 服務限流

​ 服務限流是指在一定時間段內限制服務的請求量以保護系統,主要用於防止突發流量而導致的服務崩潰,比如秒殺、搶購、雙十一等場景,也可以用於安全目的,比如應對外部暴力攻擊。

​ 常用的限流演算法有以下幾種:

  • 計數器演算法

    ​ 通過維護一個內部計數器,對一段時間的服務請求進行累計,判斷計數器是否達到預先設定的閾值。如果沒有達到閾值,就允許請求通過,並且計數器加1;如果達到閾值,則拒絕服務,拋棄請求。進入下一個計時周期後,計數器清零,重新計數。

    ​ 計數器演算法是限流演算法中最簡單的演算法,缺點是有突刺現象,不僅請求通過的速率不均勻,而且請求輸出的速率也不均勻,對後續處理的並發要求比較高。比如:設定服務周期為1秒,請求的上限閾值為1000。如果前1ms內已經收到1000個請求,那麼剩下的時間都只能拒絕,而且後端服務需要並發處理1000個請求。

  • 漏桶演算法

    ​ 漏桶演算法的原理可以這樣理解,將服務請求想像成流入漏桶的水,漏桶中的水以恆定的速率從桶底流出,當流入漏桶的水速度過快,超過了漏桶容量時,則直接溢出。所以,漏桶演算法能夠控制服務請求按照固定速率均勻輸出,平滑突發流量,實現流量整形,為後續處理提供一個穩定的流量。但是,漏桶演算法無法控制請求按照一定的速率均勻輸入。

  • 令牌桶演算法

    ​ 令牌桶演算法是速率限制(Rate Limiting)和流量整形(Traffic Shaping)中最常使用的一種演算法。典型情況下,令牌桶演算法用來控制發送到網路上的數據的數目,並允許突發數據的發送。

    ​ 首先設定一個可以存放固定數量的令牌桶,使用某種機制以恆定的速度產生令牌。每次請求調用時,都必須去桶中獲取令牌,只有拿到令牌,才能放行,否則只能等待可用的令牌,或者直接拒絕。如果桶中的令牌消耗的速度小於產生的速度,令牌就會不斷地增多,直至填滿,再產生的令牌就會從桶中溢出。

    ​ 所以,令牌桶演算法既可以控制請求均勻輸入的速度,又可以控制請求的均勻輸出速率。

三、服務熔斷

​ 我們在各種場景下都會接觸到熔斷這兩個字。高壓電路中,如果某處電壓過高,熔斷器就會熔斷,對電路進行保護。股票交易中,如果股票漲跌幅過大,也會採用熔斷機制,暫停交易,來控制交易風險。

​ 同樣,在微服務架構中,熔斷機制也是起著類似的作用。當調用鏈路中的某個微服務長時間不可用或者有延遲,響應過慢,系統就會熔斷對該節點微服務的調用,快速返回錯誤資訊。當監控到該微服務正常工作後,再次恢復該調用鏈路。

四、Hystrix

​ 服務熔斷和服務限流作為微服務架構中作為服務治理的重要手段,在很多開源框架或者產品中都包含了此類功能。比如阿里dubbo,Netflix Hystrix,go-micro,go-kit等。

​ Hystrix是Netflix公司的開源項目,它是一個延遲和故障容錯庫,旨在隔離對遠程系統、服務和第三方庫的訪問點,防止級聯故障,並在無法避免發生故障的複雜分散式系統中實現了彈性。

​ Hystrix項目使用了java語言開發,程式碼託管地址為://github.com/Netflix/Hystrix。另外,有人使用go語言將該項目進行了移植,程式碼託管地址為://github.com/afex/hystrix-go/hystrix。

​ Hystrix可以做到以下事情:

  • 通過控制延遲和故障來保障第三方服務調用的可靠性
  • 在複雜的分散式系統中防止級聯故障,防止雪崩
  • 快速失敗、快速恢復
  • 回退並優雅降級
  • 提供近實時監控、報警和操作控制

​ Hystrix 能使你的系統在出現依賴服務失效的時候,通過隔離系統所依賴的服務,防止服務級聯失敗,同時提供失敗回退機制,更優雅地應對失效,並使你的系統能更快地從異常中恢復。

使用Hystrix 非常簡單:

package main

import (
	"github.com/afex/hystrix-go/hystrix"
	"log"
)

func main() {
	// 每一類微服務、遠程系統、第三方庫的調用都被包裝為一個命令,
	// 調用之前需要先行設置相關的配置資訊
	hystrix.ConfigureCommand(
		"ServiceA", // 命令名稱
		hystrix.CommandConfig{
			Timeout:                1000, // 超時時間設置(毫秒)
			MaxConcurrentRequests:  1,    // 最大並發請求數
			ErrorPercentThreshold:  50,   // 錯誤率閾值,超過閾值將熔斷服務
			SleepWindow:            5000, // 服務熔斷後,過多長時間,熔斷器再次檢測是否開啟(毫秒)
			RequestVolumeThreshold: 20,   // 服務熔斷前,所需的最小請求量,請求閾值  熔斷器是否打開首先要滿足這個條件;這裡的設置表示至少有5個請求才進行ErrorPercentThreshold錯誤百分比計算
		})

	// 使用hystrix調用遠程服務,並根據命令名稱啟用ConfigureCommand中設置的參數
	_ = hystrix.Do(
		"ServiceA", // 命令名稱
		// 運行函數
		func() error {
			// 調用遠程服務
			log.Println("invoke a remote service")
			return nil
		},
		// 失敗回退函數
		func(err error) error {
			log.Println("fall back")
			return err
		},
	)
}

註:
本文全部源程式碼位於://github.com/wangshizebin/micro-service
本文時候用的開發工具為:goland 來自 嗖嗖下載