漏水桶和令牌桶的限流算法

924 阅读2分钟

前言:

服务上线后,我们一般会对自己 服务 有个 预估,这个 服务能够 承受多少请求,当单位时间内请求数过高超过我们 预估 的 阀值,我们就应该 拒绝多余的请求。我们 一般会用 漏水桶 和令牌桶算法来实现 以上逻辑。

令牌桶算法:

原理:

一个水桶按照一定速率往桶里放令牌,每一个请求进来 都会去水桶里面尝试 获取 令牌,如果 没有获取到 就 拒绝 该请求。水桶有一定的容量,最多只能放 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
}

总结:

  1. 多方资料都说令牌桶 要比 漏水桶 好,因为 令牌桶 在 限流的同时还允许 在短时间内的 合理并发(我个人觉得其实差不多, 漏水桶 从空桶 激增到 溢出 不也并发么)
  2. 这个代码只是学习,线上生产还应该考虑到原子操作等.
  3. 可以 用redis 配合 lua 来做这块逻辑