令牌桶(Token Bucket)

想象有一个木桶,系统按照固定速率,例如10ms每次,往桶里加入Token,如果桶已经满了就不再添加。新请求来临时,会各自拿走一个Token,如果没有Token 就拒绝服务。这里如果一段时间没有请求时,桶内就会积累一些token,下次一旦有突发流量,只要token足够,也能一次处理。

总结下令牌桶算法的特点,令牌桶即可以控制进入系统的请求请求量,同时允许突发流量。

在秒杀活动中,用户的请求速率是不固定的,这里我们假定为10r/s,令牌按照5个每秒的速率放入令牌桶,桶中最多存放20个令牌,那系统就只会允许持续的每秒处理5 个请求,或者每隔4 秒,等桶中20 个令牌攒满后,一次处理20个请求的突发情况,保证系统稳定性。

go简易版实现

package main

import (
    "fmt"
    "time"
)

type Bucket struct {
    max   int
    ch    chan int
    timer *time.Ticker
}

func NewBucket(n int, sec time.Duration) *Bucket {
    return &Bucket{
        max:   n,
        ch:    make(chan int, n),
        timer: time.NewTicker(sec),
    }
}

func (b *Bucket) Get() bool {
    select {
    case <-b.ch:
        return true
    default:
        return false
    }
}

func (b *Bucket) Ticker() {
    for i := 0; i < b.max; i++ {
        b.ch <- 1
    }
    for {
        select {
        case <-b.timer.C:
            fmt.Println("len: ", len(b.ch))
            for i := len(b.ch); i < b.max; i++ {
                b.ch <- 1
            }
        }
    }
}

func main() {
    bucket := NewBucket(10, 3*time.Second)
    go bucket.Ticker()
    for i := 0; i < 6; i++ {
        go func(b *Bucket, id int) {
            for {
                if b.Get() {
                    fmt.Println("ok: ", id)
                } else {
                    fmt.Println("no: ", id)
                }
                time.Sleep(2 * time.Second)
            }
        }(bucket, i)
    }
    for {
        
    }
}

推荐阅读更多精彩内容