脚本如何限制访问频率

wen 实用脚本 32

本文目录导读:

脚本如何限制访问频率

  1. 基于时间窗口的简单计数
  2. 基于滑动日志(Sliding Log)
  3. 令牌桶算法(Token Bucket,最推荐)
  4. IP/用户维度限流(最重要的安全措施)
  5. 实战场景下的分层架构建议
  6. 重要提示
  7. 总结建议

限制访问频率(即限流)通常是为了防止恶意攻击、爬虫滥用或保证服务器稳定运行,实现方式根据场景(服务端/客户端、单机/分布式、全局/用户维度)有所不同。

以下是几种常用的实现脚本(Python示例,但逻辑通用),以及相应的策略:

基于时间窗口的简单计数

这是最直接的思路,用一个计数器记录单位时间内的访问次数,超限则拒绝。

适用场景: 单机、轻量级、对精度要求不高的场景。

伪代码逻辑:

import time
class SimpleRateLimiter:
    def __init__(self, max_requests=10, window_seconds=60):
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self.requests = []  # 存储每个请求的时间戳
    def is_allowed(self):
        now = time.time()
        # 1. 移除窗口之外的旧记录
        self.requests = [t for t in self.requests if now - t < self.window_seconds]
        # 2. 检查当前窗口内的请求数
        if len(self.requests) < self.max_requests:
            self.requests.append(now)
            return True
        else:
            return False
# 使用示例
limiter = SimpleRateLimiter(max_requests=5, window_seconds=10)
# 模拟请求
if limiter.is_allowed():
    print("正常处理")
else:
    print("请求过于频繁,请稍后再试")

缺点: 在窗口边界可能出现突刺(例如第一秒请求完成,下一个窗口刚开始瞬间请求大量涌入)。


基于滑动日志(Sliding Log)

解决窗口边界突刺问题,记录每次请求的时间戳,并计算过去指定时间内的总量。

适用场景: 需要更精确的限流的本地场景。

逻辑: 使用一个有序集合(Sorted Set)或者列表记录所有时间戳,每次请求时,删除掉比当前时间早一个窗口的记录,检查剩余数量,你可以用 Redis 的 ZSET(有序集合)实现分布式版本。

Redis 实现思路(推荐用于分布式):

  • ZADD key timestamp member (member 通常设为唯一 ID 或时间戳本身)
  • ZREMRANGEBYSCORE key -inf (now - window)
  • ZCARD key 获取当前集合大小
  • 如果小于阈值,批准;否则拒绝。

令牌桶算法(Token Bucket,最推荐)

原理: 一个固定容量的桶按固定速率放入令牌,请求需要先消耗一个令牌才能被处理,如果桶空了,请求被拒绝。

优点: 允许有突发流量(桶里可以积累令牌),同时又限制了平均流量(注入速率固定)。

Python 单机实现:

import time
class TokenBucket:
    def __init__(self, rate=1, capacity=10):
        self.rate = rate  # 令牌放入速率(个/秒)
        self.capacity = capacity  # 桶容量
        self.tokens = capacity  # 当前令牌数
        self.last_refill_time = time.time()
    def is_allowed(self):
        now = time.time()
        # 1. 计算这段时间应该产生的令牌数
        elapsed = now - self.last_refill_time
        new_tokens = elapsed * self.rate
        # 2. 补充令牌(不能超过容量)
        self.tokens = min(self.capacity, self.tokens + new_tokens)
        self.last_refill_time = now
        # 3. 消耗令牌
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False
# 示例:每秒允许2个请求,最多积累5个突发令牌
limiter = TokenBucket(rate=2, capacity=5) 

分布式实现: 可以使用 Redis 的 INCR + 过期时间,或者使用 Lua 脚本封装原子操作,更成熟的方案是集成如 Guava RateLimiter(Java)或 Limiter 中间件。


IP/用户维度限流(最重要的安全措施)

通常你需要区分不同来源(IP 或用户 ID),而不是全局限制。

常见策略:

  1. 单 IP 限流: 限制每个 IP 每分钟 60 次。
  2. 用户维度: 限制每个已登录用户每分钟 100 次。

实现方式:

import time
from collections import defaultdict
class IPBasedLimiter:
    def __init__(self, max_requests=100, window_seconds=60):
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self.ip_records = defaultdict(list)  # {ip: [timestamp1, timestamp2]}
    def is_allowed(self, ip):
        now = time.time()
        # 清理该 IP 下的过期记录
        self.ip_records[ip] = [t for t in self.ip_records[ip] if now - t < self.window_seconds]
        if len(self.ip_records[ip]) < self.max_requests:
            self.ip_records[ip].append(now)
            return True
        return False
# 使用示例
limiter = IPBasedLimiter(max_requests=5, window_seconds=10)
def handle_request(request):
    client_ip = request.get('remote_ip', 'unknown')
    if limiter.is_allowed(client_ip):
        print(f"处理来自 {client_ip} 的请求")
    else:
        print(f"拒绝来自 {client_ip} 的请求 (频率过高)")

注意: 如果用户量巨大且在分布式环境下,IP 记录不能存在本地字典,必须存在 Redis 等共享存储里。


实战场景下的分层架构建议

在实际的 WEB 服务或 API 脚本中,建议从近到远设置三层防护

  1. 网关层(最前沿):

    • 使用 Nginxlimit_req_zone 模块或 Cloudflare 的 Rate Limiting。

    • 优点: 性能高,不消耗应用服务器资源。

    • Nginx 配置示例:

      limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
      server {
          location /api/ {
              limit_req zone=one burst=20 nodelay; # 允许突发 20 个请求
          }
      }
  2. 应用层(业务逻辑):

    • 使用 Redis + Lua 脚本实现令牌桶或滑动窗口。
    • 用于处理更复杂的策略(例如按用户 ID 限流、按 API 路径限流)。
  3. 客户端(前端兜底?):

    不做安全上的信任,只用于提升用户体验(如按钮 1 秒内不可重复点击)。

重要提示

  • 拒绝响应码: 当限制频率时,HTTP 最好返回 429 Too Many Requests
  • 返回头部信息: 建议在响应头中包含 X-RateLimit-Limit(总限额)、X-RateLimit-Remaining(剩余次数)、Retry-After(需要等待多少秒后重试),方便客户端做客户端流控。
  • 滑动窗口 vs 固定窗口: 除了 Redis ZSET 实现滑动窗口,也可以用漏桶算法(Leaky Bucket,强制平滑速率)或令牌桶(允许突发)。
  • 分布式锁: 如果使用 Redis 计数器,在并发极高时需要考虑原子操作(可以使用 INCR + EXPIRE,但不同窗口切换时可能有竞态问题,建议使用 Lua 或 Redlock 确保一致性)。

总结建议

  • 简单测试脚本: 使用本地 TokenBucket 类。
  • 生产环境单机应用: 使用 Nginx 限流 + 框架自带的限流中间件(Flask-Limiter 支持 Redis 后端)。
  • 分布式高并发系统: 使用 Redis 滑动窗口(ZSET)或令牌桶(配合 Lua),并结合 Nginx 网关层限流。

抱歉,评论功能暂时关闭!