欢迎光临
我们一直在努力

Python API限流策略:保护你的服务免受滥用

Python API限流策略:保护你的服务免受滥用

引言

在高并发的API服务中,限流是保障系统稳定性的关键措施。当请求量超过系统处理能力时,限流可以有效保护服务不被压垮,确保公平使用。作为一名从Python转向Rust的后端开发者,我在实践中总结了多种限流策略的实现方式。本文将深入探讨Python中API限流的设计与实现,帮助你构建健壮的限流系统。

一、限流核心概念

1.1 什么是限流

限流(Rate Limiting)是一种控制系统资源使用的技术,通过限制单位时间内的请求数量来保护服务。

1.2 限流的作用

  • 防止服务过载:保护后端服务不被大量请求压垮
  • 公平使用:确保每个用户都能公平访问资源
  • 防止恶意攻击:抵御DoS/DDoS攻击
  • 资源分配:合理分配系统资源

1.3 常见限流算法对比

算法特点适用场景
固定窗口计数器 简单易实现,可能出现突发流量 对精度要求不高的场景
滑动窗口计数器 精度较高,实现稍复杂 需要平滑限流的场景
令牌桶 支持突发流量,平滑输出 大多数场景
漏桶 严格控制流量速率 流量整形场景

二、限流算法实现

2.1 固定窗口计数器

import time
from collections import defaultdict

class FixedWindowCounter:
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.counters = defaultdict(int)
self.windows = {}

