前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 标准库 time/rate 介绍

golang 标准库 time/rate 介绍

作者头像
ppxai
发布2020-09-23 17:50:06
8580
发布2020-09-23 17:50:06
举报
文章被收录于专栏:皮皮星球皮皮星球

标准库限流器 time/rate 使用介绍

golang官方库中有一个rate包,实现了令牌桶算法。仓库地址:https://github.com/golang/time

  1. 生成一个Limiter对象
代码语言:javascript
复制
func NewLimiter(r Limit, b int) *Limiter {
	return &Limiter{
		limit:  r,
		burst:  b,
	}
}

如上所示,其中r是往令牌桶中放令牌的速率,b是令牌桶的大小。

  1. 尝试获取token

例如:

代码语言:javascript
复制
func (lim *Limiter) Allow() bool {
	return lim.AllowN(time.Now(), 1)
}

API很多,最终都是依靠 reserveN 来确定

代码语言:javascript
复制
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
	lim.mu.Lock()

	if lim.limit == Inf {
		lim.mu.Unlock()
		return Reservation{
			ok:        true,
			lim:       lim,
			tokens:    n,
			timeToAct: now,
		}
	}

	now, last, tokens := lim.advance(now)

	// Calculate the remaining number of tokens resulting from the request.
	tokens -= float64(n)

	// Calculate the wait duration
	var waitDuration time.Duration
	if tokens < 0 {
		waitDuration = lim.limit.durationFromTokens(-tokens)
	}

	// Decide result
	ok := n <= lim.burst && waitDuration <= maxFutureReserve

	// Prepare reservation
	r := Reservation{
		ok:    ok,
		lim:   lim,
		limit: lim.limit,
	}
	if ok {
		r.tokens = n
		r.timeToAct = now.Add(waitDuration)
	} else {
		panic(10)
	}

	// Update state
	if ok {
		lim.last = now
		lim.tokens = tokens
		lim.lastEvent = r.timeToAct
	} else {
		lim.last = last
	}

	lim.mu.Unlock()
	return r
}
``

首先用一个常量来判断Limiter是否是无限大,上限是 math.MaxFloat64。

然后就是计算当前时间、上一次的时间和桶里剩余的token数量,这里在一种极端情况下,存在超出预期的情况,后面会介绍。然后就是计算是否是当前token不够,需要等待一段时间来获取令牌。

其中最值得关注的是 advance 方法

代码语言:javascript
复制
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
	last := lim.last
	if now.Before(last) {
		last = now
	}

	// Avoid making delta overflow below when last is very old.
	maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
	elapsed := now.Sub(last)
	if elapsed > maxElapsed {
		elapsed = maxElapsed
	}

	// Calculate the new number of tokens, due to time that passed.
	delta := lim.limit.tokensFromDuration(elapsed)
	tokens := lim.tokens + delta
	if burst := float64(lim.burst); tokens > burst {
		tokens = burst
	}

	return now, last, tokens
}

实现也很清晰,根据令牌桶里保存的上一次取令牌的时间,计算出两次时间的时间差,这里有个点是使用 durationFromTokens 方法来计算此时填满令牌桶需要的时间。取二者最小,避免delta过大,变量tokens溢出。

durationFromTokens是用来转换token生成的时间的工具方法,比如输入n个token 返回值是生成这n个token需要多少时间。

tokensFromDuration也是工具方法,用来转换一段时间能生成多少个token。

极端情况

当使用这个令牌桶时,如果burst为1,Limite为int32的最大值(2147483647)的时候,并发情况,会出现预期之外的情况。

在这个时候,上面两个工具函数的输入输出对应情况:

生成的token数量

需要的时间

0

0

1

0

2

0

3

1ns

4

1ns

5

2ns

6

2ns

7

3ns

耗时

生成的token数量

0ns

0

1ns

2.147483647

2ns

4.294967294

3ns

6.442450941

可以留意到,如果是limit比较大,但是不是足够打,而burst足够小,会有一种情况,导致桶中的令牌会被迅速取完。

因为在rate包中burst代表的其实是令牌桶的大小。

当burst也就是桶的size很小,假设burst为1,在请求并发的时候,假设很多个请求拿到的time的纳秒时间都是相同,就会导致桶里的令牌被迅速取走,而时间戳相同,所以不会往桶里加令牌,这样后续的请求,即使在limit设置很高的情况下,也会返回false,需要等待至少1ns的时间。

这时候,可以增大burst,减少这种请求取令牌的时候time相同时迅速取完桶里的令牌的情况。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020年09月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 标准库限流器 time/rate 使用介绍
  • 极端情况
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档