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

前言:

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

令牌桶算法:

原理:

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

你可能感兴趣的:(漏水桶和令牌桶的限流算法)