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\n", i)
            m[i] = true
        } else {
            //fmt.Printf("i=%d is not allow\n", i)
            m[i] = false
        }
    }

    for i := 0; i < 1000; i++ {
        fmt.Printf("i=%d allow=%v\n", 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\n", 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\n", i)
            continue
        }
        fmt.Printf("i=%d is not allow\n", i)
    }

}

 

優點

不限制流速, 能夠應對突發流量

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM