前言:
服务上线后,我们一般会对自己 服务 有个 预估,这个 服务能够 承受多少请求,当单位时间内请求数过高超过我们 预估 的 阀值,我们就应该 拒绝多余的请求。我们 一般会用 漏水桶 和令牌桶算法来实现 以上逻辑。
令牌桶算法:
原理:
一个水桶按照一定速率往桶里放令牌,每一个请求进来 都会去水桶里面尝试 获取 令牌,如果 没有获取到 就 拒绝 该请求。水桶有一定的容量,最多只能放 n个 令牌,当令牌 数量过多就会 溢出。
代码实现:
var (
lastReqTime = time.Now().Unix() //上个请求 的时间戳
lastTokenNum int64 = 1 //上次请求时剩余的令牌
tokenRate int64 = 1 //每秒增加 1 个 token
bucketCap int64 = 100 //令牌桶的总量
)
func tokenLimiter() bool {
nowT := time.Now().Unix() //当前时间
//(当前时间 减去 上次请求时间 ) 乘 速度 = 这段时间 生产的 令牌数 加上 原来的 令牌数 判断 令牌 数是否 大于 桶的 容量 ,如果 大于 就取桶容量
nowTokenNum := int64(math.Min(float64((nowT-lastReqTime)*tokenRate+lastTokenNum), float64(bucketCap))) //现在令牌还有的数量
if nowTokenNum > 0 {
lastReqTime = nowT
lastTokenNum--
return true
}
return false
}
漏水桶算法:
原理:
一个水桶下面有个洞,会按照一定的速度漏水,每次一个请求过来 就相当于 往水桶里面加一滴水 ,如果 当前 水桶 满了 ,请求 就会溢出,溢出的水就相当于 是 被拒绝的请求。
代码实现:(没有恒定速率消费,代码有问题)
var (
lastReqTime = time.Now().Unix() //上次请求时间
lastReqNum int64 = 10 //上次水桶剩余的数量
leakyRate int64 = 1 //漏水速度每秒漏一滴
bucketCap int64 = 100 //令牌桶的总容量
)
func leakyLimiter() bool {
nowT := time.Now().Unix() //当前时间
//当前水量 = (上次剩余的水量 - 这段时间流去的水量) , 当前水量 最小为 0
nowReqNum := int64(math.Max(float64(lastReqNum-(nowT-lastReqTime)*leakyRate), 0))
//如果当前 水量已经比桶的大了 ,就直接返回 false 说明 水溢出了
if nowReqNum > bucketCap {
return false
}
lastReqNum++
lastReqTime = nowT
return true
}
总结:
- 多方资料都说令牌桶 要比 漏水桶 好,因为 令牌桶 在 限流的同时还允许 在短时间内的 合理并发(我个人觉得其实差不多, 漏水桶 从空桶 激增到 溢出 不也并发么)
- 这个代码只是学习,线上生产还应该考虑到原子操作等.
- 可以 用redis 配合 lua 来做这块逻辑