對於大型項目,通常最好使用 Cloudflare Rate Limiting 或 HAProxy 等工具。這些功能強大、可靠,可以為您處理繁重的工作。
但是對於較小的專案 - 或者如果您想了解事情是如何工作的 - 您可以在程式碼中建立自己的速率限制器。為什麼?
在本指南結束時,您將了解如何在 TypeScript 中建立基本節流器以保護您的 API 不被淹沒。以下是我們將要介紹的內容:
本指南旨在作為一個實用的起點,非常適合想要學習基礎知識而又不想不必要的複雜性的開發人員。 但它還沒有準備好生產。
在開始之前,我想對 Lucia 的速率限制部分給予正確的評估。
讓我們定義 Throttler 類別:
export class Throttler { private storage = new Map<string, ThrottlingCounter>(); constructor(private timeoutSeconds: number[]) {} }
Throttler 建構子接受超時持續時間 (timeoutSeconds) 清單。每次使用者被封鎖時,持續時間都會根據此清單逐漸增加。最終,當達到最終超時時,您甚至可以觸發回調以永久禁止用戶的 IP - 儘管這超出了本指南的範圍。
以下是建立阻止使用者增加間隔的節流器實例的範例:
const throttler = new Throttler([1, 2, 4, 8, 16]);
此實例第一次會阻止使用者一秒鐘。兩人第二次,以此類推。
我們使用 Map 來儲存 IP 位址及其對應的資料。地圖是理想的選擇,因為它可以有效地處理頻繁的添加和刪除。
專業提示:使用地圖來處理經常變化的動態資料。對於靜態、不變的數據,物件更好。 (兔子洞1)
當您的端點收到請求時,它會提取使用者的 IP 位址並諮詢 Throttler 以確定是否應允許該請求。
案例 A:新用戶或不活躍用戶
如果在 Throttler 中找不到該 IP,則這可能是用戶的第一個請求,或者他們已經處於不活動狀態足夠長的時間。在這種情況下:
案例 B:活躍用戶
如果找到該IP,則表示該用戶之前曾發出過請求。這裡:
在後一種情況下,我們需要檢查自上一個區塊以來是否已經過去了足夠的時間。透過索引,我們知道應該引用哪個 timeoutSeconds。如果沒有,只需反彈即可。否則更新時間戳。
export class Throttler { private storage = new Map<string, ThrottlingCounter>(); constructor(private timeoutSeconds: number[]) {} }
更新索引時,上限為timeoutSeconds的最後一個索引。如果沒有它,counter.index 1 就會溢出,接下來存取 this.timeoutSeconds[counter.index] 將導致執行時期錯誤。
此範例顯示如何使用 Throttler 來限制使用者呼叫 API 的頻率。如果使用者發出太多請求,他們會收到錯誤,而不是運行主邏輯。
const throttler = new Throttler([1, 2, 4, 8, 16]);
在登入系統中使用速率限制時,您可能會遇到以下問題:
為了防止這種情況,請使用使用者的唯一使用者ID而不是他們的IP進行速率限制。此外,您必須在成功登入後重置節流器狀態,以避免不必要的阻塞。
在 Throttler 類別中加入重置方法:
export class Throttler { // ... public consume(key: string): boolean { const counter = this.storage.get(key) ?? null; const now = Date.now(); // Case A if (counter === null) { // At next request, will be found. // The index 0 of [1, 2, 4, 8, 16] returns 1. // That's the amount of seconds it will have to wait. this.storage.set(key, { index: 0, updatedAt: now }); return true; // allowed } // Case B const timeoutMs = this.timeoutSeconds[counter.index] * 1000; const allowed = now - counter.updatedAt >= timeoutMs; if (!allowed) { return false; // denied } // Allow the call, but increment timeout for following requests. counter.updatedAt = now; counter.index = Math.min(counter.index + 1, this.timeoutSeconds.length - 1); this.storage.set(key, counter); return true; // allowed } }
登入成功後使用:
export class Throttler { private storage = new Map<string, ThrottlingCounter>(); constructor(private timeoutSeconds: number[]) {} }
當您的節流器追蹤 IP 和速率限制時,重要的是要考慮如何以及何時刪除不再需要的 IP 記錄。如果沒有清理機制,您的節流器將繼續在記憶體中儲存記錄,隨著資料的成長,可能會導致效能問題。
為了防止這種情況,您可以實現一個清理功能,在一定時間不活動後定期刪除舊記錄。以下是如何新增簡單的清理方法以從節流器中刪除過時條目的範例。
const throttler = new Throttler([1, 2, 4, 8, 16]);
安排清理的一個非常簡單的方法(但可能不是最好的)是使用 setInterval:
export class Throttler { // ... public consume(key: string): boolean { const counter = this.storage.get(key) ?? null; const now = Date.now(); // Case A if (counter === null) { // At next request, will be found. // The index 0 of [1, 2, 4, 8, 16] returns 1. // That's the amount of seconds it will have to wait. this.storage.set(key, { index: 0, updatedAt: now }); return true; // allowed } // Case B const timeoutMs = this.timeoutSeconds[counter.index] * 1000; const allowed = now - counter.updatedAt >= timeoutMs; if (!allowed) { return false; // denied } // Allow the call, but increment timeout for following requests. counter.updatedAt = now; counter.index = Math.min(counter.index + 1, this.timeoutSeconds.length - 1); this.storage.set(key, counter); return true; // allowed } }
這種清理機制有助於確保您的節流器不會無限期地保留舊記錄,從而保持應用程式的效率。雖然這種方法簡單且易於實現,但可能需要針對更複雜的用例進一步細化(例如,使用更高級的調度或處理高並發)。
透過定期清理,您可以防止記憶體膨脹,並確保一段時間內未嘗試發出請求的用戶不再被追蹤 - 這是使您的速率限制系統可擴展且資源高效的第一步。
如果您喜歡冒險,您可能有興趣閱讀屬性的分配方式及其變化方式。另外,為什麼不考慮內聯快取之類的虛擬機器優化,單態性特別青睞這種優化。享受。 ↩
以上是節流解釋:管理 API 請求限制的指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!