首頁 >web前端 >js教程 >節流解釋:管理 API 請求限制的指南

節流解釋:管理 API 請求限制的指南

DDD
DDD原創
2024-12-05 22:29:10477瀏覽

什麼時候應該在程式碼中實施限制?

對於大型項目,通常最好使用 Cloudflare Rate Limiting 或 HAProxy 等工具。這些功能強大、可靠,可以為您處理繁重的工作。

但是對於較小的專案 - 或者如果您想了解事情是如何工作的 - 您可以在程式碼中建立自己的速率限制器。為什麼?

  • 很簡單:您將建立一些簡單易懂的東西。
  • 預算友善:除了託管伺服器之外,無需額外費用。
  • 它適用於小型專案:只要流量較低,它就能保持快速且有效率。
  • 它是可重複使用的:您可以將其複製到其他項目中,而無需設定新的工具或服務。

你將學到什麼

在本指南結束時,您將了解如何在 TypeScript 中建立基本節流器以保護您的 API 不被淹沒。以下是我們將要介紹的內容:

  • 可設定的時間限制:每次被阻止的嘗試都會增加鎖定持續時間以防止濫用。
  • 請求上限:設定允許的最大請求數。這對於涉及付費服務的 API 尤其有用,例如 OpenAI。
  • 記憶體儲存:一種無需 Redis 等外部工具即可工作的簡單解決方案,非常適合小型專案或原型。
  • 每用戶限制:使用 IPv4 位址追蹤每個用戶的請求。我們將利用 SvelteKit 的內建方法輕鬆檢索客戶端 IP。

本指南旨在作為一個實用的起點,非常適合想要學習基礎知識而又不想不必要的複雜性的開發人員。 但它還沒有準備好生產

在開始之前,我想對 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,則這可能是用戶的第一個請求,或者他們已經處於不活動狀態足夠長的時間。在這種情況下:

    • 允許該操作。
    • 透過儲存使用者的 IP 並設定初始逾時來追蹤使用者。
  • 案例 B:活躍用戶

    如果找到該IP,則表示該用戶之前曾發出過請求。這裡:

    • 檢查自上一個區塊以來是否已經過了所需的等待時間(基於 timeoutSeconds 陣列)。
    • 如果已經過了足夠的時間:
    • 更新時間戳記。
    • 增加逾時索引(上限為最後一個索引以防止溢位)。
    • 如果沒有,請拒絕該請求。

在後一種情況下,我們需要檢查自上一個區塊以來是否已經過去了足夠的時間。透過索引,我們知道應該引用哪個 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]);

Throttling Explained: A Guide to Managing API Request Limits

認證注意事項

在登入系統中使用速率限制時,您可能會遇到以下問題:

  1. 使用者登錄,觸發 Throttler 將逾時與其 IP 關聯起來。
  2. 使用者登出或會話結束(例如,立即登出、cookie 隨會話過期以及瀏覽器崩潰等)。
  3. 當他們不久後嘗試再次登入時,Throtler 可能仍會阻止他們,返回 429 Too Many Requests 錯誤。

為了防止這種情況,請使用使用者的唯一使用者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 和速率限制時,重要的是要考慮如何以及何時刪除不再需要的 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
    }
}

這種清理機制有助於確保您的節流器不會無限期地保留舊記錄,從而保持應用程式的效率。雖然這種方法簡單且易於實現,但可能需要針對更複雜的用例進一步細化(例如,使用更高級的調度或處理高並發)。

透過定期清理,您可以防止記憶體膨脹,並確保一段時間內未嘗試發出請求的用戶不再被追蹤 - 這是使您的速率限制系統可擴展且資源高效的第一步。


  1. 如果您喜歡冒險,您可能有興趣閱讀屬性的分配方式及其變化方式。另外,為什麼不考慮內聯快取之類的虛擬機器優化,單態性特別青睞這種優化。享受。 ↩

以上是節流解釋:管理 API 請求限制的指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn