golang 标准库 time/rate 介绍
标准库限流器 time/rate 使用介绍
golang官方库中有一个rate包,实现了令牌桶算法。仓库地址:https://github.com/golang/time
- 生成一个Limiter对象
func NewLimiter(r Limit, b int) *Limiter {
return &Limiter{
limit: r,
burst: b,
}
}
如上所示,其中r是往令牌桶中放令牌的速率,b是令牌桶的大小。
- 尝试获取token
例如:
func (lim *Limiter) Allow() bool {
return lim.AllowN(time.Now(), 1)
}
API很多,最终都是依靠 reserveN 来确定
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 方法
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相同时迅速取完桶里的令牌的情况。
- HDUOJ-------(1022)Train Problem I
- NYOJ-----素数环
- HDUOJ-----(1329)Calling Extraterrestrial Intelligence Again
- 修改WordPress默认评论表情(附:跳转到多说评论框的方法)
- HDUOJ--------(1312)Red and Black
- HDUOJ---------(1045)Fire Net
- HDUOJ----(1175)连连看
- HDUOJ-----(1072)Nightmare(bfs)
- deque容器的运用一点一点积累
- HDUOJ----(1016)Prime Ring Problem
- WordPress获取文章浏览总数
- HDUOJ----Safecracker(1015)
- hduoj---Tempter of the Bone
- nyoj------------找球号(一)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Apache 文件上传与文件下载案例详解
- 前端CLI脚手架思路解析并从0到1搭建
- Linux中搭建完整的samba服务器全攻略(centos版)
- Linux 命令行通配符及转义符的实现
- Linux采用双网卡bond、起子接口的方式
- 在Linux分区或逻辑卷中创建文件系统的方法
- Linux下通过sed命令对kv方式的配置文件进行修改
- Linux中将txt导入到mysql的方法教程
- LNMP部署及HTTPS服务开启教程
- 在 Linux 上锁定虚拟控制台会话的实现方法
- Linux中split大文件分割和cat合并文件详解
- centos下安装配置phpMyAdmin的方法步骤
- ubuntu中python调用C/C++方法之动态链接库详解
- linux下cat命令连接文件并打印到标准输出设备上
- 详解Linux误删用户家目录的恢复方法