前文,我们分享了限流算法中的滑动窗口算法及其实践。尽管滑动窗口算法可以提供一定的限流效果,但它仍然受限于窗口的大小和时间间隔。
在某些情况下,突发流量可能会导致窗口内的请求数超过限制。今天分享的漏桶限流算法,就能很好的改进这个问题。
漏桶限流
算法介绍
漏桶限流算法就是为了更好地平滑请求的流量,改进滑动窗口算法的弊端。算法的原理很简单:它维护一个固定容量的漏桶,请求以不定的速率流入漏桶,而漏桶以固定的速率流出。如果请求到达时,漏桶已满,则会触发拒绝策略。
可以从生产者-消费者模式去理解这个算法,请求充当生产者,每个请求就像一滴水,当请求到达时,它被放入一个队列(漏桶)中。而漏桶则充当消费者,以固定的速率从队列中消费请求,就像从桶底的孔洞中不断漏出水滴。
消费的速率等于限流阈值,例如每秒处理2个请求,即500毫秒消费一个请求。漏桶的容量就像队列的容量,当请求堆积超过指定容量时,会触发拒绝策略,即新到达的请求将被丢弃或延迟处理。算法的实现如下:
代码实现
package main
import (
"fmt"
"time"
)
type LeakyBucket struct {
rate float64 // 漏桶速率,单位请求数/秒
capacity int // 漏桶容量,最多可存储请求数
water int // 当前水量,表示当前漏桶中的请求数
lastLeakMs int64 // 上次漏水的时间戳,单位秒
}
func NewLeakyBucket(rate float64, capacity int) *LeakyBucket {
return &LeakyBucket{
rate: rate,
capacity: capacity,
water: 0,
lastLeakMs: time.Now().Unix(),
}
}
func (lb *LeakyBucket) Allow() bool {
now := time.Now().Unix()
elapsed := now - lb.lastLeakMs
// 漏水,根据时间间隔计算漏掉的水量
leakAmount := int(float64(elapsed) / 1000 * lb.rate)
if leakAmount > 0 {
if leakAmount > lb.water {
lb.water = 0
} else {
lb.water -= leakAmount
}
}
// 判断当前水量是否超过容量
if lb.water > lb.capacity {
lb.water-- // 如果超过容量,减去刚刚增加的水量
return false
}
// 增加水量
lb.water++
lb.lastLeakMs = now
return true
}
func main() {
// 创建一个漏桶,速率为每秒处理3个请求,容量为4个请求
leakyBucket := NewLeakyBucket(3, 4)
// 模拟请求
for i := 1; i <= 15; i++ {
now := time.Now().Format("15:04:05")
if leakyBucket.Allow() {
fmt.Printf(now+" 第 %d 个请求通过\n", i)
} else {
fmt.Printf(now+" 第 %d 个请求被限流\n", i)
}
time.Sleep(200 * time.Millisecond) // 模拟请求间隔
}
}
执行结果:
优缺点
优点
缺点
利弊都给大家分享了!怎么更进一步呢?下期再和你分享一个最常见的思路!