控制應用程式向服務發送請求的速率,確保你能控制在服務 限速 限制與整體容量範圍內。 此方法有助於避免或減少節流錯誤,並更準確地預測吞吐量。
速率限制在許多情境下是合適的,但對於大規模且重複性的自動化任務(如批次處理)尤其有用。
內容和問題
對受限服務執行大量操作可能會導致流量增加和吞吐量下降,因為你需要追蹤被拒絕的請求,然後再重試操作。 隨著作業次數增加,節流限制可能會要求分多次重新傳送資料,因而對效能造成更大的影響。
例如,請考慮以下在發生錯誤時重試、將資料匯入 Azure Cosmos DB 的問題流程:
您的應用程式必須將 10,000 筆記錄內嵌至 Azure Cosmos DB。 每筆記錄需花費 10 個請求單元(RU)來完成,因此完成工作總共需 100,000 RUs。
您的 Azure Cosmos DB 實例已佈建 20,000 RU 的容量。
您會將所有 10,000 筆記錄傳送至 Azure Cosmos DB。成功寫入 2,000 筆記錄,並拒絕 8,000 筆記錄。
您將剩餘的 8,000 筆記錄傳送至 Azure Cosmos DB。成功寫入 2,000 筆記錄,並拒絕 6,000 筆記錄。
您將剩餘的 6,000 筆記錄傳送至 Azure Cosmos DB。成功寫入 2,000 筆記錄,並拒絕 4,000 筆記錄。
您將剩餘的 4,000 筆記錄傳送至 Azure Cosmos DB。成功寫入 2,000 筆記錄,並拒絕 2,000 筆記錄。
您會將其餘 2,000 筆記錄傳送至 Azure Cosmos DB。 全部都已成功寫入。
擷取工作會成功完成,但必須先將 30,000 筆紀錄傳送到 Azure Cosmos DB。 整個資料集僅包含 10,000 筆紀錄。
在這個例子中還有其他因素需要考慮:
大量錯誤也可能導致額外的工作量來記錄這些錯誤並處理產生的日誌資料。 前述方法可處理 20,000 個錯誤,記錄這些錯誤可能會造成處理、記憶體或儲存資源的負擔。
因為你不知道擷取服務的限速限制,你無法設定資料處理所需的時間預期。 速率限制可讓您估算資料擷取所需的時間。
解決方法
速率限制可減少流量,並可能藉由減少在指定時間內傳送至服務的記錄數目來改善輸送量。
服務可根據一段時間內的不同指標對請求進行節流,例如:
- 作業數目(例如每秒 20 個要求)。
- 數據量(例如每分鐘 2 GiB)。
- 作業的相對成本(例如每秒 20,000 RU)。
無論你用什麼標準來限速,限制速率的實作都會涉及控制在特定時間內送給服務的操作數量和/或規模。 速率限制優化了你對服務的使用,同時不會超過其限速能力。
在 API 處理請求速度超過限速匯入服務允許的情況下,你必須管理使用該服務的速度。 僅將限速視為資料速率不匹配,並在服務恢復前緩衝擷取請求,會造成風險。 如果應用程式在這種情況下停止回應,任何緩衝的資料可能會遺失。
若要避免此風險,請考慮將您的記錄傳送至能處理完整資料擷取速率的持久性訊息系統。 (像 Azure 事件中樞 這類服務每秒可處理數百萬筆操作。)接著你可以使用一個或多個工作處理器,以受控速率從訊息系統讀取記錄,且速度在限速服務的限制範圍內。 將記錄提交至訊息傳遞系統,可以節省內部記憶體,因為您只需從佇列中取出在特定時間區間內可處理的記錄。
Azure 提供數個可搭配此模式使用的持久傳訊服務,包括:
當你傳送紀錄時,你用來釋出紀錄的時間範圍可能比服務限制的時間更細緻。 系統通常會根據你能輕鬆理解和操作的時間範圍來設定油門。 然而,對於執行服務的電腦來說,這些時間範圍可能遠遠超過它處理資訊的速度。 例如,系統可能會以每秒或每分鐘為單位進行節流,但程式碼的處理時間通常是奈秒或毫秒等級。
雖然不是必須,但通常建議以較少數量的記錄更頻繁地傳送,以提升吞吐量。 因此,與其嘗試每秒或每分鐘才為一次釋出將記錄集中成批處理,不如把批次切得更細,好讓資源消耗(記憶體、CPU 和網路)的速率維持得更平均。 這種做法避免了因突發請求爆發而產生的瓶頸。 例如,若服務允許每秒 100 次操作,則實作速率限制器可能會透過每 200 毫秒釋放 20 次操作來平衡請求,如下圖所示。
此外,有時需要多個未協調的進程來共用節流服務。 在此情境下,若要實現速率限制,你可以邏輯地分割服務容量,然後使用分散式互斥系統來管理這些分割區的專屬鎖。 接著,這些未經協調的程序在需要容量時,就會競相爭奪這些分割區上的鎖。 針對進程保留鎖定的每個分割區,它會被授與一定數量的容量。
例如,如果受限流的系統允許每秒 500 個請求,您可以建立 20 個分割區,每個分割區各可處理每秒 25 個請求。 如果需要發出 100 個要求的程式,可能會要求分散式互斥系統提供四個分割區。 系統可能會提供兩個分割區,為期 10 秒。 接著,此程式會將速率限制為每秒 50 個要求、在兩秒內完成工作,然後釋放鎖定。
實作此模式的一種方法是使用 Azure 儲存體。 在此情況下,您會在容器中為每個邏輯分割區建立 1 個 0 位元組 Blob 物件。 然後,您的應用程式可以直接針對這些 Blob 取得 一段短時間內的獨佔租用 (例如 15 秒)。 應用程式每獲授與一筆租用權,就可以使用該分割區對應的容量配額。 申請必須追蹤租期,以便在租期屆滿時停止使用所核准的容量。 當你實作這個模式時,通常會希望每個程序在需要容量時嘗試租用一個隨機分割區。
若要進一步降低延遲,您可以為每個進程配置少量的獨佔容量。 如此一來,程序只有在需要超出其保留容量時,才會嘗試取得共用容量的租約。
作為 Azure 儲存體 的替代方案,你也可以透過使用 ZooKeeper、etcd 和 Redis/Redsync 等技術來實作這類租約管理系統。
問題和考慮
在決定如何實施此模式時,請考慮以下幾點:
雖然速率限制模式可以減少限速錯誤的次數,但你的應用程式仍需妥善處理可能發生的限速錯誤。
確保重試與速率限制相互協調。 盲目或過度激進的重試會增加負載並造成重試風暴,因此應傳播背壓訊號(例如 HTTP 429 與
Retry-After),並使用有限次數的重試次數,並在嘗試間保持小幅隨機延遲。如果你的應用程式有多個工作流共用同一個限速服務,你需要把所有工作流整合進你的速率限制策略中。 例如,您可能支援將記錄大量載入資料庫中,但也會查詢相同資料庫中的記錄。 你可以透過確保所有工作流程都透過相同的速率限制機制來管理容量。 或者,您可以為每個工作流程保留個別的容量集區。
限速服務可用於多種應用。 在某些情況下,這種使用是可以協調的(如本文前面所示)。 如果您開始看到超出預期數量的節流錯誤,這種增加可能表示應用程式在存取某項服務時發生爭用。 在這種情況下,你可能需要考慮暫時降低速率限制機制所施加的吞吐量,直到其他應用程式的使用量減少。
使用此模式的時機
當下列情況時,請使用此模式:
你需要減少因速率限制服務而產生的限速錯誤。
相較於單純在發生錯誤時重試的作法,你會希望將流量降到最低。
你需要僅在有足夠容量可處理這些記錄時,才將它們從佇列中取出,以降低記憶體消耗。
在下列情況下,此模式可能不適用:
此操作需即時同步完成,延遲極低,且無法容忍排隊或延遲處理。
主要瓶頸不是請求率,而是並行性或資源爭用(例如CPU飽和或長時間的在機內工作)。 在這種情況下,擴展或並發控制會更為適當。
工作負載設計
評估如何在工作負載設計中使用速率限制模式,以達成Azure Well-Architected框架支柱所涵蓋的目標與原則。 下表提供此模式如何支援每個要素目標的指引。
| 支柱 | 此模式如何支援支柱目標 |
|---|---|
| 可靠性 設計決策有助於使工作負載具有韌性,並確保在故障發生後能復原到正常運作的狀態。 | 此策略透過體認並尊重在服務希望避免過度使用時,與其通訊所涉及的限制與成本,來保護客戶。 - RE:07 自我保護 |
如果此模式在一個支柱內部引入取捨,請將它們與其他支柱的目標進行考量。
Example
下列範例應用程式可讓使用者將不同類型的記錄提交至 API。 每個記錄類型都有獨特的工作處理器,執行以下步驟:
- 驗證
- 擴充
- 將記錄插入資料庫
應用程式的所有元件(API、工作處理器 A 與工作處理器 B)都是獨立的程序,可以獨立擴展。 這些程序之間並不直接溝通。
在此範例中,每個 blob 租約代表允許資料庫吞吐量的固定份額。 處理器只能以其目前持有租約的總速率進行出隊與寫入。 隨著處理器隨時間增加或失去租約,允許的寫入速率會改變,這會讓資料庫總流量維持在設定範圍內,同時仍能讓所有排隊工作繼續進行。
此圖表包含下列工作流程:
- 使用者會將 A 類型的 10,000 筆記錄提交至 API。
- API 會將這 10,000 筆紀錄加入 A 佇列中。
- 使用者會將類型 B 的 5,000 筆記錄提交至 API。
- API 會將這 5,000 筆紀錄加入 B 佇列。
- 工作處理器 A 發現佇列 A 中有記錄項目,並嘗試取得 Blob 2 的獨佔租用權。
- 工作處理器 B 看到佇列 B 有記錄,並嘗試取得對 blob 2 的排他租約。
- 工作處理者 A 未能取得租用權。
- 工作處理器 B 取得 Blob 2 的 15 秒租用權。 它現在可以將對資料庫的請求速率限制為每秒 100 次。
- 工作處理器 B 從佇列 B 中移除 100 筆紀錄並寫入。
- 一秒過去了。
- 工作處理器 A 看到佇列 A 有更多記錄,並嘗試取得對 blob 6 的排他租約。
- 工作處理器 B 發現佇列 B 有更多記錄,並嘗試取得對 blob 3 的排他租約。
- 工作處理器 A 取得 blob 6 為期 15 秒的租約。 它現在可以將對資料庫的請求速率限制為每秒 100 次。
- 作業處理器 B 取得 blob 3 的租約,為期 15 秒。 它現在可以將對資料庫的請求速率限制為每秒 200 次。 (它也持有 Blob 2 的租約。)
- 工作處理器 A 從佇列 A 中移除 100 筆紀錄並寫入。
- 工作處理器 B 從佇列 B 中移除 200 筆紀錄並寫入。
- 一秒過去了。
- 工作處理器 A 看到佇列 A 有更多記錄,並嘗試取得對 blob 0 的排他租約。
- 作業處理器 B 發現佇列 B 有更多記錄,並嘗試取得對 blob 1 的排他租約。
- 作業處理器 A 取得 blob 0 的 15 秒租用權。 它現在可以將對資料庫的請求速率限制為每秒 200 次。 (它也持有 Blob 6 的租約。)
- 作業處理器 B 取得了 Blob 1 的租約,租期為 15 秒。 它現在可以以每秒 300 的速度對資料庫的要求進行速率限制。 (它也持有 Blob 2 和 Blob 3 的租用。)
- 工作處理器 A 從佇列 A 中移除 200 筆紀錄並寫入。
- 工作處理器 B 從佇列 B 中移除 300 筆紀錄並寫入。
- 等等。
15 秒後,一個或兩個任務仍然無法完成。 隨著租約到期,處理器也應減少其出隊與寫入的請求數量。
此模式的實作版本有多種程式語言:
下一步
以下指引在實施此模式時也可能相關:
使用 Azure API 管理 的進階要求節流. 利用此作為輔助的邊緣准入控制,以強制執行每鍵呼叫速率限制與配額,並向客戶回傳一致的反壓訊號。
選擇 Azure 傳訊服務。 選擇最適合用於緩衝和受控擷取的持久性訊息骨幹。
處理 Azure 應用程式中的暫態故障。 設計重試行為,讓客戶端在達到限制時正確退縮。
相關資源
以下的模式與指引在實施此模式時也可能相關:
Throttling. 速率限制模式通常是在服務被限速時實作的。
Retry. 當對受節流限制的服務提出要求而導致節流錯誤時,通常適合在經過適當的間隔後重試這些要求。
以佇列為基礎的負載調節 與速率限制模式相似,但在幾個關鍵方面有所不同:
速率限制不一定需要使用佇列來管理負載,但它確實需要使用永久性傳訊服務。 例如,速率限制模式可以使用像 Apache Kafka 或事件中心這類服務。
速率限制模式引入了在各分割區上的分散式互斥系統概念,讓您能夠管理多個彼此未經協調、且與同一個受限流服務通訊的程序容量。
當服務間存在效能不匹配或想提升韌性時,Queue-Based 負載平衡模式都適用。 所以這個模式比速率限制更廣泛,後者更專注於有效存取被限速的服務。