1 技術方案
1.1 限流/熔斷開關和閾值在ETCD中配置
在公共包go-common中封裝一個方法,在ETCD中設置限流/熔斷開啓/關閉的開關,將父類context傳遞進去,讀取環境變量,開關開啓則進行限流和熔斷(限流/熔斷閾值寫死,從環境變量中讀取)
優點:目標明確,工作量、技術實現可預知
缺點:需要人爲開啓/關閉限流、熔斷開關,限流/熔斷閾值固定,必須達到該閾值纔會出發,不夠靈活
1.2 根據打點統計結果決定限流/熔斷開啓時機(實時計算)
當同一個用戶對同一個模型或報表多次執行請求時(例如報表下載、查看線圖),請求次數超過閾值,自動開發限流;
當OLAP對同一個(模型/報表)請求多次返回查詢超時或者報錯時,這種返回異常超過設置的閾值,自動開啓熔斷,即在短時間內再次請求(該模型/報表)時,啓明星不再向OLAP查詢,而是直接報提示信息:OLAP查詢資源緊張/異常,等一定時間後(ETCD配置)再查詢的提示信息。
優點:視實時請求量、OLAP返回情況進行自動限流/熔斷,不需要人爲介入,比較靈活;
缺點:需要事先對各個接口打點,工作量大,情景複雜,實現難度高。
1.3 通過kong網關進行限流和熔斷
1.3.1 kong網關簡介
在微服務架構中,由於系統和服務的細分,導致系統結構變得非常複雜, 爲了跨平臺,爲了統一集中管理api,同時爲了不暴露後置服務。甚至有時候需要對請求進行一些安全、負載均衡、限流、熔斷、灰度等中間操作,基於此類種種的客觀需求一個類似綜合前置的系統就產生了,這就是API網關(API Gateway)。API網關作爲分散在各個業務系統微服務的API聚合點和統一接入點,外部請求通過訪問這個接入點,即可訪問內部所有的REST API服務。目前常用的微服務網關有kong、zuul、gateway。
1.3.2 優缺點分析
優點:可以利用現有框架進行功能擴展
缺點:需要接入grpc服務,前期工作量大
(1)grpc服務接入
Golang項目通過sidecar部署grpc-proxy和grpcserver(詳見:grpc-proxy(bridge)),實現grpc服務接入網關。
(2)grpc-proxy服務包括bridge服務以及proxy服務,其中bridge包括了服務註冊以及http請求橋接爲grpc請求,proxy包括了服務發現功能。
(3)golang可使用grpcx創建以及啓動服務,使用原生的grpc服務,需實現diagnose服務
1.4 實施節點安排
第一階段(年前):可以先實現方案1.1
第二階段(年後):等打點、緩存都加過之後,可以優化到方案1.2
第二階段(年後):go項目接入kong網關後,可以逐步優化到方案1.3
2 限流
2.1 限流(rate limit)技術方案
限流器,從字面上理解就是用來限制流量,有時候流量突增(可預期的比如“雙11”,不可預期的微博的熱門話題等),會將後端服務壓垮,甚至直接宕機,使用限流器能限制訪問後端的流量,起到一個保護作用,被限制的流量,可以根據具體的業務邏輯去處理,直接返回錯誤或者返回默認值等等。
2.2 限流算法
2.2.1 令牌桶算法
一個存放固定容量令牌的桶,按照固定速率往桶中添加令牌,請求是否被處理需要看桶中令牌是否足夠,當令牌數爲0時,則拒絕新的請求。
令牌桶算法限制的是平均流入速率,並允許一定程度的突發流量。
2.2.2 漏桶算法
一個固定容量的漏桶,按照固定速率流出請求,流入請求速率任意,當流入的請求數累積到漏桶容量時,則新的請求被拒絕。
漏桶算法限制的是平均流入速率,主要目的是平滑突發流入速率。
https://github.com/uber-go/ratelimit
https://github.com/juju/ratelimit
https://github.com/lyft/ratelimit
基於令牌桶算法和漏桶算法來實現的限速限流,Golang實現:https://github.com/yangwenmai/ratelimit
2.3 限流方式
2.3.1 應用級限流
- 限流總併發/連接/請求數
- 通過池化技術限流總資源數(連接池、線程池)
- 限流某個接口的總併發/請求數
- 限流某個接口的時間窗口請求數(某接口每秒的請求數)
- 平滑限流某接口的請求數
2.3.2 分佈式限流
分佈式限流最關鍵的是將限流服務做成原子化,如可以使用Redis + Lua或Ngnix + Lua的方式實現這兩種技術可以實現高併發和高性能。
2.3.3 接入層限流
接入層通常指請求流量的入口,主要的目的有:負載均衡、非法請求過濾、請求聚合、緩存、降級、限流、A/B測試、服務質量監控等。
2.3.4 節流
有時想要在特定的時間窗口內對重複的相同事件最多隻處理一次,或者想要限制多個連續的相同事件最小執行時間間隔,那麼可以使用節流(throttle)是心啊,其防止多個相同事件連續重複執行。
3 降級與熔斷(circuit breaker)
在開發高併發系統時,可以通過使用緩存、限流、降級等方式保護系統,維護服務的穩定性和可用性。
當訪問量劇增、服務異常(如響應時間過長或者不響應)、非核心服務影響到核心系統的性能時,仍然需要保證關鍵服務是可用的,這是就需要使用服務降級方案。系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級。
服務降級的目的是保證核心服務可用,即使是有損的。
3.1 降級分類
1.按照是否自動化分爲:自動降級、人工開關降級
2.按照功能分類:接口降級、服務降級
3.2 熔斷(circuit breaker)技術方案
和限流器對依賴服務的保護機制不一樣,熔斷器是當依賴的服務已經出現故障時,爲了保證自身服務的正常運行不再訪問依賴的服務,防止雪崩效應。
熔斷器有三種狀態:
- 關閉狀態:服務正常,並維護一個失敗率統計,當失敗率達到閥值時,轉到開啓狀態;
- 開啓狀態:服務異常,調用 fallback 函數,一段時間之後,進入半開啓狀態
- 半開啓裝態:嘗試恢復服務,失敗率高於閥值,進入開啓狀態,低於閥值,進入關閉狀態
github.com/afex/hystrix-go,提供了 go 熔斷器實現,使用也很方便。
(1)首先創建一個熔斷器:
使用hystrix-go創建熔斷器:
// 熔斷器
hystrix.ConfigureCommand(
"addservice", // 熔斷器名字,可以用服務名稱命名,一個名字對應一個熔斷器,對應一份熔斷策略
hystrix.CommandConfig{
Timeout: 100, // 超時時間 100ms
MaxConcurrentRequests: 2, // 最大併發數,超過併發返回錯誤
RequestVolumeThreshold: 4, // 請求數量的閥值,用這些數量的請求來計算閥值
ErrorPercentThreshold: 25, // 錯誤數量閥值,達到閥值,啓動熔斷,25%
SleepWindow: 1000, // 熔斷嘗試恢復時間
},
)
hystrix提供了阻塞和非阻塞兩種使用方式,完整代碼可以參考如下鏈接: https://github.com/hatlonely/hellogolang/blob/master/sample/addservice/cmd/client/main.go
(2)阻塞方式使用 Do 方法,返回一個 err
熔斷的阻塞方式:
err := hystrix.Do("addservice", func() error {
// 正常業務邏輯,一般是訪問其他資源
var err error
// 設置總體超時時間 10 ms 超時
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50*time.Millisecond))
defer cancel()
res, err = client.Add(
ctx, req,
// 這裏可以再次設置重試次數,重試時間,重試返回碼
grpc_retry.WithMax(3),
grpc_retry.WithPerRetryTimeout(time.Duration(5)*time.Millisecond),
grpc_retry.WithCodes(codes.DeadlineExceeded),
)
return err
}, func(err error) error {
// 失敗處理邏輯,訪問其他資源失敗時,或者處於熔斷開啓狀態時,會調用這段邏輯
// 可以簡單構造一個response返回,也可以有一定的策略,比如訪問備份資源
// 也可以直接返回 err,這樣不用和遠端失敗的資源通信,防止雪崩
// 這裏因爲我們的場景太簡單,所以我們可以在本地在作一個加法就可以了
fmt.Println(err)
res = &addservice.AddResponse{V: req.A + req.B}
return nil
})
(3)非阻塞方式使用 Go 方法,返回一個 error 的 channel,建議在有多個資源需要併發訪問的場景下使用
熔斷的非阻塞方式:
err1 := hystrix.Go("addservice", func() error {
var err error
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50*time.Millisecond))
defer cancel()
res1, err = client.Add(ctx, req)
if err == nil {
success <- struct{}{}
}
return err
}, nil)
// 有 fallback 處理
err2 := hystrix.Go("addservice", func() error {
var err error
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(50*time.Millisecond))
defer cancel()
res2, err = client.Add(ctx, req)
if err == nil {
success <- struct{}{}
}
return err
}, func(err error) error {
fmt.Println(err)
res2 = &addservice.AddResponse{V: req.A + req.B}
success <- struct{}{}
return nil
})
for i := 0; i < 2; i++ {
select {
case <-success:
fmt.Println("success", i)
case err := <-err1:
fmt.Println("err1:", err)
case err := <-err2:
// 這個分支永遠不會走到,因爲熔斷機制裏面永遠不會返回錯誤
fmt.Println("err2:", err)
}
}
go-kit微服務實踐,http restful api、日誌功能、限流、服務監控、服務發現與註冊、api網關、服務鏈路跟蹤、服務熔斷、jwt身份認證:https://github.com/vincecfl/go-kit-one
https://github.com/rubyist/circuitbreaker
當程序進行遠程調用時,通常使用熔斷器(機制)/斷路器circuit breaker。遠程調用通常會在超時之前掛起一段時間。如果應用程序發出大量這些請求,許多資源可能會被擁堵在一起,等待這些超時發生。熔斷器通過包裝這些遠程調用,並在發生定義數量的故障或超時後跳閘。當熔斷器被絆倒時,任何之後的調用都將避免進行遠程調用並將錯誤返回給調用者。同時,熔斷器將定期允許再次嘗試某些調用請求,如果這些調用成功,將關閉訪問鏈路。
參考
微服務組件之限流器與熔斷器:https://studygolang.com/articles/13254
Golang實現請求限流的幾種辦法:https://blog.csdn.net/micl200110041/article/details/82013032
測試代碼: https://github.com/hatlonely/hellogolang/blob/master/sample/addservice/cmd/client/main.go
Circuit Breaker Pattern: https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn589784(v%3dpandp.10)
hystrix-go: https://github.com/afex/hystrix-go/
限流:漏桶算法和令牌桶算法:http://maiyang.github.io/%E6%8A%80%E6%9C%AF/%E7%AE%97%E6%B3%95/2017/05/28/rate-limit-algorithm
維基百科:Token_bucket:https://en.wikipedia.org/wiki/Token_bucket
維基百科:Leaky_bucket:https://en.wikipedia.org/wiki/Leaky_bucket
接口限流實踐:http://www.cnblogs.com/LBSer/p/4083131.html
流量調整和限流技術:http://colobu.com/2014/11/13/rate-limiting/
微服務最強開源流量網關Kong:https://www.jianshu.com/p/934d18ff2efd