前文,我们分享了限流算法中的固定窗口限流算法、滑动窗口算法、漏桶限流算法及它们的实践。今天分享一个实践中最常用的限流算法:令牌桶算法。
令牌桶限流
算法介绍
令牌桶算法是实现限流的一种常见思路,用于限制请求的速率。它可以确保系统在高负载情况下仍能提供稳定的服务,并防止突发流量对系统造成过载。
最常用的 Google Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现。令牌桶算法基于一个令牌桶的概念,其中令牌以固定的速率产生,并放入桶中。每个令牌代表一个请求的许可。当请求到达时,需要从令牌桶中获取一个令牌才能通过。如果令牌桶中没有足够的令牌,则请求被限制或丢弃。
令牌桶算法的实现步骤如下:
代码实现
package main
import (
"fmt"
"sync"
"time"
)
// TokenBucket 表示一个令牌桶。
type TokenBucket struct {
rate float64 // 令牌添加到桶中的速率。
capacity float64 // 桶的最大容量。
tokens float64 // 当前桶中的令牌数量。
lastUpdate time.Time // 上次更新令牌数量的时间。
mu sync.Mutex // 互斥锁,确保线程安全。
}
// NewTokenBucket 创建一个新的令牌桶,给定令牌添加速率和桶的容量。
func NewTokenBucket(rate float64, capacity float64) *TokenBucket {
return &TokenBucket{
rate: rate,
capacity: capacity,
tokens: capacity, // 初始时,桶是满的。
lastUpdate: time.Now(),
}
}
// Allow 检查是否可以从桶中移除一个令牌。如果可以,它移除一个令牌并返回 true。
// 如果不可以,它返回 false。
func (tb *TokenBucket) Allow() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
// 根据经过的时间计算要添加的令牌数量。
now := time.Now()
elapsed := now.Sub(tb.lastUpdate).Seconds()
tokensToAdd := elapsed * tb.rate
tb.tokens += tokensToAdd
if tb.tokens > tb.capacity {
tb.tokens = tb.capacity // 确保令牌数量不超过桶的容量。
}
if tb.tokens >= 1.0 {
tb.tokens--
tb.lastUpdate = now
return true
}
return false
}
func main() {
tokenBucket := NewTokenBucket(2.0, 3.0)
for i := 1; i <= 10; i++ {
now := time.Now().Format("15:04:05")
if tokenBucket.Allow() {
fmt.Printf(now+" 第 %d 个请求通过\n", i)
} else { // 如果不能移除一个令牌,请求被拒绝。
fmt.Printf(now+" 第 %d 个请求被限流\n", i)
}
time.Sleep(200 * time.Millisecond)
}
}
执行结果:
优缺点
优点
缺点
四种基本算法的对比
结合之前固定窗口限流算法、滑动窗口算法、漏桶限流算法,这里把这几个算法做个对比和总结如下。
算法 | 优点 | 缺点 | 适合场景 |
---|---|---|---|
固定窗口 | 简单直观,易于实现适用于稳定的流量控制易于实现速率控制 | 无法应对短时间内的突发流量流量不均匀时可能导致突发流量 | 稳定的流量控制,不需要保证请求均匀分布的场景 |
滑动窗口 | 平滑处理突发流量颗粒度更小,可以提供更精确的限流控制 | 实现相对复杂需要维护滑动窗口的状态存在较高的内存消耗 | 需要平滑处理突发流量的场景 |
漏桶算法 | 平滑处理突发流量可以固定输出速率,有效防止过载 | 对突发流量的处理不够灵活无法应对流量波动的情况 | 需要固定输出速率的场景避免流量的突然增大对系统的冲击的场景 |
令牌桶 | 平滑处理突发流量可以动态调整限流规则能适应不同时间段的流量变化 | 实现相对复杂需要维护令牌桶的状态 | 需要动态调整限流规则的场景需要平滑处理突发流量的场景 |
结各个算法的优缺点、适用场景上表都说得很清楚了。后续大家在项目中根据情况按需使用就好。
但以上不是限流算法的全部,随着微服务架构的普及,系统的服务通常会部署在多台服务器上,此时就需要分布式限流来保证整个系统的稳定性。后续我们将继续分享分布式限流的技术方案。