def is_allowed(self, key: str) -> bool:
now = time.time()
window_key = int(now // self.window_seconds)

if self.windows.get(key) != window_key:
self.counters[key] = 0
self.windows[key] = window_key

if self.counters[key] < self.max_requests:
self.counters[key] += 1
return True

return False

def get_remaining(self, key: str) -> int:
now = time.time()
window_key = int(now // self.window_seconds)

if self.windows.get(key) != window_key:
return self.max_requests

return self.max_requests – self.counters[key]

2.2 滑动窗口计数器

from collections import deque

class SlidingWindowCounter:
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.timestamps = defaultdict(deque)

def is_allowed(self, key: str) -> bool:
now = time.time()
window_start = now – self.window_seconds

timestamps = self.timestamps[key]

while timestamps and timestamps[0] < window_start:
timestamps.popleft()

if len(timestamps) < self.max_requests:
timestamps.append(now)
return True

return False

def get_remaining(self, key: str) -> int:
now = time.time()
window_start = now – self.window_seconds

timestamps = self.timestamps[key]
while timestamps and timestamps[0] < window_start:
timestamps.popleft()

return self.max_requests – len(timestamps)

2.3 令牌桶算法

class TokenBucket:
def __init__(self, capacity: int, rate: float):
self.capacity = capacity
self.rate = rate
self.tokens = capacity
self.last_refill = time.time()

def _refill(self):
now = time.time()
elapsed = now – self.last_refill
new_tokens = elapsed * self.rate

self.tokens = min(self.capacity, self.tokens + new_tokens)
self.last_refill = now

def is_allowed(self, tokens: int = 1) -> bool:
self._refill()

if self.tokens >= tokens:
self.tokens -= tokens
return True

return False

def get_remaining(self) -> float:
self._refill()
return self.tokens

2.4 漏桶算法

class LeakyBucket:
def __init__(self, capacity: int, rate: float):
self.capacity = capacity
self.rate = rate
self.queue = deque()
self.last_leak = time.time()

def _leak(self):
now = time.time()
elapsed = now – self.last_leak

leaks = int(elapsed * self.rate)
for _ in range(leaks):
if self.queue:
self.queue.popleft()

self.last_leak = now

def is_allowed(self) -> bool:
self._leak()

if len(self.queue) < self.capacity:
self.queue.append(time.time())
return True

return False

def get_remaining(self) -> int:
self._leak()
return self.capacity – len(self.queue)

三、分布式限流实现

3.1 基于Redis的分布式限流

import redis
import json

class RedisRateLimiter:
def __init__(self, redis_client: redis.Redis):
self.redis_client = redis_client

def fixed_window_limit(self, key: str, max_requests: int, window_seconds: int) -> bool:
window_key = f"rate_limit:{key}:{int(time.time() // window_seconds)}"

count = self.redis_client.incr(window_key)
if count == 1:
self.redis_client.expire(window_key, window_seconds)

return count <= max_requests

def sliding_window_limit(self, key: str, max_requests: int, window_seconds: int) -> bool:
now = time.time()
window_start = now – window_seconds

pipeline = self.redis_client.pipeline()
pipeline.zadd(f"rate_limit:{key}", {now: now})
pipeline.zremrangebyscore(f"rate_limit:{key}", 0, window_start)
pipeline.zcard(f"rate_limit:{key}")
pipeline.expire(f"rate_limit:{key}", window_seconds)

results = pipeline.execute()
count = results[2]

return count <= max_requests

def token_bucket_limit(self, key: str, capacity: int, rate: float) -> bool:
now = time.time()
token_key = f"token_bucket:{key}"

pipeline = self.redis_client.pipeline()
pipeline.get(token_key)

results = pipeline.execute()
stored = results[0]

if stored:
data = json.loads(stored)
last_refill = data['last_refill']
tokens = data['tokens']

elapsed = now – last_refill
new_tokens = elapsed * rate
tokens = min(capacity, tokens + new_tokens)
else:
tokens = capacity
last_refill = now

if tokens >= 1:
tokens -= 1
self.redis_client.set(token_key, json.dumps({
'tokens': tokens,
'last_refill': now
}))
return True

return False

四、限流中间件实现

4.1 Flask限流中间件

from flask import Flask, request, jsonify
from functools import wraps

app = Flask(__name__)
rate_limiter = RedisRateLimiter(redis.Redis())

def rate_limit(max_requests: int, window_seconds: int):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
client_id = request.remote_addr

if not rate_limiter.fixed_window_limit(client_id, max_requests, window_seconds):
return jsonify({
'error': 'Rate limit exceeded',
'retry_after': window_seconds
}), 429

return f(*args, **kwargs)
return decorated_function
return decorator

@app.route('/api/data')
@rate_limit(max_requests=100, window_seconds=60)
def get_data():
return jsonify({'data': 'Hello, World!'})

4.2 FastAPI限流中间件

from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.base import BaseHTTPMiddleware

app = FastAPI()
rate_limiter = RedisRateLimiter(redis.Redis())

class RateLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, max_requests: int, window_seconds: int):
super().__init__(app)
self.max_requests = max_requests
self.window_seconds = window_seconds

async def dispatch(self, request: Request, call_next):
client_id = request.client.host

if not rate_limiter.fixed_window_limit(client_id, self.max_requests, self.window_seconds):
raise HTTPException(
status_code=429,
detail={
'error': 'Rate limit exceeded',
'retry_after': self.window_seconds
}
)

response = await call_next(request)
return response

app.add_middleware(RateLimitMiddleware, max_requests=100, window_seconds=60)

@app.get('/api/data')
async def get_data():
return {'data': 'Hello, World!'}

五、多级限流策略

5.1 多层限流架构

class MultiLevelRateLimiter:
def __init__(self, redis_client: redis.Redis):
self.redis_client = redis_client
self.limiters = {
'ip': RedisRateLimiter(redis_client),
'user': RedisRateLimiter(redis_client),
'global': RedisRateLimiter(redis_client)
}

def check_all_limits(self, ip: str, user_id: str = None) -> bool:
checks = [
self.limiters['ip'].fixed_window_limit(f"ip:{ip}", 100, 60),
self.limiters['global'].fixed_window_limit("global", 1000, 60)
]

if user_id:
checks.append(
self.limiters['user'].fixed_window_limit(f"user:{user_id}", 50, 60)
)

return all(checks)

5.2 动态限流策略

class DynamicRateLimiter:
def __init__(self, redis_client: redis.Redis):
self.redis_client = redis_client
self.base_limit = 100
self.min_limit = 10
self.max_limit = 1000

def get_dynamic_limit(self) -> int:
cpu_usage = self._get_cpu_usage()
memory_usage = self._get_memory_usage()

load_factor = 1.0

if cpu_usage > 80:
load_factor *= 0.5
elif cpu_usage > 60:
load_factor *= 0.8

if memory_usage > 80:
load_factor *= 0.5
elif memory_usage > 60:
load_factor *= 0.8

return max(self.min_limit, min(self.max_limit, int(self.base_limit * load_factor)))

def is_allowed(self, key: str) -> bool:
limit = self.get_dynamic_limit()
return self.redis_client.incr(key) <= limit

def _get_cpu_usage(self) -> float:
return 40.0

def _get_memory_usage(self) -> float:
return 50.0

六、实际业务场景应用

6.1 API网关限流

class APIGatewayRateLimiter:
def __init__(self, redis_client: redis.Redis):
self.redis_client = redis_client
self.rules = {
'/api/v1/users': {'max_requests': 100, 'window_seconds': 60},
'/api/v1/orders': {'max_requests': 50, 'window_seconds': 60},
'/api/v1/payments': {'max_requests': 20, 'window_seconds': 60}
}

def check_rate_limit(self, endpoint: str, client_id: str) -> bool:
if endpoint not in self.rules:
return True

rule = self.rules[endpoint]
key = f"rate_limit:{endpoint}:{client_id}"

return self.redis_client.incr(key) <= rule['max_requests']

def set_rule(self, endpoint: str, max_requests: int, window_seconds: int):
self.rules[endpoint] = {
'max_requests': max_requests,
'window_seconds': window_seconds
}

6.2 用户级限流

class UserRateLimiter:
def __init__(self, redis_client: redis.Redis):
self.redis_client = redis_client

def check_user_limit(self, user_id: str, limit_type: str) -> bool:
limits = {
'daily': {'max_requests': 1000, 'window_seconds': 86400},
'hourly': {'max_requests': 100, 'window_seconds': 3600},
'minute': {'max_requests': 10, 'window_seconds': 60}
}

if limit_type not in limits:
return True

rule = limits[limit_type]
key = f"user_limit:{user_id}:{limit_type}"

return self.redis_client.incr(key) <= rule['max_requests']

七、限流监控与告警

7.1 限流监控系统

class RateLimitMonitor:
def __init__(self, redis_client: redis.Redis):
self.redis_client = redis_client

def get_rate_limit_stats(self, pattern: str = '*') -> dict:
stats = {}

for key in self.redis_client.keys(f"rate_limit:{pattern}"):
key_str = key.decode('utf-8')
count = int(self.redis_client.get(key) or 0)
ttl = self.redis_client.ttl(key)

stats[key_str] = {
'count': count,
'ttl': ttl
}

return stats

def get_top_limiters(self, limit: int = 10) -> list:
stats = self.get_rate_limit_stats()
sorted_stats = sorted(stats.items(), key=lambda x: x[1]['count'], reverse=True)

return sorted_stats[:limit]

def check_anomalies(self, threshold: int = 90) -> list:
anomalies = []
stats = self.get_rate_limit_stats()

for key, info in stats.items():
percentage = (info['count'] / self._get_limit(key)) * 100
if percentage > threshold:
anomalies.append({
'key': key,
'percentage': percentage,
'count': info['count']
})

return anomalies

7.2 限流告警系统

class RateLimitAlertSystem:
def __init__(self, monitor: RateLimitMonitor):
self.monitor = monitor
self.alert_threshold = 90
self.alert_history = []

def check_and_alert(self):
anomalies = self.monitor.check_anomalies(self.alert_threshold)

for anomaly in anomalies:
if anomaly['key'] not in self.alert_history:
self._send_alert(anomaly)
self.alert_history.append(anomaly['key'])

self._clean_alert_history()

def _send_alert(self, anomaly: dict):
print(f"ALERT: Rate limit approaching for {anomaly['key']} "
f"- {anomaly['percentage']:.2f}% utilized")

def _clean_alert_history(self):
if len(self.alert_history) > 100:
self.alert_history = self.alert_history[-100:]

八、性能优化策略

8.1 本地缓存优化

class CachedRateLimiter:
def __init__(self, redis_client: redis.Redis, local_cache_size: int = 1000):
self.redis_client = redis_client
self.local_cache = {}
self.local_cache_size = local_cache_size

def is_allowed(self, key: str, max_requests: int, window_seconds: int) -> bool:
if key in self.local_cache:
entry = self.local_cache[key]
if entry['window'] == int(time.time() // window_seconds):
if entry['count'] < max_requests:
entry['count'] += 1
return True
return False

result = self._redis_check(key, max_requests, window_seconds)

if result:
if len(self.local_cache) >= self.local_cache_size:
oldest_key = next(iter(self.local_cache))
del self.local_cache[oldest_key]

self.local_cache[key] = {
'count': 1,
'window': int(time.time() // window_seconds)
}

return result

def _redis_check(self, key: str, max_requests: int, window_seconds: int) -> bool:
window_key = f"rate_limit:{key}:{int(time.time() // window_seconds)}"

count = self.redis_client.incr(window_key)
if count == 1:
self.redis_client.expire(window_key, window_seconds)

return count <= max_requests

8.2 批量操作优化

class BatchRateLimiter:
def __init__(self, redis_client: redis.Redis):
self.redis_client = redis_client

def check_batch_limits(self, keys: list, max_requests: int, window_seconds: int) -> dict:
now = time.time()
window = int(now // window_seconds)

pipeline = self.redis_client.pipeline()
window_keys = [f"rate_limit:{key}:{window}" for key in keys]

for window_key in window_keys:
pipeline.incr(window_key)
pipeline.expire(window_key, window_seconds)

results = pipeline.execute()
counts = results[::2]

return {key: count <= max_requests for key, count in zip(keys, counts)}

总结

API限流是构建高可用系统的关键技术。通过本文的学习,你应该掌握了以下核心要点:

  • 限流算法:固定窗口、滑动窗口、令牌桶、漏桶
  • 分布式限流:基于Redis的实现
  • 限流中间件:Flask、FastAPI中间件
  • 多级限流:IP级、用户级、全局级限流
  • 动态限流:根据系统负载动态调整限流参数
  • 监控告警:限流监控与异常告警
  • 性能优化:本地缓存、批量操作
  • 作为从Python转向Rust的后端开发者,掌握限流策略对于构建健壮的API服务至关重要。后续文章将探讨如何在Rust中实现高性能限流系统。

    赞(0)
    未经允许不得转载:171主机测评 » Python API限流策略:保护你的服务免受滥用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址