Golang限流技术详解

发表时间: 2024-06-11 16:05

限流与熔断降级

限流的意义

并发系统三大利器: 缓存、降级、限流

缓存: 提升系统访问速度和增大处理容量, 为相应业务增加缓存。

降级: 当服务器压力剧增时, 根据业务策略降级, 以此释放服务资源保证业务正常。

限流: 通过对并发限速, 以达到拒绝服务、排队或等待、降级等处理

限流分类

漏桶限流

每次请求时计算桶流量, 超过阈值则降级请求

令牌桶限流

每次请求时从令牌桶里取令牌, 取不到则降级请求

time/rate 限速器使用(漏桶)

"golang.org/x/time/rate"


rate.NewLimiter(limit, burst)

limit表示每秒产生token数、burst最多存token数

Allow 判断当前是否可以取到token

Wait 阻塞等待直到取到token

Reserve 返回等待时间, 再去取token

【实例】:

package mainimport (    "context"    "golang.org/x/time/rate"    "log"    "testing"    "time")func Test_RateLimiter(t *testing.T) {    l := rate.NewLimiter(1, 5)    log.Println(l.Limit(), l.Burst())    for i := 0; i < 100; i++ {        //阻塞等待直到,取到一个token        log.Println("before Wait")        c, _ := context.WithTimeout(context.Background(), time.Second*2)        if err := l.Wait(c); err != nil {            log.Println("limiter wait err:" + err.Error())        }        log.Println("after Wait")        //返回需要等待多久才有新的token,这样就可以等待指定时间执行任务        r := l.Reserve()        log.Println("reserve Delay:", r.Delay())        //判断当前是否可以取到token        a := l.Allow()        log.Println("Allow:", a)    }}

测试方法:

在文件所在目录中执行:

go test

测试单个文件

go test -v main_test.go

测试单个函数

go test -v main_test.go -test.run Test_RateLimiter

【实例】:

package mainimport (    "fmt"    "sync"    "time"    //"golang.org/x/time/rate")type Bucket struct {    cap int64 // 总容量    lock sync.Mutex // 互斥锁 不需要在初始化中赋值    tokens int64    rate int64 // 速率 每秒产生多少token    lastTime int64 // 记录时间}func (b *Bucket) Allow() bool {    b.lock.Lock()    defer b.lock.Unlock()    now := time.Now().Unix() // 当前时间    // (当前时间 - 上次记录时间)*每秒产生    b.tokens = b.tokens + (now-b.lastTime)*b.rate    if b.tokens > b.cap {        b.tokens = b.cap    }    b.lastTime = now    if b.tokens > 0 {        return true    }    return false}// 添加令牌的操作func (b *Bucket) addToken() {    b.lock.Lock()    defer b.lock.Unlock()    if b.tokens+b.rate <= b.cap { // 4+3>        b.tokens += b.rate    } else {        b.tokens = b.cap    }    b.lastTime = time.Now().Unix() // 当前时间}// 启动一个协程不停的处理func (b *Bucket) start() { // newBucket的时候 启动start 协程    for {        time.Sleep(time.Second)        b.addToken()    }}func main() {    // 方法一:原生使用令牌的方法    /*    // 创建一个每秒产生5个令牌的令牌桶    limiter := rate.NewLimiter(5, 1)    for i := 0; i < 10; i++ {    // 尝试获取一个令牌    if limiter.Allow() {    fmt.Println("处理请求", i)    } else {    fmt.Println("限流请求", i)    }    // 模拟请求处理时间    time.Sleep(time.Second)    }    */    // 方法二: 使用协程    limiter := Bucket{        cap: 10,        tokens: 5,        rate: 1,    }    // 使用协程的方式添加表单令牌    go limiter.start()    for i := 0; i < 10; i++ {        // 尝试获取一个令牌        if limiter.Allow() {            fmt.Println("处理请求", i)        } else {            fmt.Println("限流请求", i)        }        // 模拟请求处理时间        time.Sleep(time.Second)        }}