// ratelimit.go:每 key 进程内令牌桶(rpm,design-arch §2.3)。 // gateway 无状态多副本部署时为「每副本」限流,接受该近似(无 Redis 约束)。 package gateway import ( "sync" "time" ) // tokenBucket 单 key 令牌桶。 type tokenBucket struct { tokens float64 last time.Time rpm int capacity float64 } // RateLimiter 每 key rpm 令牌桶(进程内)。 type RateLimiter struct { mu sync.Mutex buckets map[int64]*tokenBucket now func() time.Time } // NewRateLimiter 构造。 func NewRateLimiter() *RateLimiter { return &RateLimiter{buckets: map[int64]*tokenBucket{}, now: time.Now} } // Allow 判定 key 是否可放行;返回 (是否放行, 剩余令牌, 重置秒数)。 // rpm<=0 视为不限流(放行)。 func (r *RateLimiter) Allow(keyID int64, rpm int) (bool, int, int) { if rpm <= 0 { return true, 0, 0 } r.mu.Lock() defer r.mu.Unlock() now := r.now() b, ok := r.buckets[keyID] if !ok || b.rpm != rpm { b = &tokenBucket{tokens: float64(rpm), rpm: rpm, capacity: float64(rpm), last: now} r.buckets[keyID] = b } // 补充令牌:速率 rpm/60 每秒 elapsed := now.Sub(b.last).Seconds() b.last = now b.tokens += elapsed * float64(rpm) / 60.0 if b.tokens > b.capacity { b.tokens = b.capacity } if b.tokens < 1 { // 需要等多少秒才凑满 1 个令牌 need := (1 - b.tokens) / (float64(rpm) / 60.0) return false, 0, int(need) + 1 } b.tokens-- remaining := int(b.tokens) reset := int((b.capacity - b.tokens) / (float64(rpm) / 60.0)) return true, remaining, reset }