"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Explicação sobre limitação: um guia para gerenciar limites de solicitações de API

Explicação sobre limitação: um guia para gerenciar limites de solicitações de API

Publicado em 2024-12-22
Navegar:310

Quando você deve implementar a limitação em seu código?

Para grandes projetos, geralmente é melhor usar ferramentas como Cloudflare Rate Limiting ou HAProxy. Eles são poderosos, confiáveis ​​​​e cuidam do trabalho pesado para você.

Mas para projetos menores – ou se você quiser aprender como as coisas funcionam – você pode criar seu próprio limitador de taxa diretamente em seu código. Por que?

  • É simples: você criará algo simples e fácil de entender.
  • É econômico: Sem custos extras além de hospedar seu servidor.
  • Funciona para pequenos projetos: Contanto que o tráfego esteja baixo, mantém as coisas rápidas e eficientes.
  • É reutilizável: você pode copiá-lo para outros projetos sem configurar novas ferramentas ou serviços.

O que você aprenderá

Ao final deste guia, você saberá como construir um acelerador básico em TypeScript para proteger suas APIs contra sobrecarga. Aqui está o que abordaremos:

  • Limites de tempo configuráveis: cada tentativa bloqueada aumenta a duração do bloqueio para evitar abusos.
  • Request Caps: Defina um número máximo de solicitações permitidas. Isso é especialmente útil para APIs que envolvem serviços pagos, como OpenAI.
  • Armazenamento na memória: uma solução simples que funciona sem ferramentas externas como Redis, ideal para pequenos projetos ou protótipos.
  • Limites por usuário: rastreie solicitações por usuário usando seu endereço IPv4. Aproveitaremos o SvelteKit para recuperar facilmente o IP do cliente com seu método integrado.

Este guia foi projetado para ser um ponto de partida prático, perfeito para desenvolvedores que desejam aprender o básico sem complexidade desnecessária. Mas não está pronto para produção.

Antes de começar, quero dar os devidos créditos à seção Rate Limiting de Lucia.


Implementação do acelerador

Vamos definir a classe Throttler:

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

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

O construtor Throttler aceita uma lista de durações de tempo limite (timeoutSeconds). Cada vez que um usuário é bloqueado, a duração aumenta progressivamente com base nesta lista. Eventualmente, quando o tempo limite final for atingido, você poderá até acionar um retorno de chamada para banir permanentemente o IP do usuário – embora isso esteja além do escopo deste guia.

Aqui está um exemplo de criação de uma instância de acelerador que bloqueia usuários em intervalos crescentes:

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

Esta instância bloqueará os usuários pela primeira vez por um segundo. A segunda vez para dois e assim por diante.

Usamos um mapa para armazenar endereços IP e seus dados correspondentes. Um mapa é ideal porque lida com adições e exclusões frequentes com eficiência.

Dica profissional: use um mapa para dados dinâmicos que mudam com frequência. Para dados estáticos e imutáveis, um objeto é melhor. (Toca de coelho 1)


Quando seu endpoint recebe uma solicitação, ele extrai o endereço IP do usuário e consulta o Throttler para determinar se a solicitação deve ser permitida.

Como funciona

  • Caso A: usuário novo ou inativo

    Se o IP não for encontrado no Throttler, é a primeira solicitação do usuário ou ele está inativo há tempo suficiente. Nesse caso:

    • Permitir a ação.
    • Rastreie o usuário armazenando seu IP com um tempo limite inicial.
  • Caso B: usuário ativo

    Se o IP for encontrado, significa que o usuário já fez solicitações anteriores. Aqui:

    • Verifique se o tempo de espera necessário (com base na matriz timeoutSeconds) passou desde o último bloco.
    • Se já passou tempo suficiente:
    • Atualize o carimbo de data/hora.
    • Incremente o índice de tempo limite (limitado ao último índice para evitar estouro).
    • Caso contrário, negue a solicitação.

Neste último caso, precisamos verificar se passou tempo suficiente desde o último bloco. Sabemos qual dos timeoutSeconds devemos nos referir graças a um índice. Se não, simplesmente se recupere. Caso contrário, atualize o carimbo de data/hora.

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

Ao atualizar o índice, ele limita ao último índice de timeoutSeconds. Sem ele, counter.index 1 iria estourá-lo e o próximo acesso this.timeoutSeconds[counter.index] resultaria em um erro de tempo de execução.

Exemplo de terminal

Este exemplo mostra como usar o Throttler para limitar a frequência com que um usuário pode chamar sua API. Se o usuário fizer muitas solicitações, receberá um erro em vez de executar a lógica principal.

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

Nota para autenticação

Ao usar a limitação de taxa com sistemas de login, você pode enfrentar este problema:

  1. Um usuário faz login, acionando o Throttler para associar um tempo limite ao seu IP.
  2. O usuário efetua logout ou sua sessão termina (por exemplo, efetua logout imediatamente, o cookie expira com a sessão e o navegador trava, etc.).
  3. Quando eles tentarem fazer login novamente logo depois, o Throttler ainda poderá bloqueá-los, retornando um erro 429 Too Many Requests.

Para evitar isso, use o ID de usuário exclusivo do usuário em vez de seu IP para limitação de taxa. Além disso, você deve redefinir o estado do acelerador após um login bem-sucedido para evitar bloqueios desnecessários.

Adicione um método de redefinição à classe Throttler:

export class Throttler {
    // ...

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

E use-o após um login bem-sucedido:

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

Gerenciando registros IP obsoletos com limpeza periódica

À medida que seu acelerador rastreia IPs e limites de taxa, é importante pensar em como e quando remover registros de IP que não são mais necessários. Sem um mecanismo de limpeza, seu acelerador continuará armazenando registros na memória, potencialmente levando a problemas de desempenho ao longo do tempo, à medida que os dados crescem.

Para evitar isso, você pode implementar uma função de limpeza que remove periodicamente registros antigos após um determinado período de inatividade. Aqui está um exemplo de como adicionar um método de limpeza simples para remover entradas obsoletas do acelerador.

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

Uma maneira muito simples (mas provavelmente não a melhor) de agendar a limpeza é com setInterval:

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

Esse mecanismo de limpeza ajuda a garantir que seu acelerador não retenha registros antigos indefinidamente, mantendo seu aplicativo eficiente. Embora esta abordagem seja simples e fácil de implementar, ela pode precisar de mais refinamento para casos de uso mais complexos (por exemplo, usando agendamento mais avançado ou lidando com alta simultaneidade).

Com a limpeza periódica, você evita o excesso de memória e garante que os usuários que não tentam fazer solicitações há algum tempo não sejam mais rastreados - este é o primeiro passo para tornar seu sistema de limitação de taxa escalonável e eficiente em termos de recursos.


  1. Se você estiver se sentindo aventureiro, pode estar interessado em ler como as propriedades são alocadas e como isso muda. Além disso, por que não, sobre otimizações de VMs, como caches inline, que são particularmente favorecidos pelo monomorfismo. Aproveitar. ↩

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/didof/throttling-explained-a-guide-to-managing-api-request-limits-102a?1 Se houver alguma violação, entre em contato com [email protected] para excluí-lo
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3