Pourquoi Redis Streams et Lua ? Le duo dynamique expliqué

Avant de plonger dans le code, voyons pourquoi cette combinaison est le Batman et Robin de la limitation de débit :

  • Redis Streams : Pensez-y comme une file de messages surpuissante avec des capacités de voyage dans le temps.
  • Scripts Lua : L'outil multifonction de Redis qui vous permet d'exécuter une logique complexe de manière atomique.

Ensemble, ils sont comme le beurre de cacahuète et la confiture, si le beurre de cacahuète pouvait traiter des millions de requêtes par seconde et la confiture exécuter des opérations atomiques. Délicieux.

Le Plan : Construire notre limiteur de débit personnalisé

Voici le plan de jeu :

  1. Utiliser Redis Streams pour enregistrer les requêtes entrantes.
  2. Implémenter un algorithme de fenêtre glissante avec des scripts Lua.
  3. Ajouter du piquant avec des ajustements dynamiques de débit en fonction de la charge du serveur.

Étape 1 : Enregistrer les requêtes avec Redis Streams

Tout d'abord, configurons notre flux pour enregistrer ces requêtes entrantes :


import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def log_request(user_id):
    return r.xadd('requests', {'user_id': user_id})

Simple, non ? Nous ajoutons simplement chaque requête à un flux appelé 'requests'. La beauté des flux est qu'ils sont ordonnés dans le temps, ce qui est parfait pour notre approche de fenêtre glissante.

Étape 2 : Le script Lua - Là où la magie opère

Maintenant, écrivons un script Lua qui va :

  • Vérifier le nombre de requêtes dans les X dernières secondes
  • Décider si la requête doit être autorisée
  • Nettoyer les anciennes entrées

local function check_rate_limit(user_id, max_requests, window_size_ms)
    local now = redis.call('TIME')
    local now_ms = tonumber(now[1]) * 1000 + tonumber(now[2] / 1000)
    local window_start = now_ms - window_size_ms
    
    -- Supprimer les anciennes entrées
    redis.call('XTRIM', 'requests', 'MINID', tostring(window_start))
    
    -- Compter les requêtes dans la fenêtre
    local count = redis.call('XLEN', 'requests')
    
    if count < max_requests then
        -- Autoriser la requête
        redis.call('XADD', 'requests', '*', 'user_id', user_id)
        return 1
    else
        -- Limite de débit dépassée
        return 0
    end
end

return check_rate_limit(KEYS[1], tonumber(ARGV[1]), tonumber(ARGV[2]))

Ce script fait beaucoup de travail :

  • Il calcule l'heure actuelle en millisecondes
  • Élague le flux pour ne garder que les entrées récentes
  • Compte les requêtes dans la fenêtre actuelle
  • Décide s'il faut autoriser la requête

Étape 3 : Tout assembler

Maintenant, enveloppons cela dans une fonction Python :


lua_script = """
-- Notre script Lua ci-dessus va ici
"""

rate_limiter = r.register_script(lua_script)

def is_allowed(user_id, max_requests=100, window_size_ms=60000):
    return bool(rate_limiter(keys=[user_id], args=[max_requests, window_size_ms]))

# Utilisation
if is_allowed('user123'):
    print("Requête autorisée !")
else:
    print("Limite de débit dépassée !")

Passer au niveau supérieur : Ajustements dynamiques de débit

Mais attendez, il y a plus ! Et si nous pouvions ajuster notre limite de débit en fonction de la charge du serveur ? Ajoutons une touche à notre script Lua :


-- Ajoutez ceci en haut de notre script Lua
local server_load = tonumber(redis.call('GET', 'server_load') or "50")
local dynamic_max_requests = math.floor(max_requests * (100 - server_load) / 100)

-- Puis utilisez dynamic_max_requests au lieu de max_requests dans notre logique

Maintenant, nous ajustons notre limite de débit en fonction d'une valeur 'server_load' stockée dans Redis. Vous pourriez mettre à jour cette valeur périodiquement en fonction de vos métriques de serveur réelles.

Les pièges : Ce qui pourrait mal tourner

Avant de vous précipiter pour implémenter cela en production, parlons de quelques pièges potentiels :

  • Utilisation de la mémoire : Les flux peuvent consommer beaucoup de mémoire s'ils ne sont pas correctement élagués. Surveillez l'utilisation de la mémoire Redis.
  • Dérive de l'horloge : Si vous exécutez cela sur plusieurs serveurs, assurez-vous que leurs horloges sont synchronisées.
  • Complexité des scripts Lua : Rappelez-vous, les scripts Lua bloquent Redis. Gardez-les courts et simples.

Conclusion : Pourquoi c'est important

Alors, pourquoi se donner tout ce mal alors que vous pourriez simplement utiliser une solution préconstruite ? Voici pourquoi :

  • Flexibilité : Vous pouvez adapter cela à n'importe quel schéma de limitation de débit, aussi étrange soit-il, que vous pouvez imaginer.
  • Performance : Cette configuration peut gérer un nombre incroyable de requêtes par seconde.
  • Apprentissage : Construire cela à partir de zéro vous donne une compréhension approfondie des concepts de limitation de débit.

De plus, soyons honnêtes, c'est tout simplement cool de dire que vous avez construit votre propre limiteur de débit.

Réflexion

"La seule façon de faire du bon travail est d'aimer ce que vous faites." - Steve Jobs

Alors que nous terminons ce voyage dans la limitation de débit personnalisée, demandez-vous : Quels autres composants "standards" pourraient bénéficier d'une refonte personnalisée alimentée par Redis ? Les possibilités sont infinies, limitées seulement par votre imagination (et peut-être la mémoire de votre instance Redis).

Maintenant, allez-y et limitez le débit avec style ! Vos API vous remercieront, et qui sait, peut-être serez-vous la vedette de la prochaine rencontre de développeurs. "Oh, vous utilisez un limiteur de débit prêt à l'emploi ? C'est mignon."