在 Golang 中实现用户级别(例如按 IP 或用户 ID)的限流,常用的算法包括滑动窗口、令牌桶以及基于 Redis 的分布式方案。下面将详细介绍四种实现方式,并提供可运行的示例代码和选型建议。
方案1:使用滑动窗口(推荐用于单机精确限流)
滑动窗口通过记录每个请求的时间戳,统计当前时间窗口内的请求数量,精度高且能避免临界问题。
原理
- 为每个用户维护一个请求时间戳队列(如 slice)。
- 每次请求时,将当前时间戳加入队列,并移除所有早于
当前时间 - 窗口大小的时间戳。 - 检查剩余数量是否超过阈值(1000)。
实现示例
package main
import (
"sync"
"time"
)
type SlideWindowLimiter struct {
windowSize time.Duration // 窗口大小,如1分钟
maxRequest int // 最大请求数,如1000
requests map[string][]time.Time
mu sync.Mutex
}
func NewSlideWindowLimiter(windowSize time.Duration, maxRequest int) *SlideWindowLimiter {
return &SlideWindowLimiter{
windowSize: windowSize,
maxRequest: maxRequest,
requests: make(map[string][]time.Time),
}
}
// Allow 判断某个用户是否允许请求
func (l *SlideWindowLimiter) Allow(userID string) bool {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
cutoff := now.Add(-l.windowSize)
// 获取该用户的请求时间戳列表
times, exists := l.requests[userID]
if !exists {
times = []time.Time{}
}
// 过滤掉窗口之外的时间戳
valid := make([]time.Time, 0, len(times))
for _, t := range times {
if t.After(cutoff) {
valid = append(valid, t)
}
}
// 判断是否超过限制
if len(valid) >= l.maxRequest {
l.requests[userID] = valid // 保存过滤后的列表(虽然超限,但保留用于下次判断)
return false
}
// 允许请求,加入当前时间戳
valid = append(valid, now)
l.requests[userID] = valid
return true
}
优缺点
- 优点:精确控制窗口内请求数,无突刺问题。
- 缺点:每个用户需要维护时间戳列表,内存占用随请求频率增加;需要自行处理过期数据清理。
方案2:使用令牌桶算法
令牌桶允许一定程度的突发流量,适合流量波动的场景。
原理
- 桶容量为 1000,按固定速率(每秒 1000/60 ≈ 16.67 个)添加令牌。
- 请求到来时从桶中取走一个令牌,若桶空则拒绝。
手动实现示例
package main
import (
"sync"
"time"
)
type TokenBucket struct {
capacity int // 桶容量
rate float64 // 令牌生成速率(个/秒)
tokens float64 // 当前令牌数
lastRefill time.Time // 上次填充时间
mu sync.Mutex
}
func NewTokenBucket(capacity int, rate float64) *TokenBucket {
return &TokenBucket{
capacity: capacity,
rate: rate,
tokens: float64(capacity),
lastRefill: time.Now(),
}
}
func (tb *TokenBucket) refill() {
now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
newTokens := elapsed * tb.rate
tb.tokens = min(float64(tb.capacity), tb.tokens+newTokens)
tb.lastRefill = now
}
func (tb *TokenBucket) Allow() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
tb.refill()
if tb.tokens >= 1 {
tb.tokens--
return true
}
return false
}
func min(a, b float64) float64 {
if a < b {
return a
}
return b
}
使用时需为每个用户维护一个 TokenBucket 实例。
方案3:使用 golang.org/x/time/rate 包(官方限流器)
该包基于令牌桶算法,使用方便且性能优秀,适合单机限流。
安装
go get golang.org/x/time/rate</


619

被折叠的 条评论
为什么被折叠?



