Go 基于令牌桶实现的官方限流器实际使用

1 篇文章 0 订阅
1 篇文章 0 订阅

最近项目需要对新增做限流,就使用到了go官方提供的以令牌桶算法实现的限流器的使用,找了许多博文资料,自己亲测成功有效。主要导官方工具包

"golang.org/x/time/rate"

一:先附上代码

1.抽出关键代码以工具包形式避免代码耦合度过高

package limiter

import (
	"golang.org/x/time/rate"
	"sync"
	"time"
)

type Limiters struct {
	limiters *sync.Map
}

type Limiter struct {
	limiter *rate.Limiter
	lastGet time.Time //上一次获取token的时间
	key     string
}

var GlobalLimiters = &Limiters{
	limiters: &sync.Map{},
}

var once = sync.Once{}

func NewLimiter(r rate.Limit, b int, key string) *Limiter {
	once.Do(func() {
		go GlobalLimiters.clearLimiter()
	})
	keyLimiter := GlobalLimiters.getLimiter(r, b, key)
	return keyLimiter
}

func (l *Limiter) Allow() bool {
	l.lastGet = time.Now()
	return l.limiter.Allow()
}

// r:往桶里放Token的速率 b:令牌桶的大小 key:可对某id\ip做限制
func (ls *Limiters) getLimiter(r rate.Limit, b int, key string) *Limiter {
	limiter, ok := ls.limiters.Load(key)
	if ok {

		return limiter.(*Limiter)
	}
	l := &Limiter{
		limiter: rate.NewLimiter(r, b),
		lastGet: time.Now(),
		key:     key,
	}
	ls.limiters.Store(key, l)
	return l
}

//清除过期的限流器
func (ls *Limiters) clearLimiter() {
	for {
		time.Sleep(1 * time.Minute)
		ls.limiters.Range(func(key, value interface{}) bool {
			//超过1分钟
			if time.Now().Unix()-value.(*Limiter).lastGet.Unix() > 60 {

				ls.limiters.Delete(key)
			}
			return true
		})
	}
}

2.在需要进行限流的接口使用

// createDataBank 注册新数据文件
func CreateFootPrint(c *gin.Context) {
	appG := app.Gin{C: c}
	// 10ms放一个token,桶容量100
	limit := limiter.NewLimiter(rate.Every(10*time.Millisecond), 100, "")
	// 令牌桶限流器--防止大量请求
	if !limit.Allow() {
		appG.Response(models.ErrorFrequently, "写入失败", "您的访问过于频繁,请稍后再试")
		return
	}
	// 省略业务代码
   ........

	appG.Response(models.Success, "成功", response)
}

自测方法,建立一个limiter_test测试限流器是否管用

package limiter

import (
	"golang.org/x/time/rate"
	"testing"
	"time"
)

func TestMqProducer(t *testing.T) {

	l := NewLimiter(rate.Every(time.Millisecond * 31), 1, "152****86")
	for i := 0; i < 10; i++ {
		if l.Allow() {
			t.Log("success")
		} else {
			t.Log("您的访问过于频繁")
		}
		time.Sleep(time.Millisecond * 20)
	}
}

二、原理和参数记录

time/rate包的Limiter类型对限流器进行了定义,所有限流功能都是通过基于Limiter类型实现的,其内部结构如下

type Limiter struct {
 mu     sync.Mutex
 limit  Limit
 burst  int // 令牌桶的大小
 tokens float64
 last time.Time // 上次更新tokens的时间
 lastEvent time.Time // 上次发生限速器事件的时间(通过或者限制都是限速器事件)
}

其主要字段的作用是:

  • limit:limit字段表示往桶里放Token的速率,它的类型是Limit,是int64的类型别名。设置limit时既可以用数字指定每秒向桶中放多少个Token,也可以指定向桶中放Token的时间间隔,其实指定了每秒放Token的个数后就能计算出放每个Token的时间间隔了。

  • burst: 令牌桶的大小。

  • tokens: 桶中的令牌。

  • last: 上次往桶中放 Token 的时间。

  • lastEvent:上次发生限速器事件的时间(通过或者限制都是限速器事件)  

可以看到在 timer/rate 的限流器实现中,并没有单独维护一个 Timer 和队列去真的每隔一段时间向桶中放令牌,而是仅仅通过计数的方式表示桶中剩余的令牌。每次消费取 Token 之前会先根据上次更新令牌数的时间差更新桶中Token数
这个池子一开始容量为b,装满b个令牌,然后每秒往里面填充r个令牌。
由于令牌池中最多有b个令牌,所以一次最多只能允许b个事件发生,一个事件花费掉一个令牌。

可以使用以下方法构造一个限流器对象:

limiter := rate.NewLimiter(10, 100);

这里有两个参数:

第一个参数是 r Limit,设置的是限流器Limiter的limit字段,代表每秒可以向 Token 桶中产生多少 token。Limit 实际上是 float64 的别名。

第二个参数是 b int,b 代表 Token 桶的容量大小,也就是设置的限流器 Limiter 的burst字段。

对于以上例子来说,其构造出的限流器的令牌桶大小为 100, 以每秒 10 个 Token 的速率向桶中放置 Token,除了给r Limit参数直接指定每秒产生的 Token 个数外,还可以用 Every 方法来指定向桶中放置 Token 的间隔,例如:

limit := rate.Every(100 * time.Millisecond);
limiter := rate.NewLimiter(limit, 100);

以上就表示每 100ms 往桶中放一个 Token。本质上也是一秒钟往桶里放 10 个

Limiter有三个主要的方法 Allow、Reserve和Wait,最常用的是Wait和Allow方法

这三个方法每调用一次都会消耗一个令牌,这三个方法的区别在于没有令牌时,他们的处理方式不同

Allow: 如果没有令牌,则直接返回false

Reserve:如果没有令牌,则返回一个reservation,

Wait:如果没有令牌,则等待直到获取一个令牌或者其上下文被取消。

一些好的博文参考

Go 官方限流器的用法详解

Golang 标准库 限流器 time/rate 设计与实现

Golang限流器time/rate实现剖析

Golang官方限流器的用法详解

golang常用代码片段--定制化gin中间件

golang.org/x/time/rate 使用说明

go使用案例

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值