Rate limiting och skydd mot botmissbruk
Implementera distribuerad rate limiting och throttling med Redis för att hantera toppar och blockera missbrukande klienter.
Rate limiting och skydd mot botmissbruk är en gratis lektion i Bootcamp i backendutveckling med FastAPI på CoddyKit. Detta är lektion 2 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Bootcamp i backendutveckling med FastAPI, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bootcamp i backendutveckling med FastAPI innehåller totalt 4 lektioner.
Varför distribuerad hastighetsbegränsning behövs
En ensam FastAPI-worker som lagrar en räknare för hastighetsbegränsning i processminnet slutar fungera så fort ni skalar horisontellt. Med N Uvicorn-workers bakom en lastbalanserare får en missbrukande klient N gånger den tillåtna mängden, eftersom varje worker räknar självständigt.
- Mål: en gemensam räknare som alla workers och pods ser.
- Verktyg: Redis, ett atomiskt minnesbaserat datalager som alla instanser ansluter till.
- Målsättning: hantera legitima trafiktoppar samtidigt som missbrukande botar stryps eller blockeras.
Under hela lektionen bygger vi först algoritmerna i ren Python och kopplar sedan in dem som FastAPI-beroenden.
Räknare med fast tidsfönster
Den enklaste algoritmen är att dela in tiden i fasta fönster (till exempel intervall på 60 sekunder) och öka en räknare per klient och fönster. När räknaren överskrider gränsen avvisas begäran.
Nedan ser ni en simulering i ren Python, så att ni kan se hur det fungerar utan Redis. Lägg märke till gränsproblemet: en klient kan skicka hela sin tilldelade kvot i slutet av ett fönster och sedan igen i början av nästa, vilket tillfälligt fördubblar den faktiska frekvensen.
import time
class FixedWindow:
def __init__(self, limit, window_seconds):
self.limit = limit
self.window = window_seconds
self.buckets = {}
def allow(self, key, now):
win = int(now // self.window)
bucket_key = (key, win)
count = self.buckets.get(bucket_key, 0) + 1
self.buckets[bucket_key] = count
return count <= self.limit
limiter = FixedWindow(limit=3, window_seconds=60)
base = 1000.0
for i in range(5):
ok = limiter.allow('user:42', base + i)
print(f'request {i+1}: {"ALLOW" if ok else "BLOCK"}')Logg med glidande tidsfönster
För att eliminera gränsproblemet sparar vi en logg med tidsstämplar per klient och räknar endast de händelser som inträffat inom det efterföljande fönstret räknat från now. Metoden är exakt men minneskrävande: en post per begäran.
Mönstret nedan är exakt det som vi kommer att översätta till en sorterad Redis-uppsättning, där poängen är tidsstämpeln.
import time
from collections import deque
class SlidingLog:
def __init__(self, limit, window_seconds):
self.limit = limit
self.window = window_seconds
self.logs = {}
def allow(self, key, now):
dq = self.logs.setdefault(key, deque())
cutoff = now - self.window
while dq and dq[0] <= cutoff:
dq.popleft()
if len(dq) < self.limit:
dq.append(now)
return True
return False
limiter = SlidingLog(limit=2, window_seconds=10)
for t in [0, 1, 2, 11, 12]:
ok = limiter.allow('ip:1.2.3.4', float(t))
print(f't={t}s -> {"ALLOW" if ok else "BLOCK"}')Token bucket: hantera trafiktoppar
Fasta och glidande tidsfönster är strikta. För att hantera toppar och samtidigt upprätthålla ett långsiktigt genomsnitt är token bucket standardvalet.
- Varje klient har en bucket med en kapacitet (maximal topp) och en påfyllnadstakt (tokens per sekund).
- Varje begäran förbrukar en token. Om bucketen är tom begränsas begäran.
- En topp på upp till
capacitybegäranden passerar omedelbart, varefter trafiken jämnas ut till påfyllnadstakten.
Detta är den algoritm vi rekommenderar för publika API:er som möter trafik med legitima men varierande toppar.
class TokenBucket:
def __init__(self, capacity, refill_per_sec):
self.capacity = capacity
self.refill = refill_per_sec
self.tokens = capacity
self.last = 0.0
def allow(self, now):
elapsed = now - self.last
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill)
self.last = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
bucket = TokenBucket(capacity=5, refill_per_sec=1)
for t in [0, 0, 0, 0, 0, 0, 3]:
print(f't={t}: {"ALLOW" if bucket.allow(float(t)) else "THROTTLE"}')Varför atomicitet är viktigt
Med många workers är läs-ändra-skriv för en räknare ett klassiskt race condition: två workers läser count=4, ökar värdet, skriver båda 5, och en begäran som borde ha blockerats släpps igenom.
Redis löser detta eftersom varje kommando är atomiskt, men ett beslut om hastighetsbegränsning kräver vanligtvis flera kommandon (öka, ange utgångstid, jämför). Den robusta lösningen är ett Lua-skript som körs av EVAL: Redis kör hela skriptet atomiskt, utan att någon annan klient kan lägga sig emellan.
Nästa scener visar det Redis-baserade fasta tidsfönstret och token bucket-algoritmen med denna metod.
Fast tidsfönster i Redis med INCR + EXPIRE
Den billigaste distribuerade begränsaren är en Redis-nyckel per klient och tidsfönster. INCR returnerar det nya antalet atomiskt. Vid det första träffet anger vi en TTL som motsvarar fönstret, så att nyckeln rensas automatiskt.
Vi omsluter båda kommandona i ett litet Lua-skript, så att ökningen och utgångstiden utgör en atomisk enhet. Detta är kopplingskod på FastAPI-sidan, inte ett fristående program.
import redis.asyncio as redis
FIXED_WINDOW_LUA = """
local current = redis.call('INCR', KEYS[1])
if current == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return current
"""
class RedisFixedWindow:
def __init__(self, client, limit, window):
self.client = client
self.limit = limit
self.window = window
self.script = client.register_script(FIXED_WINDOW_LUA)
async def allow(self, identifier: str) -> bool:
bucket = int(__import__('time').time()) // self.window
key = f'rl:fw:{identifier}:{bucket}'
count = await self.script(keys=[key], args=[self.window])
return count <= self.limit
# rl = RedisFixedWindow(redis.from_url('redis://localhost'), 100, 60)Redis-token bucket i Lua
För att hantera toppar flyttar vi token bucket till ett Lua-skript. Tillståndet lagras i en Redis-hash med tokens och ts (senaste påfyllnadstid). Skriptet fyller på baserat på den förflutna tiden, försöker förbruka en token och skriver tillbaka tillståndet — allt atomiskt.
Returvärdet 1 betyder att begäran tillåts och 0 betyder att den begränsas. Vi anger också en TTL så att inaktiva klienter frigör sitt minne.
TOKEN_BUCKET_LUA = """
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local state = redis.call('HMGET', key, 'tokens', 'ts')
local tokens = tonumber(state[1]) or capacity
local ts = tonumber(state[2]) or now
local delta = math.max(0, now - ts)
tokens = math.min(capacity, tokens + delta * refill)
local allowed = 0
if tokens >= 1 then
tokens = tokens - 1
allowed = 1
end
redis.call('HMSET', key, 'tokens', tokens, 'ts', now)
redis.call('EXPIRE', key, math.ceil(capacity / refill) * 2)
return allowed
"""
# Invoked with EVAL via redis-py: script(keys=[key], args=[cap, refill, time.time()])Ett återanvändbart FastAPI-beroende
Vi exponerar begränsaren som ett beroende, så att valfri route kan aktivera den. Beroendet härleder klientens identitet, kör begränsaren och kastar HTTPException(429) när kvoten är slut.
Föredra en autentiserad identitet (API-nyckel eller användar-ID) framför en rå IP-adress, eftersom IP-adresser delas bakom NAT och enkelt kan roteras av botar. Använd IP-adress som reserv endast för anonym trafik.
from fastapi import Request, HTTPException, Depends
def client_identity(request: Request) -> str:
api_key = request.headers.get('x-api-key')
if api_key:
return f'key:{api_key}'
return f'ip:{request.client.host}'
def rate_limit(limit: int, window: int):
async def dependency(request: Request):
ident = client_identity(request)
limiter = request.app.state.limiter
if not await limiter.allow(f'{ident}:{limit}:{window}'):
raise HTTPException(status_code=429, detail='Rate limit exceeded')
return dependency
# @app.get('/search', dependencies=[Depends(rate_limit(30, 60))])
# async def search(): ...Ärliga 429-svar: Retry-After och headers
En korrekt begränsare är också en artig begränsare. Välfungerande klienter respekterar standardsignaler. Genom att skicka dem minskar ni antalet nya försök och supportärenden.
- 429 Too Many Requests är den enda korrekta statusen. Använd aldrig 403 eller 503 för begränsning.
Retry-Afteranger hur många sekunder klienten ska vänta.X-RateLimit-Limit,X-RateLimit-RemainingochX-RateLimit-Resethjälper klienter att själva hålla rätt takt.
Låt även Lua-skriptet returnera det återstående antalet och återställningstiden, så att ni kan fylla i dessa headers utan extra round trips.
from fastapi import Request, HTTPException
async def enforce(request: Request, limit: int, window: int):
limiter = request.app.state.limiter
allowed, remaining, reset_in = await limiter.check(client_identity(request))
if not allowed:
raise HTTPException(
status_code=429,
detail='Rate limit exceeded',
headers={
'Retry-After': str(reset_in),
'X-RateLimit-Limit': str(limit),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': str(reset_in),
},
)Nivåindelade gränser och eskalering vid missbruk
En enda global gräns är trubbig. Produktionssystem kombinerar flera nivåer:
- Gränser per route: en billig
GETtål betydligt mer trafik än en kostsam sök- eller login-endpoint. - Nivåindelade identiteter: anonyma IP-adresser får en snäv kvot, autentiserade användare får mer och betalplaner mest.
- Eskalering: när en klient upprepade gånger får 429 lägger ni till den i en Redis-blocklista med en exponentiell TTL, så att missbrukande botar får allt längre avstängningar.
Hjälpfunktionen nedan beräknar en fördubblad avstängningstid som begränsas till en timme.
def next_ban_seconds(strikes: int) -> int:
base = 60 # 1 minute
cap = 3600 # 1 hour
return min(cap, base * (2 ** strikes))
for s in range(8):
print(f'strike {s}: ban for {next_ban_seconds(s)}s')Upptäck botar genom mer än bara räkning
Hastighetsbegränsning begränsar volymen, men avancerade botar håller sig strax under gränsen. Kombinera begränsning med billiga beteendesignaler:
- Saknade eller skräpiga headers: en saknad
User-Agenteller en UA som finns på en känd blocklista. - Andel misslyckade inloggningar: en hög kvot misslyckade i förhållande till lyckade autentiseringar per IP signalerar credential stuffing.
- Sökvägsentropi: snabba träffar mot många orelaterade endpoints tyder på skrapning.
- Proof of work / CAPTCHA som ett villkor när en poäng passerar en tröskel, i stället för en direkt blockering.
För in dessa signaler i en riskpoäng per klient i Redis och minska token bucket-kapaciteten dynamiskt för klienter med hög risk.
Snabbkontroll: Välj algoritm
Ert publika FastAPI-API körs över många Uvicorn-workers och flera pods. Legitima klienter skickar ibland korta legitima toppar, men ni måste upprätthålla ett stabilt långsiktigt genomsnitt och hålla beslutet konsekvent över alla instanser. Vilken design passar bäst?
Sammanfattning och checklista för produktion
Nu kan ni bygga distribuerad och missbrukstålig trafikbegränsning för FastAPI:
- Centralisera tillståndet i Redis så att alla workers och pods delar en räknare.
- Välj algoritm efter behov: fast tidsfönster för enkla gränser, glidande logg för exakthet och token bucket för att hantera toppar med ett upprätthållet genomsnitt.
- Garantera atomicitet med Lua-skript via
EVALför att undvika race conditions vid läs-ändra-skriv. - Exponera begränsare som FastAPI-beroenden och basera dem i första hand på autentiserad identitet, med IP som reserv.
- Svara ärligt med 429,
Retry-AfterochX-RateLimit-*-headers. - Kombinera försvar: gränser per route och nivå, blocklistor med exponentiell backoff samt beteendebaserad botpoängsättning utöver ren räkning.
Öppna alltid kretsen försiktigt vid fel: om Redis inte kan nås ska ni medvetet avgöra om trafik ska tillåtas eller blockeras och larma om det.
Lär dig Bootcamp i backendutveckling med FastAPI med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 21
- Lektioner
- 84
Vanliga frågor
Är lektionen ”Rate limiting och skydd mot botmissbruk” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Bootcamp i backendutveckling med FastAPI, inklusive ”Rate limiting och skydd mot botmissbruk”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Bootcamp i backendutveckling med FastAPI innehåller totalt 4 lektioner.
Vad lär jag mig i ”Rate limiting och skydd mot botmissbruk”?
Implementera distribuerad rate limiting och throttling med Redis för att hantera toppar och blockera missbrukande klienter. Ni övar på Bootcamp i backendutveckling med FastAPI med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Bootcamp i backendutveckling med FastAPI?
Du behöver inga förkunskaper. Utbildningen i Bootcamp i backendutveckling med FastAPI på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.
Hur lång tid tar lektionen ”Rate limiting och skydd mot botmissbruk”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Bootcamp i backendutveckling med FastAPI-lektionen?
Ja. Varje Bootcamp i backendutveckling med FastAPI-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Minska riskerna med OWASP API Security Top 10
- Rate limiting och skydd mot botmissbruk
- Hantering av hemligheter och nyckelrotation
- CORS, CSP och säkra headerprinciper