限流算法

文章地址

为什么限流,因为单一用户占用过多资源导致其他用户的服务质量。 好处:

1. 令牌桶

type TokenBucket struct {
	mu         sync.Mutex    // 保护并发访问
	capacity   int           // 桶的最大容量
	tokens     float64       // 当前可用令牌数量(可以是小数,表示部分令牌)
	refillRate float64       // 每秒补充的令牌数
	lastTime   time.Time     // 上次补充令牌的时间
}

func (b *TokenBucket) Allow() bool {
	b.mu.Lock()
	defer b.mu.Unlock()

	now := time.Now()
	elapsed := now.Sub(b.lastTime).Seconds()

	// 根据时间流逝计算应该补充多少令牌
	b.tokens += elapsed * b.refillRate
	if b.tokens > float64(b.capacity) {
		b.tokens = float64(b.capacity)
	}
	b.lastTime = now

	// 如果有足够的令牌,消费一个令牌并允许请求
	if b.tokens >= 1 {
		b.tokens -= 1
		return true
	}
	// 否则拒绝
	return false
}

2. 漏桶

type LeakyBucket struct {
	mu        sync.Mutex      // 并发保护
	capacity  int             // 桶的最大容量(允许排队的请求数)
	interval  time.Duration   // 出水间隔时间(比如每100ms漏一个请求)
	lastLeak  time.Time       // 上次出水时间
	queueSize int             // 当前排队中的请求数量
}

// 这里应该还有一个方法是,将漏桶的请求发送处理
// 但是为了统一就是这样写了
func (b *LeakyBucket) Allow() bool {
	b.mu.Lock()
	defer b.mu.Unlock()

	now := time.Now()
	elapsed := now.Sub(b.lastLeak)

	// 计算漏掉了多少请求
	leaked := int(elapsed / b.interval)
	if leaked > 0 {
		// 移除排队请求
		if leaked > b.queueSize {
			b.queueSize = 0
		} else {
			b.queueSize -= leaked
		}
		b.lastLeak = now
	}

	// 如果还没超出桶的容量,允许请求并加入队列
	if b.queueSize < b.capacity {
		b.queueSize++
		return true
	}
	// 否则拒绝
	return false
}

3. 固定窗口计数器

type FixedWindowLimiter struct {
	mu         sync.Mutex       // 用于并发安全
	limit      int              // 每个时间窗口允许的最大请求数
	windowSize time.Duration    // 时间窗口大小(例如1秒)
	counter    int              // 当前窗口内的请求计数
	windowTime time.Time        // 当前窗口开始的时间
}

func (l *FixedWindowLimiter) Allow() bool {
	l.mu.Lock()
	defer l.mu.Unlock()

	now := time.Now()

	// 如果当前时间已超出窗口范围,重置窗口
	if now.Sub(l.windowTime) > l.windowSize {
		l.windowTime = now
		l.counter = 0
	}

	// 计数未超过上限,允许请求
	if l.counter < l.limit {
		l.counter++
		return true
	}
	// 达到上限,拒绝请求
	return false
}

4. 推拉窗计数器

type SlidingWindowLog struct {
	mu       sync.Mutex       // 并发安全
	limit    int              // 在窗口期内允许的最大请求数
	window   time.Duration    // 滑动窗口大小
	requests []time.Time      // 记录请求时间戳的切片
}

func (l *SlidingWindowLog) Allow() bool {
	l.mu.Lock()
	defer l.mu.Unlock()

	now := time.Now()
	threshold := now.Add(-l.window)

	// 移除窗口外的旧请求记录
	filtered := []time.Time{}
	for _, t := range l.requests {
		if t.After(threshold) {
			filtered = append(filtered, t)
		}
	}
	l.requests = filtered

	// 如果请求数未达到限制,记录当前时间并允许请求
	if len(l.requests) < l.limit {
		l.requests = append(l.requests, now)
		return true
	}
	// 否则拒绝
	return false
}

评论

0 条