idou老師教你學Istio:如何用 Istio 實現速率限制

雲容器大師發表於2018-12-12

使用 Istio 可以很方便地實現速率限制。本文介紹了速率限制的使用場景,使用 memquota\redisquota adapter 實現速率限制的方法,通過配置 rule 實現有條件的速率限制,以及速率限制的原理。

使用場景

在許多場景下都需要對服務進行速率限制。

一種常見的場景是防止來自外部服務的過度呼叫(如爬蟲)。

另一種常見的場景是呼叫某些收費的外部服務,但是提供了免費配額,可以使用速率限制確保只使用免費的配額。

 

2  環境準備

  • 在 Kubernetes 叢集上部署 Istio

  • 部署 Bookinfo 示例應用

  • 配置 Bookinfo 應用各個微服務的 destinationrule 和 virtualservice

使用 Istio 實現速率限制

1. 建立 memquota 或 redisquota 型別的 handler

一個請求到達某個服務時,一般會發生兩次對 Mixer 的呼叫,一次是前置檢查,一次是遙測報告。每一次呼叫,Mixer 都需要呼叫一個或多個介面卡。因此需要定義處理速率限制的 handler,可以在 memquota 和 redisquota 這兩種型別的 handler 中選擇一種:

上面的 memquota handler 定義了三種不同的速率限制模式:

  • 如果沒有 override 能夠匹配,預設每秒限制 500 次請求;

  • 如果請求的 destination 是 reviews,每 5 秒限制 1 次請求;

  • 如果請求的 destination 是 productpage,每 5 秒限制 1 次請求。

然後建立 memquota handler:

上面使用的是 memquota handler,memquota handler 繫結在 Mixer 程式上,沒有持久化,無 HA 能力,因此並不適合生產使用。可以用 redisquota handler 替代 memquota handler 實現持久化:

上面定義了一個 redisquota handler,定義的三種速率限制模式和上文的 memquota handler相同。

其中 rateLimitAlgorithm 欄位可以選擇 ROLLING_WINDOW 或者 FIXED_WINDOW,兩種演算法的介紹見下文“速率限制的原理”一節。

然後建立該 redisquota handler:

 2. 建立 quota template 的 instance

不同的介面卡需要不同的資料塊作為輸入來進行處理。例如日誌介面卡需要日誌輸入,指標介面卡需要指標輸入,認證介面卡需要憑據輸入。介面卡在請求時消費的資料是由 Mixer 的 template 來描述的,通過 quota template 可以定義 memquota\redisquota handler 需要的 dimension 資料。

編輯 quota_instance.yaml 內容如下:

上面的 quota template 定義了四種可以被 memquota\redisquota 使用的 dimension,可以 override 匹配到某些屬性的請求。比如 destination 會被置為 destination.labels["app"], destination.service.host 和 "unknown" 中第一個不為空的值。

然後執行如下命令建立 quota template 的 instance:

 

3. 建立 quota rule 

Rule 負責通知 Mixer,哪個 instance 應該在什麼時候傳送給哪個 handler。

編輯 quota_rule.yaml 內容如下:

上面這條 rule 告訴 Mixer 使用上面建立的 handler.memquota\handler.redisquota handler,並將上面建立的 requestcount.quota instance 構建的物件傳遞給該 handler。

然後執行如下命令建立上述 rule:

 

4. 建立 QuotaSpec

QuotaSpec 物件用於定義每次請求消費的 quota instance 的數量。

編輯 quota_spec.yaml 內容如下:

在上面的 QuotaSpec 中,我們定義每次請求消費 1 個 quota instance。

然後執行如下命令建立上述 QuotaSpec:

 

5. 建立 QuotaSpecBinding

QuotaSpecBinding 物件用於將上面建立的 QuotaSpec 繫結到需要應用限流的服務上。通過給不同的服務繫結不同的 QuotaSpec,可以實現對於呼叫代價較高的服務,每個請求消費較多的 quota instance。

編輯 quota_spec_binding.yaml 內容如下:

上面的 QuotaSpecBinding 將 productpage 繫結到名為 request-count 的 QuotaSpec 上。需要注意的是,如果服務的 namespace 和 QuotaSpecBinding 不同,需要指定 namespace。

然後執行如下命令建立上述 QuotaSpec:

 

6. 在瀏覽器中重新整理 productpage 頁面

因為限制了 productpage 只允許每 5 秒 2 個請求,所以如果持續重新整理頁面,會看到如下的 RESOURCE_EXHAUSTED:Quota is exhausted for: requestcount。

有條件的速率限制

上面只使用了 dimensions 來定義速率限制的條件,還可以在 rule 中使用任意屬性來定義規則。

例如,在某些場景下,我們不需要對已經登入的使用者進行速率限制。在 bookinfo 示例中,我們通過設定 cookie user=<username> 來識別已經登入的使用者。

修改 quota_rule.yaml 內容如下:

然後執行如下命令修改 rule:

修改後的 rule 使得當且僅當 user=<username> cookie 為空時才應用 memquota 或者 redisquota 介面卡,從而保證了已登入的使用者免受速率限制。

以“kokokobe”使用者登入,並持續重新整理頁面,每次都可以刷出下圖所示頁面。

登出之後持續重新整理頁面,會再一次看到下圖所示的 RESOURCE_EXHAUSTED:Quota is exhausted for: requestcount。

5 速率限制的原理

速率限制有 Token Bucket、Fixed Window、Rolling Window 等常見的演算法實現。

Token Bucket 演算法每經過固定的時間間隔會產生一個 token,如果此時 bucket 沒有滿,則產生的 token 可以被放入 bucket 中。當請求到來時,如果此時 bucket 中有足夠多的 token,則可以同時取走多個;如果此時 bucket 中 token 不夠,則拒絕服務。

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 演算法。

如果在該時間間隔內的請求數超過了 maxAmount,Mixer 會返回 RESOURCE_EXHAUSTED 資訊給 Envoy,Envoy 再返回 HTTP 429 StatusTooManyRequests 給呼叫者。

相關文章