分布式限流
Vistack 内置两级限流:单机令牌桶 + 分布式滑动窗口,通过配置一键切换,登录后接口按用户 ID 限流。
设计目标
text
用户请求 ──▶ 限流中间件(按 user_id 生成 key)
├─ 令牌桶(单机:进程内内存,低延迟)
└─ 滑动窗口(分布式:Redis Lua,跨实例共享)
▼
放行 / 拒绝(429 + Retry-After + X-RateLimit-*)算法一:令牌桶(单机)
适用场景:单实例部署或进程内限流,零网络开销。
go
// internal/middlewares/ratelimit/token_bucket.go
type TokenBucket struct {
mu sync.Mutex
rate float64 // 每秒补充令牌数
burst float64 // 桶容量(突发上限)
tokens float64
lastTime time.Time
}- 经典漏桶/令牌桶实现:按
rate补充令牌,桶容量burst允许突发; - 基于
time差值补充,不依赖定时器; - 线程安全(
sync.Mutex)。
算法二:滑动窗口(分布式)
适用场景:多实例部署,需要跨实例共享窗口。基于 Redis ZSet + Lua 脚本原子执行。
lua
-- 原子:清理窗口外旧记录 → 计数 → 未超限则写入
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, now - window)
local count = redis.call('ZCARD', KEYS[1])
if count < limit then
redis.call('ZADD', KEYS[1], now, ARGV[4]) -- member 用 UUID 防同毫秒去重
redis.call('PEXPIRE', KEYS[1], window)
return {1, limit - count - 1, now + window} -- {allowed, remaining, reset_at}
end
local oldest = redis.call('ZRANGE', KEYS[1], 0, 0, 'WITHSCORES')
local resetAt = now + window
if oldest[2] then
resetAt = tonumber(oldest[2]) + window -- 最早记录过期时间 = 可再放行时间
end
return {0, 0, resetAt}实现要点:
- ZSet 记录每次请求的时间戳,member 用
uuid.NewString()保证全局唯一(防止同毫秒多请求被 ZSet 去重); - ZREMRANGEBYSCORE 清理窗口外旧记录,窗口内计数即
ZCARD; - 整个判断 + 写入在一个 Lua 脚本内原子完成,无竞态;
- 返回
{allowed, remaining, reset_at},支撑标准限流响应头。
中间件行为
go
// internal/middlewares/ratelimit.go
type Result struct {
Allowed bool
Limit int
Remaining int
ResetAt time.Time
}放行/拒绝时返回标准响应头:
| 响应 | 说明 |
|---|---|
200 | 放行,带 X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset |
429 Too Many Requests | 拒绝,带 Retry-After(秒)+ X-RateLimit-* 头 |
fail-open 策略:Redis 不可用时,分布式限流降级为直接放行(记录错误日志),保证业务可用性优先。
限流 key 设计
text
vistack:ratelimit:{user_id} # 滑动窗口 key 前缀登录后的接口按 user_id 限流,未登录接口可配置全局 key。
配置
toml
[ratelimit]
enabled = true
algorithm = "sliding_window" # token_bucket | sliding_window
token_rate = 10 # 令牌桶:每秒补充令牌数
token_burst = 20 # 令牌桶:桶容量
window = 60 # 滑动窗口:窗口时长(秒)
limit = 100 # 滑动窗口:窗口内最大请求数单测覆盖
internal/middlewares/ratelimit/ratelimit_test.go 覆盖:
- 令牌桶按速率补充、突发上限、并发安全;
- 滑动窗口窗口滑动、超限拒绝、ResetAt 计算;
- 中间件 429 响应与响应头。
演进方向
- 播放上报 IP 限流(防匿名刷量);
- 分布式限流阈值动态配置(etcd 配置中心热更新)。
