idou老師教你學istio30:Mixer Redis Quota Adapter 實現和機制
1.1參數
1.2 Params.Quota
1.3Params.Override
1.4Params.QuotaAlgorithm
速率限制的算法:
Fixed Window 算法每個時間間隔對應一個計數器,每當有請求到來,如果此時計數器未達到配額的限定值,則計數器加 1,否則拒絕服務。當進入下一個時間間隔時,計數器失效被重置。該算法的缺點在於不能保證在任意的時間間隔內,速率都被限制在配額以下。即如果請求集中在計數器失效的時間點附近,則在該時間點附近的時間間隔內,速率最大能達到配額的兩倍。
Rolling Window 算法通過對上一個時間間隔請求數和當前時間間隔已處理的請求數進行加權,實現了對任意時間間隔的速率的估算。
圖片來自
https://blog.cloudflare.com/counting-things-a-lot-of-different-things/
如上圖所示,在上一分鐘內處理了 42 個請求,當前這一分鐘已經過去了 15 秒,處理了 18 個請求,則當前這一分鐘的速率可以估算為:
rate = 42 ((60-15)/60) + 18 = 42 0.75 + 18 = 49.5
如果使用 memquota adapter,默認使用亞秒級分辨率的 rolling window 實現速率限制。
如果使用 redisquota adapter,可以配置使用 Rolling Window 算法或者 Fixed Window 算法。
1.5例子
上面的 redisquota handler 定義了三種不同的速率限制模式:
如果沒有 override 能夠匹配,默認每秒限制 500 次請求;
如果請求的 destination 是 reviews,每秒限制 1 次請求;
如果請求的 destination 是 productpage,每秒限制 2 次請求。
- 代碼分析
2.1Mixer Server 啟動時註冊 Check 接口的流程
首先從 Mixer Server 的啟動入口 main() 函數看起,在 istio/mixer/cmd/mixs/main.go 中:
在 main() 函數中調用了 GetRootCmd() 方法,獲取 cobra 命令樹的根節點。
在 GetRootCmd() 方法中添加了 serverCmd() 命令。
在 serverCmd() 中定義了,當我們執行 mixs server 命令時,會調用 runServer() 函數,將 mixer 作為一個 gRPC server 啟動。
在 runServer() 中,調用了 server.New() 方法,初始化了一個具備全部功能的 Mixer server,可以開始接收流量。
在 server.New() 方法中,調用了 newServer() 函數。
在 newServer() 函數中,通過調用 grpc.NewServer() 方法,創建了一個 gRPC server,但是尚未註冊服務,而且未啟動去接收請求。然後調用了 RegisterMixerServer() 函數註冊 Mixer 服務。
在 RegisterMixerServer() 函數中,調用了上面創建的 gRPC server 的 RegisterService() 方法在該 gRPC server 中註冊 Mixer 服務。該方法的參數 _Mixer_serviceDesc 是對 Mixer 服務的描述。
在對 Mixer 服務的描述中可以看出,該服務提供了兩個接口,分別是 Check 和 Report。限流功能調用的是 Check 接口。
在處理 Check 請求的 handler 中,定義了該接口的 RPC 方法全稱,並且調用了 MixerServer 接口提供的 Check() 方法。
MixerServer 接口定義了 Mixer 服務提供的 API。Check接口在執行對服務的調用之前,預先對一些條件進行檢查,並分配配額。
2.2運行時 Mixer 處理 Check 接口調用的流程
如上圖所示,在 istio/mixer/pkg/api/grpcServer.go 中實現了 MixerServer 接口。
在 MixerServer 接口的 Check() 方法的實現中,調用了 check() 方法。
在 check() 方法中調用了 Dispatcher 接口的 Quota() 方法。
如上圖所示是 Dispatcher 接口的定義,Quota() 方法將請求分發到與 Quota API 相關的 adapter。
上圖所示是 Quota() 方法在運行時的實現,在實現時調用了 dispatch() 方法。
在 dispatch() 方法中,通過 session 的 variety 字段區分 preprocess、check、report 或 quota 幾種操作類型。如果是 quota 操作,首先匹配和 quota 的名稱對應的 instance,然後調用 dispatchToHandler() 方法對匹配到的 instance 進行分發。
在 dispatchTohandler() 方法中,調用了 ScheduleWork() 方法,註冊 invokeHandler() 方法,使其在某個時間點被調用。
在 invokeHandler() 方法中,通過調用 DispatchQuota() 方法,將 instace 分發到 Redis Quota adapter。
2.3Mixer Server啟動時註冊DispatchQuota() 函數流程
在 Mixer Server 的啟動入口 main() 函數中,調用 supportedTemplates() 函數傳入了支持的模板。
supportedTemplates() 函數返回 generatedTmplRepo.SupportedTmplInfo。
在 generatedTmplRepo 包中,將 SupportedTmplInfo 的 DispatchQuota 字段定義為上述函數,在該函數中調用了 Redis Quota Adatper 的 HandleQuota() 方法。
2.4Redis Quota Adapter 的實現
2.4.1結構定義
首先定義 handler 結構如下:
其中 client 字段是一個 Redis client,用於連接存放 quota 信息的 Redis 數據庫;limits 字段用於存放用戶配置的限制;dimensionHash 字段用於存放用戶定義的 override 的 dimension 的 hash;scripts 字段用於存放兩種限流算法的 Lua 腳本供 Redis 使用;getTime() 用於獲取當前時間。
builder 結構用於構建 handler,包含 quotaTypes 和 adapterConfig 兩個字段。adapterConfig 字段用於存放構建 handler 時需要的參數。
Params 結構定義如上,Quotas 字段是用戶定義的限制,RedisServerUrl 字段存放 Redis server 的地址,ConnectionPoolSize 字段存放到 Redis 的最大的 idle 連接數。
Params_Quota 結構定義如上。Name 字段是 quota 的名稱;MaxAmount 是分配 quota 數量的上限;ValidDuration 是分配的 quota 有效的時間;BucketDuration 字段僅在 RollingWindow 算法中使用,是窗口滑動的時間間隔;RateLimitAlgorithm 字段是限流算法的名稱,默認值是 FIXED_WINDOW;Overrides 字段定義了在某些條件下對默認規則的覆蓋。
Params_Override 結構定義如上。Dimensions 字段定義了該 override 適用的條件,MaxAmount 定義了該 override 下分配 quota 數量的上限。
2.4.2啟動時的配置方法
Validate() 方法對 builder 的參數進行校驗,並檢查 Redis 是否能連通,以及 Lua 腳本是否被導入 Redis。
Build() 方法通過 builder 構建 handler。
getDimensionHash() 方法計算 dimension 的 hash,在 Build() 方法中被調用,用於構建 handler。
2.4.3運行時方法
在 2.3 節中提到,Redis Quota Adapter 運行時的入口是 HandleQuota() 方法,該方法可以分為 3 個部分。
首先是調用 getKeyAndQuotaAmount() 方法,獲取該 instance 適用的 key 和 maxAmount。
getKeyAndQuotaAmount() 方法實現如上。如果該 instance 沒有任何 override 能夠匹配,那麽 key 為 quota 的名稱,maxAmount 為該 quota 默認的 maxAmount。如果匹配到了某個 override,那麽 key 為 quota 的名稱加上該 override 的 dimension 的 hash,maxAmount 為該 override 的 maxAmount。
第二步是調用 Redis 中的限流算法的 Lua 腳本,根據配置信息和參數信息,獲取運行結果。
然後調用 getAllocatedTokenFromResult() 函數,將 Redis 的返回結果轉換成分配的 token 數和有效時間,返回最終結果。
getAllocatedTokenFromResult() 函數定義如上,Redis 的返回結果應該有兩個值,第一個值為分配的 token 數,第二個值為 token 有效的時間。
相關服務請訪問:https://support.huaweicloud.com/cce/index.html?utm_content=cce_helpcenter_2019
idou老師教你學istio30:Mixer Redis Quota Adapter 實現和機制