golang限流器

服务限流

在突发的流量下,通过限制用户访问的流量,保证服务能够正常运行

 常见的限流思路

  •  排队
  •  应用场景:秒杀抢购,用户点击抢购之后,进行排队,直到抢到或售罄为止
  •  拒绝
  •  应用场景:除秒杀之外的任何场景

 限流算法

  •  计数器限流算法
  •  漏桶限流算法
  •  令牌桶限流算法

计数器限流算法

  • 在单位时间内进行计数,如果大于设置的最大值,则进行拒绝
  • 如果过了单位时间,则重新进行计数

package main

import (
    "fmt"
    "sync/atomic"
    "time"
)

type CounterLimit struct {
    counter      int64 //计数器
    limit        int64 //指定时间窗口内允许的最大请求数
    intervalNano int64 //指定的时间窗口
    unixNano     int64 //unix时间戳,单位为纳秒
}

func NewCounterLimit(interval time.Duration, limit int64) *CounterLimit {

    return &CounterLimit{
        counter:      0,
        limit:        limit,
        intervalNano: int64(interval),
        unixNano:     time.Now().UnixNano(),
    }
}

func (c *CounterLimit) Allow() bool {

    now := time.Now().UnixNano()
    if now-c.unixNano > c.intervalNano { //如果当前过了当前的时间窗口,则重新进行计数
        atomic.StoreInt64(&c.counter, 0)
        atomic.StoreInt64(&c.unixNano, now)
        return true
    }

    atomic.AddInt64(&c.counter, 1)
    return c.counter < c.limit //判断是否要进行限流
}

func main() {

    limit := NewCounterLimit(time.Second, 100)
    m := make(map[int]bool)
    for i := 0; i < 1000; i++ {
        allow := limit.Allow()
        if allow {
            //fmt.Printf("i=%d is allow
", i)
            m[i] = true
        } else {
            //fmt.Printf("i=%d is not allow
", i)
            m[i] = false
        }
    }

    for i := 0; i < 1000; i++ {
        fmt.Printf("i=%d allow=%v
", i, m[i])
    }
}

计数器限流算法

优点:

  实现非常简单

缺点:

  突发流量会出现毛刺现象

    比如一秒限流100个请求, 前100ms内处理完了100个请求,后900ms时间内没有请求处理

  计数不准确

漏桶限流算法

  • 一个固定大小的水桶
  • 以固定速率流出
  • 水桶满了,则进行溢出(拒绝)

package main

import (
    "fmt"
    "math"
    "time"
)

type BucketLimit struct {
    rate       float64 //漏桶中水的漏出速率
    bucketSize float64 //漏桶最多能装的水大小
    unixNano   int64   //unix时间戳
    curWater   float64 //当前桶里面的水
}

func NewBucketLimit(rate float64, bucketSize int64) *BucketLimit {
    return &BucketLimit{
        bucketSize: float64(bucketSize),
        rate:       rate,
        unixNano:   time.Now().UnixNano(),
        curWater:   0,
    }
}

func (b *BucketLimit) reflesh() {
    now := time.Now().UnixNano()
    //时间差, 把纳秒换成秒
    diffSec := float64(now-b.unixNano) / 1000 / 1000 / 1000
    b.curWater = math.Max(0, b.curWater-diffSec*b.rate)
    b.unixNano = now
    return
}

func (b *BucketLimit) Allow() bool {
    b.reflesh()
    if b.curWater < b.bucketSize {
        b.curWater = b.curWater + 1
        return true
    }

    return false
}

func main() {

    //限速50qps, 桶大小100
    limit := NewBucketLimit(50, 100)
    m := make(map[int]bool)
    for i := 0; i < 1000; i++ {
        allow := limit.Allow()
        if allow {
            m[i] = true
            continue
        }
        m[i] = false
        time.Sleep(time.Millisecond * 10)
    }

    for i := 0; i < 1000; i++ {
        fmt.Printf("i=%d allow=%v
", i, m[i])
    }
}

漏桶限流算法

优点

  • 解决了计数器限流算法的毛刺问题
  • 整体流量控制的比较平稳

缺点

  • 无法应对某些突发的流量

令牌桶限流算法

  • 一个固定大小的水桶
  • 以固定速率放入token
  • 如果能够拿到token则处理,否则拒绝

package main

import (
    "fmt"

    "golang.org/x/time/rate"
)

func main() {

    //限速50qps, 桶大小100
    limit := rate.NewLimiter(50, 100)
    for i := 0; i < 1000; i++ {
        allow := limit.Allow()
        if allow {
            fmt.Printf("i=%d is allow
", i)
            continue
        }
        fmt.Printf("i=%d is not allow
", i)
    }

}

优点

不限制流速, 能够应对突发流量

原文地址:https://www.cnblogs.com/sunlong88/p/13622185.html