«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Объяснение регулирования: руководство по управлению лимитами запросов API

Объяснение регулирования: руководство по управлению лимитами запросов API

Опубликовано 22 декабря 2024 г.
Просматривать:695

Когда следует реализовывать регулирование в своем коде?

Для крупных проектов обычно лучше использовать такие инструменты, как Cloudflare Rate Limiting или HAProxy. Они мощные, надежные и возьмут на себя всю тяжелую работу.

Но для небольших проектов — или если вы хотите узнать, как все работает — вы можете создать свой собственный ограничитель скорости прямо в своем коде. Почему?

  • Это просто: вы создадите что-то простое и легкое для понимания.
  • Бюджетно: никаких дополнительных затрат, кроме хостинга вашего сервера.
  • Это работает для небольших проектов: пока трафик низкий, все происходит быстро и эффективно.
  • Многократное использование: вы можете скопировать его в другие проекты без установки новых инструментов или сервисов.

Чему вы научитесь

К концу этого руководства вы узнаете, как создать базовый дроссель в TypeScript, чтобы защитить ваши API от перегрузки. Вот что мы рассмотрим:

  • Настраиваемые ограничения по времени: каждая заблокированная попытка увеличивает продолжительность блокировки во избежание злоупотреблений.
  • Ограничение количества запросов: установите максимальное количество разрешенных запросов. Это особенно полезно для API, включающих платные сервисы, например OpenAI.
  • Хранилище в памяти: простое решение, работающее без внешних инструментов, таких как Redis, — идеальное решение для небольших проектов или прототипов.
  • Ограничения на количество пользователей: отслеживайте запросы для каждого пользователя, используя его IPv4-адрес. Мы воспользуемся SvelteKit, чтобы легко получить IP-адрес клиента с помощью встроенного метода.

Это руководство задумано как практическая отправная точка и идеально подходит для разработчиков, которые хотят изучить основы без ненужных сложностей. Но он не готов к производству.

Прежде чем начать, я хочу отдать должное разделу Люсии «Ограничение скорости».


Реализация дросселя

Давайте определим класс Throttler:

export class Throttler {
    private storage = new Map();

    constructor(private timeoutSeconds: number[]) {}
}

Конструктор Throttler принимает список длительности таймаута (timeoutSeconds). Каждый раз, когда пользователь блокируется, продолжительность постепенно увеличивается в зависимости от этого списка. В конце концов, когда истечет последний тайм-аут, вы даже можете запустить обратный вызов, чтобы навсегда заблокировать IP-адрес пользователя, хотя это выходит за рамки данного руководства.

Вот пример создания экземпляра дросселя, который блокирует пользователей с увеличением интервалов:

const throttler = new Throttler([1, 2, 4, 8, 16]);

Этот экземпляр впервые заблокирует пользователей на одну секунду. Второй раз на двоих и так далее.

Мы используем карту для хранения IP-адресов и соответствующих им данных. Карта идеальна, поскольку она эффективно обрабатывает частые добавления и удаления.

Совет для профессионалов: используйте карту для динамических данных, которые часто меняются. Для статических, неизменных данных лучше использовать объект. (Кроличья нора 1)


Когда ваша конечная точка получает запрос, она извлекает IP-адрес пользователя и консультируется с регулятором, чтобы определить, следует ли разрешить запрос.

Как это работает

  • Случай А: Новый или неактивный пользователь

    Если IP-адрес не найден в Throttler, это либо первый запрос пользователя, либо он был неактивен достаточно долго. В этом случае:

    • Разрешить действие.
    • Отслеживайте пользователя, сохраняя его IP-адрес с начальным тайм-аутом.
  • Случай Б: Активный пользователь

    Если IP-адрес найден, это означает, что пользователь уже делал предыдущие запросы. Здесь:

    • Проверьте, прошло ли требуемое время ожидания (на основе массива timeoutSeconds) с момента их последней блокировки.
    • Если прошло достаточно времени:
    • Обновите временную метку.
    • Увеличьте индекс тайм-аута (ограничен последним индексом, чтобы предотвратить переполнение).
    • Если нет, отклоните запрос.

В последнем случае нам нужно проверить, прошло ли достаточно времени с момента последнего блока. Мы знаем, на какой из timeoutSeconds нам следует ссылаться, благодаря индексу. Если нет, просто вернитесь обратно. В противном случае обновите временную метку.

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
    }
}

