Waarom Redis Streams en Lua? Het Dynamische Duo Uitgelegd

Voordat we met de code aan de slag gaan, laten we eens kijken waarom deze combinatie de Batman en Robin van rate limiting is:

  • Redis Streams: Zie het als een krachtige berichtenwachtrij met tijdreisfuncties.
  • Lua Scripts: Het Zwitserse zakmes van Redis (oeps, ik zou die uitdrukking niet gebruiken—laten we zeggen "de multitool") waarmee je complexe logica atomisch kunt uitvoeren.

Samen zijn ze als pindakaas en jam, als pindakaas miljoenen verzoeken per seconde kon verwerken en jam atomische operaties kon uitvoeren. Heerlijk.

Het Plan: Onze Eigen Rate Limiter Bouwen

Hier is het stappenplan:

  1. Gebruik Redis Streams om binnenkomende verzoeken te loggen.
  2. Implementeer een schuivend venster-algoritme met Lua-scripts.
  3. Voeg wat pit toe met dynamische aanpassingen van de snelheid op basis van serverbelasting.

Stap 1: Verzoeken Loggen met Redis Streams

Laten we eerst onze stream instellen om die binnenkomende verzoeken te loggen:


import redis

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

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

Eenvoudig, toch? We voegen elk verzoek toe aan een stream genaamd 'requests'. Het mooie van streams is dat ze tijdsgeordend zijn, wat perfect is voor onze schuivende venster-aanpak.

Stap 2: Het Lua Script - Waar de Magie Gebeurt

Laten we nu een Lua-script schrijven dat:

  • Het aantal verzoeken in de laatste X seconden controleert
  • Bepaalt of het verzoek moet worden toegestaan
  • Oude items opruimt

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
    
    -- Oude items verwijderen
    redis.call('XTRIM', 'requests', 'MINID', tostring(window_start))
    
    -- Verzoeken in het venster tellen
    local count = redis.call('XLEN', 'requests')
    
    if count < max_requests then
        -- Verzoek toestaan
        redis.call('XADD', 'requests', '*', 'user_id', user_id)
        return 1
    else
        -- Limiet overschreden
        return 0
    end
end

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

Dit script doet veel werk:

  • Het berekent de huidige tijd in milliseconden
  • Trimt de stream om alleen recente items te behouden
  • Telt de verzoeken in het huidige venster
  • Bepaalt of het verzoek mag doorgaan

Stap 3: Alles Samenvoegen

Laten we dit nu in een Python-functie samenvoegen:


lua_script = """
-- Ons Lua-script van hierboven komt hier
"""

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

# Gebruik
if is_allowed('user123'):
    print("Verzoek toegestaan!")
else:
    print("Limiet overschreden!")

Naar een Hoger Niveau: Dynamische Aanpassingen van de Snelheid

Maar wacht, er is meer! Wat als we onze limiet konden aanpassen op basis van de serverbelasting? Laten we een draai geven aan ons Lua-script:


-- Voeg dit toe aan het begin van ons Lua-script
local server_load = tonumber(redis.call('GET', 'server_load') or "50")
local dynamic_max_requests = math.floor(max_requests * (100 - server_load) / 100)

-- Gebruik dan dynamic_max_requests in plaats van max_requests in onze logica

Nu passen we onze limiet aan op basis van een 'server_load'-waarde die in Redis is opgeslagen. Je zou deze waarde periodiek kunnen bijwerken op basis van je werkelijke serverstatistieken.

De Valkuilen: Wat Kan Er Fout Gaan?

Voordat je dit in productie implementeert, laten we het hebben over enkele mogelijke problemen:

  • Geheugengebruik: Streams kunnen veel geheugen gebruiken als ze niet goed worden getrimd. Houd je Redis-geheugengebruik in de gaten.
  • Klokafwijking: Als je dit op meerdere servers uitvoert, zorg ervoor dat hun klokken gesynchroniseerd zijn.
  • Lua Script Complexiteit: Onthoud dat Lua-scripts Redis blokkeren. Houd ze kort en krachtig.

Afronding: Waarom Dit Belangrijk Is

Dus, waarom al deze moeite doen als je gewoon een kant-en-klare oplossing kunt gebruiken? Hier is waarom:

  • Flexibiliteit: Je kunt dit aanpassen aan elk vreemd en wonderlijk rate limiting-schema dat je kunt bedenken.
  • Prestaties: Deze setup kan een enorm aantal verzoeken per seconde aan.
  • Leren: Dit zelf bouwen geeft je een diep begrip van rate limiting-concepten.

Bovendien, laten we eerlijk zijn, het is gewoon cool om te zeggen dat je je eigen rate limiter hebt gebouwd.

Stof tot Nadenken

"De enige manier om geweldig werk te doen, is door te houden van wat je doet." - Steve Jobs

Nu we deze reis naar aangepaste rate limiting afronden, vraag jezelf af: Welke andere "standaard" componenten zouden kunnen profiteren van een aangepaste, door Redis aangedreven make-over? De mogelijkheden zijn eindeloos, alleen beperkt door je verbeelding (en misschien het geheugen van je Redis-instantie).

Ga nu op pad en beperk met stijl! Je API's zullen je dankbaar zijn, en wie weet, misschien ben je wel het gesprek van de dag op de volgende ontwikkelaarsbijeenkomst. "Oh, gebruik je een kant-en-klare rate limiter? Dat is schattig."