Rate Limiting en Java : comment protéger une API contre les surcharges et les bots

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Rate Limiting et Throttling en Java : de la théorie au code opérationnel



Imaginez : votre nouveau microservice en Java vient tout juste de démarrer, quand soudain une avalanche de requêtes s'abat sur lui. Pas des attaquants, non — juste un client négligent qui a oublié de mettre un délai dans son script. Résultat : la base de données plante, le serveur s'étouffe, et les utilisateurs (y compris les consciencieux) reçoivent une erreur 502 Bad Gateway. Cela vous dit quelque chose ?



Aujourd'hui, nous allons parler de la façon d'éviter cela. Le sujet de notre discussion est le rate limiting et le throttling en Java. Ce ne sont pas seulement des termes à la mode du monde DevOps, mais votre première ligne de défense pour toute API publique. Nous verrons en quoi la « limitation de débit » diffère de l'« écrêtage », nous examinerons les algorithmes populaires et, surtout, nous écrirons du code réel.



Si vous codez en Java et souhaitez que votre service reste vivant sous n'importe quelle charge, vous êtes au bon endroit. C'est parti !



Rate Limiting vs Throttling : quelle est la différence ?



Ces concepts sont souvent confondus, bien qu'ils résolvent des problèmes différents, bien que connexes.



  • Rate Limiting — c'est une barrière stricte. « Pas plus de 10 requêtes par seconde depuis une même IP ». Vous dépassez la limite ? Vous obtenez 429 Too Many Requests. C'est juste.
  • Throttling — c'est un mécanisme plus souple. Le service ne bloque pas la requête, mais ralentit son traitement. Par exemple, vous mettez la requête dans une file d'attente et la traitez à une vitesse fixe.


Dans les projets réels, le throttling en Java est souvent implémenté via des pools de threads (ThreadPoolExecutor) avec une capacité limitée, tandis que le rate limiting l'est via des compteurs, des caches et des filtres intermédiaires.



Algorithmes de Rate Limiting : choisissez votre arme



Avant d'écrire du code, choisissons une stratégie. Voici les trois algorithmes les plus populaires pour la limitation des requêtes en Java.



1. Token Bucket (Seau de jetons)

Un classique. Nous avons un « seau » dans lequel N nouveaux jetons tombent chaque seconde. Pour exécuter une requête, vous devez prendre un jeton. Si le seau est vide, la requête est rejetée. L'algorithme gère très bien les pics soudains de trafic, permettant d'« emprunter » des jetons à l'avance (jusqu'à la taille du seau).



2. Sliding Window (Fenêtre glissante)

Une méthode plus précise. Au lieu de compter les requêtes par seconde calendaire, nous déplaçons la fenêtre temporelle. Par exemple, « pas plus de 100 requêtes au cours des 60 dernières secondes ». Cela évite le « pic » au début de chaque intervalle.



3. Fixed Window (Fenêtre fixe)

La plus simple, mais dangereuse. On réinitialise le compteur toutes les N secondes. Problème : on peut faire mille requêtes à la fin d'une fenêtre, et encore mille au début de la suivante. Pas adapté aux systèmes critiques.



Nous nous concentrerons sur les deux premiers — ils offrent le meilleur équilibre entre performance et précision.



Écrire le code : un Rate Limiter fait maison



Implémentons un rate limiter en Java en utilisant l'algorithme Token Bucket. C'est un excellent exemple pour comprendre le fonctionnement interne des systèmes de limitation.



import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicLong;

public class TokenBucketRateLimiter {

private final long maxTokens; // Taille maximale du seau private final long refillRate; // Jetons par seconde private final ConcurrentHashMap<String, Bucket> buckets = new ConcurrentHashMap<>();

public TokenBucketRateLimiter(long maxTokens, long refillRate) { this.maxTokens = maxTokens; this.refillRate = refillRate; }

public boolean tryAcquire(String clientId) { Bucket bucket = buckets.computeIfAbsent(clientId, k -> new Bucket(maxTokens)); return bucket.tryConsume(); }

private class Bucket { private final AtomicLong tokens; private volatile long lastRefillTimestamp;

Bucket(long cap

Blogs

Book Recommendations