При обновлении индекса он ограничивается последним индексом timeoutSeconds. Без него counter.index 1 переполнит его, и следующий доступ к this.timeoutSeconds[counter.index] приведет к ошибке времени выполнения.

Пример конечной точки

В этом примере показано, как использовать Throttler, чтобы ограничить частоту обращения пользователя к вашему API. Если пользователь сделает слишком много запросов, вместо запуска основной логики он получит ошибку.

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300]);

export async function GET({ getClientAddress }) {
    const IP = getClientAddress();

    if (!throttler.consume(IP)) {
        throw error(429, { message: 'Too Many Requests' });
    }

    // Read from DB, call OpenAI - do the thing.

    return new Response(null, { status: 200 });
}

Throttling Explained: A Guide to Managing API Request Limits

Примечание по аутентификации

При использовании ограничения скорости в системах входа вы можете столкнуться с этой проблемой:

  1. Пользователь входит в систему, и Throttler связывает тайм-аут с его IP-адресом.
  2. Пользователь выходит из системы или его сеанс завершается (например, происходит немедленный выход из системы, срок действия файлов cookie истекает вместе с сеансом, происходит сбой браузера и т. д.).
  3. Когда вскоре после этого они попытаются снова войти в систему, дроссель все равно может заблокировать их, вернув ошибку 429 Too Many Requests.

Чтобы предотвратить это, для ограничения скорости используйте уникальный идентификатор пользователя вместо его IP-адреса. Кроме того, вам необходимо сбросить состояние дроссельной заслонки после успешного входа в систему, чтобы избежать ненужных блокировок.

Добавьте метод сброса в класс Throttler:

export class Throttler {
    // ...

    public reset(key: string): void {
        this.storage.delete(key);
    }
}

И используйте его после успешного входа в систему:

const user = db.get(email);

if (!throttler.consume(user.ID)) {
    throw error(429);
}

const validPassword = verifyPassword(user.password, providedPassword);
if (!validPassword) {
    throw error(401);
}

throttler.reset(user.id); // Clear throttling for the user

Управление устаревшими IP-записями с помощью периодической очистки

Поскольку ваш регулятор отслеживает IP-адреса и ограничения скорости, важно подумать о том, как и когда удалять записи IP, которые больше не нужны. Без механизма очистки ваш дроссель будет продолжать хранить записи в памяти, что со временем может привести к проблемам с производительностью по мере роста данных.

Чтобы предотвратить это, вы можете реализовать функцию очистки, которая периодически удаляет старые записи после определенного периода бездействия. Вот пример того, как добавить простой метод очистки для удаления устаревших записей из дросселя.

export class Throttler {
    // ...

    public cleanup(): void {
        const now = Date.now()

        // Capture the keys first to avoid issues during iteration (we use .delete)
        const keys = Array.from(this.storage.keys())

        for (const key of keys) {
            const counter = this.storage.get(key)
            if (!counter) {
                // Skip if the counter is already deleted (handles concurrency issues)
                return
            }

            // If the IP is at the first timeout, remove it from storage
            if (counter.index == 0) {
                this.storage.delete(key)
                continue
            }

            // Otherwise, reduce the timeout index and update the timestamp
            counter.index -= 1
            counter.updatedAt = now
            this.storage.set(key, counter)
        }
    }
}

Очень простой (но, вероятно, не лучший) способ запланировать очистку — использовать setInterval:

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300])
const oneMinute = 60_000
setInterval(() => throttler.cleanup(), oneMinute)

Этот механизм очистки помогает гарантировать, что ваш дроссель не будет хранить старые записи бесконечно, сохраняя эффективность вашего приложения. Хотя этот подход прост и удобен в реализации, он может нуждаться в дальнейшей доработке для более сложных случаев использования (например, с использованием более сложного планирования или обработки высокого уровня параллелизма).

С помощью периодической очистки вы предотвращаете раздувание памяти и гарантируете, что пользователи, которые какое-то время не пытались отправлять запросы, больше не отслеживаются — это первый шаг к тому, чтобы сделать вашу систему ограничения скорости масштабируемой и ресурсоэффективной.


  1. Если вы любите приключения, возможно, вам будет интересно узнать, как распределяются свойства и как они изменяются. Кроме того, почему бы и нет, об оптимизации виртуальных машин, таких как встроенные кэши, которым особенно способствует мономорфизм. Наслаждаться. ↩

Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/didof/throttling-explained-a-guide-to-managing-api-request-limits-102a?1 Если есть какие-либо нарушения, пожалуйста, свяжитесь с [email protected] удалить его
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3