前 言

NginxWeb Server

限 流

限流

如 图:

如图上的漫画,在某个时间段流量上来了,服务的接口拜访频率可能会十分快,如果咱们没有对接口拜访频次做限度可能会导致服务器无奈接受过高的压力挂掉,这时候也可能会产生数据失落,所以就要对其进行限流解决。

限流算法就能够帮忙咱们去管制每个接口或程序的函数被调用频率,它有点儿像保险丝,避免零碎因为超过拜访频率或并发量而引起瘫痪。咱们可能在调用某些第三方的接口的时候会看到相似这样的响应头:

X-RateLimit-Limit: 60         //每秒60次申请
X-RateLimit-Remaining: 22     //以后还剩下多少次
X-RateLimit-Reset: 1612184024 //限度重置工夫
HTTP Response

一般来说,限流的罕用解决伎俩有:

  • 计数器
  • 滑动窗口
  • 漏桶
  • 令牌桶

计数器

在一段时间距离内,对申请进行计数,与阀值进行比拟判断是否须要限流,一旦到了工夫临界点,将计数器清零。
count +1count11countcount

代码实现:

package main

import (
    "log"
    "sync"
    "time"
)

type Counter struct {
    rate  int           //计数周期内最多容许的申请数
    begin time.Time     //计数开始工夫
    cycle time.Duration //计数周期
    count int           //计数周期内累计收到的申请数
    lock  sync.Mutex
}

func (l *Counter) Allow() bool {
    l.lock.Lock()
    defer l.lock.Unlock()

    if l.count == l.rate-1 {
        now := time.Now()
        if now.Sub(l.begin) >= l.cycle {
            //速度容许范畴内, 重置计数器
            l.Reset(now)
            return true
        } else {
            return false
        }
    } else {
        //没有达到速率限度,计数加1
        l.count++
        return true
    }
}

func (l *Counter) Set(r int, cycle time.Duration) {
    l.rate = r
    l.begin = time.Now()
    l.cycle = cycle
    l.count = 0
}

func (l *Counter) Reset(t time.Time) {
    l.begin = t
    l.count = 0
}

func main() {
    var wg sync.WaitGroup
    var lr Counter
    lr.Set(3, time.Second) // 1s内最多申请3次
    for i := 0; i < 10; i++ {
        wg.Add(1)
        log.Println("创立申请:", i)
        go func(i int) {
          if lr.Allow() {
              log.Println("响应申请:", i)
          }
          wg.Done()
        }(i)

        time.Sleep(200 * time.Millisecond)
    }
    wg.Wait()
}

OutPut:

2021/02/01 21:16:12 创立申请: 0
2021/02/01 21:16:12 响应申请: 0
2021/02/01 21:16:12 创立申请: 1
2021/02/01 21:16:12 响应申请: 1
2021/02/01 21:16:12 创立申请: 2
2021/02/01 21:16:13 创立申请: 3
2021/02/01 21:16:13 创立申请: 4
2021/02/01 21:16:13 创立申请: 5
2021/02/01 21:16:13 响应申请: 5
2021/02/01 21:16:13 创立申请: 6
2021/02/01 21:16:13 响应申请: 6
2021/02/01 21:16:13 创立申请: 7
2021/02/01 21:16:13 响应申请: 7
2021/02/01 21:16:14 创立申请: 8
2021/02/01 21:16:14 创立申请: 9
200ms132、3、4、8、9
/queryCounter

如下图:

这种办法尽管简略,但也有个大问题就是没有很好的解决单位工夫的边界。

滑动窗口

滑动窗口滑动窗口(Sliding window)TCP滑动窗口

如 图:

一分钟61010Counter0:45+10:40~0:50

那么滑动窗口如何解决咱们下面遇到的问题呢?来看上面的图:

0:59200 +200200
格子的数量影响着滑动窗口算法的精度,仍然有工夫片的概念,无奈基本解决临界点问题
  • 相干算法实现 github.com/RussellLuo/slidingwindow

漏 桶

漏桶算法(Leaky Bucket)

如 图:

处理速度流量整形流量管制

代码实现:

type LeakyBucket struct {
    rate       float64 //固定每秒出水速率
    capacity   float64 //桶的容量
    water      float64 //桶中以后水量
    lastLeakMs int64   //桶上次漏水工夫戳 ms

    lock sync.Mutex
}

func (l *LeakyBucket) Allow() bool {
    l.lock.Lock()
    defer l.lock.Unlock()

    now := time.Now().UnixNano() / 1e6
    eclipse := float64((now - l.lastLeakMs)) * l.rate / 1000 //先执行漏水
    l.water = l.water - eclipse                              //计算残余水量
    l.water = math.Max(0, l.water)                           //桶干了
    l.lastLeakMs = now
    if (l.water + 1) < l.capacity {
        // 尝试加水,并且水还未满
        l.water++
        return true
    } else {
        // 水满,回绝加水
        return false
    }
}

func (l *LeakyBucket) Set(r, c float64) {
    l.rate = r
    l.capacity = c
    l.water = 0
    l.lastLeakMs = time.Now().UnixNano() / 1e6
}

漏桶算法有以下特点:

  • 漏桶具备固定容量,出水速率是固定常量(流出申请)
  • 如果桶是空的,则不需流出水滴
  • 能够以任意速率流入水滴到漏桶(流入申请)
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出(新申请被回绝)

漏桶限度的是常量流出速率(即流出速率是一个固定常量值),所以最大的速率就是出水的速率,不能呈现突发流量。

令牌桶算法

(Token Bucket)(Traffic Shaping)(Rate Limiting)
(token)(rate)

实现代码:

type TokenBucket struct {
    rate         int64 //固定的token放入速率, r/s
    capacity     int64 //桶的容量
    tokens       int64 //桶中以后token数量
    lastTokenSec int64 //桶上次放token的工夫戳 s

    lock sync.Mutex
}

func (l *TokenBucket) Allow() bool {
    l.lock.Lock()
    defer l.lock.Unlock()

    now := time.Now().Unix()
    l.tokens = l.tokens + (now-l.lastTokenSec)*l.rate // 先增加令牌
    if l.tokens > l.capacity {
        l.tokens = l.capacity
    }
    l.lastTokenSec = now
    if l.tokens > 0 {
        // 还有令牌,支付令牌
        l.tokens--
        return true
    } else {
        // 没有令牌,则回绝
        return false
    }
}

func (l *TokenBucket) Set(r, c int64) {
    l.rate = r
    l.capacity = c
    l.tokens = 0
    l.lastTokenSec = time.Now().Unix()
}

令牌桶有以下特点:

BN

令牌桶限度的是均匀流入速率(容许突发申请,只有有令牌就能够解决,反对一次拿3个令牌,4个令牌…),并容许肯定水平突发流量。

小 结

令牌桶