QPS < 60
单机版
滑动窗口法
思路:为每个用户维护一个请求时间戳队列,每次请求时,从该用户的请求时间戳队列移出时间窗口以外的请求时间戳,然后统计队列剩余个数,如果小于qps,则允许请求并且添加时间戳到队列
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | from time import time
 class UserRateLimiter:
 def __init__(self, user_id, max_qps=60, window_size=1):
 self.user_id = user_id
 self.max_qps = max_qps
 self.window_size = window_size
 self.request_timestamps = []
 
 def allow_request(self):
 
 current_time = time()
 
 
 while self.request_timestamps and \
 self.request_timestamps[0] <= current_time - self.window_size:
 self.request_timestamps.pop(0)
 
 if len(self.request_timestamps) < self.max_qps:
 self.request_timestamps.append(current_time)
 return True
 else:
 return False
 
 
 | 
- 数据结构:用队列存储请求时间戳,实现滑动窗口。
- 时间复杂度:O(n)(可通过二分查找优化到O(log n))。
- 优点:精确控制每秒请求数,无毛刺现象。
分布式版:
令牌桶+漏桶算法:
令牌桶:每个用户维护一个令牌桶,请求需要消耗令牌才能被处理。每次请求时,先计算需要增加的令牌,等于上次请求时间到现在的时间(秒)*qps,但不超过qps=60,然后查看令牌桶有无令牌,没有则拒绝,有则减少一个令牌
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | from time import time
 class TokenBucket:
 def __init__(self, capacity=60, fill_rate=60):
 self.capacity = capacity
 self.tokens = capacity
 self.fill_rate = fill_rate
 self.last_time = time()
 
 def allow_request(self):
 current_time = time()
 elapsed = current_time - self.last_time
 
 
 self.tokens = min(
 self.capacity,
 self.tokens + elapsed * self.fill_rate
 )
 self.last_time = current_time
 
 if self.tokens >= 1:
 self.tokens -= 1
 return True
 return False
 
 | 
令牌桶 vs 漏桶如何选择?
- 选令牌桶:需要容忍突发(如用户快速连续点击)。
- 选漏桶:严格要求匀速(如音频流传输)。
生产实践:
- 多级限流:用户级QPS限制+ 全局API网关限流。
- 黑名单机制
- 软限流:延迟返回