基于延迟计算令牌桶的gofiber频率限制中间件实现
source link: https://kiyonlin.github.io/post/work/gofiber/fiber-limiter/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
后端的api
接口一般都需要限制访问频率,一般的实现算法有令牌桶,漏桶等等。其中令牌桶支持突发流量,更合适访问流量整形。
关于令牌桶算法这里不再赘述,目前有两个golang
限流器库,github.com/juju/ratelimit和golang.org/x/time/rate,采用了延迟计算的方式实现令牌桶算法。本文主要是基于golang.org/x/time/rate的限流器进行实现,有两篇相关的文章《Golang限流器time/rate使用介绍》和《Golang限流器time/rate实现剖析》,有兴趣的同学可以查看一下。
限流响应头
限流响应头主要涉及四个字段:
- 未超出频率限制时:
X-RateLimit-Limit
:请求限制总量,对应了令牌桶算法中的突发值Burst
X-RateLimit-Remaining
:目前还可以请求的次数X-RateLimit-Reset
:多少秒才能恢复到满桶的状态
- 超出频率限制时:
Retry-After
:多少秒之后可以重试
golang.org/x/time/rate的Reserve方法返回一个*Reservation
对象,根据它的Delay方法,我们可以知道本次请求是否超出了频率限制:
delay
大于0,表示需要等待,即超出了频率限制。此时,我们根据delay
转换成秒数(至少一秒)即可。delay
等于0,表示不需要等待,即未超出频率限制。此时,我们除了Burst
,无法获取更多的有效信息。
为Reservation
增加信息
因为Reservation
缺少可以转化为X-RateLimit-Remaining
和X-RateLimit-Reset
的信息,我们需要在调用Reserve
时,保存一些相关数据(源码位置):
if ok {
r.tokens = n
r.timeToAct = now.Add(waitDuration)
// store remaining tokens as integer
// 1e-9 used to solve the problem of missing precision
r.remainedTokens = int(math.Floor(tokens + 1e-9))
r.reset = r.limit.durationFromTokens(float64(r.lim.burst) - tokens)
}
我们先看r.remainedTokens = int(math.Floor(tokens + 1e-9))
,它保存了调用Reserve
时,limiter
剩余的整数token
值。因为tokens
是float64
类型,会丢失一点精度,我们需要补全精度后,转为int
类型。
再看r.reset = r.limit.durationFromTokens(float64(r.lim.burst) - tokens)
,float64(r.lim.burst) - tokens
是已经消耗掉的token
数量,我们根据这个数量,转换为时长,即恢复这么多token
还需要多长时间。
实现fiber
频率限制中间件
Config
根据fiber中间件的实现规则,我们先创建一个配置结构:
type Config struct {
// Filter 定义了是否跳过中间件的方法,默认是nil
Filter func(*fiber.Ctx) bool
// Limit 定义了请求频率的最大值,表示每秒Limit个请求,默认值是 10
Limit int
// Burst 是最大突发值,默认值是 10
Burst int
// Message 响应消息,默认值是 "Too many requests, please try again later."
Message string
// StatusCode 状态码,默认值是 429
StatusCode int
// Key 允许用户使用自定义handler生成自定义的 key,默认值是
// func(c *fiber.Ctx) string {
// return c.IP()
// }
Key func(*fiber.Ctx) string
// Handler 触发频率限制时调用的 handler, 默认值是
// func(c *fiber.Ctx) {
// c.Status(cfg.StatusCode).Format(cfg.Message)
// }
Handler func(*fiber.Ctx)
}
利用这些配置,用户可以根据自己的需求使用中间件。
func New(config ...Config) func(*fiber.Ctx)
是中间件的工厂函数,根据用户传入的配置,返回一个fiber
中间件。
我们需要缓存下所有的限制器对象,存放在limiters
变量中,并用mu
控制并发访问:
var (
limiters = make(map[string]*rate.Limiter)
mu sync.Mutex
)
当api
接口收到请求时,获取限制器的key
,再根据key
获取限制器,没找到的话根据配置为key
新建一个限制器:
key := cfg.Key(c)
mu.Lock()
lim, ok := limiters[key]
if !ok {
// Get a default limiter
lim = rate.NewLimiter(rate.Limit(cfg.Limit), cfg.Burst)
limiters[key] = lim
}
mu.Unlock()
然后,我们调用lim.Reserve
获取一个*Reservation
对象,根据其Delay()
返回值确定有没有超出频率限制:
// Try to request
r := lim.Reserve()
// Check reservation's delay
if d := r.Delay(); d > 0 {
cfg.Handler(c)
// Return response with Retry-After header
// https://tools.ietf.org/html/rfc7231#section-7.1.3
// Set second value(at least one) to Retry-After header
c.Set(fiber.HeaderRetryAfter, strconv.FormatInt(int64(math.Ceil(d.Seconds())), 10))
return
}
// We can continue, update RateLimit headers
c.Set("X-RateLimit-Limit", strconv.Itoa(lim.Burst()))
c.Set("X-RateLimit-Remaining", strconv.Itoa(r.RemainedTokens()))
c.Set("X-RateLimit-Reset", strconv.FormatInt(int64(math.Ceil(r.Reset().Seconds())), 10))
// Bye!
c.Next()
其中X-RateLimit-Reset
和Retry-After
的值都转成秒为单位的整数,这是一个相对当前调用时间的值,客户端可以根据这个值作出相应的操作。
最后,我们提供func Set(key string, lim *rate.Limiter)
方法,用户可以根据自己的需求提前设置不同的限制器。比如针对不同的api
用户,设置不同的访问频率以及突发值。
本文主要针对api
接口的频率限制需求,在golang.org/x/time/rate的基础上为Reservation
对象增加方法,完善响应头中的信息,并应用到fiber框架中。
最后,我们可以发现基于令牌桶延迟计算实现频率限制的好处:
- 不需要定时器
- 不需要后台
goroutine
- 不需要队列
- 支持突发流量
仓库资源:
Recommend
-
24
Life is magic. Coding is art.
-
34
前段时间,我使用了 jwt 来实现邮箱验证码的校验与用户认证与登录,还特别写了一篇文章作为总结。 在那篇文章中,提到了一个点,如何限速。 在短信验证码和邮箱验证码,如果不限速,被恶意攻击造成大量的 QPS,不仅拖垮了服务,也会心疼如水的资费。鉴于君子固穷的...
-
20
限流又称为流量控制(流控),通常是指限制到达系统的并发请求数,本文列举了常见的限流策略,并以gin框架为例演示了如何为项目添加限流组件。 限流 限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。...
-
33
限流 限流又称为流量控制(流控),通常是指限制到达系统的并发请求数。 我们生活中也会经常遇到限流的场景,比如:某景区限制每日进入景区的游客数量为 8 万人;沙河地铁站早高峰通过站外排队逐一放行的...
-
31
在前文 《限流熔断是什么,怎么做,不做行不行?》中针对 “限流” 动作,有提到流量控制其内部对应着两种常用的限流算法。 其分别对应漏桶算法和令牌桶算法。因此会有的读者会好奇,这都是些啥? 为了更进一步的了解 WHY,本文来快...
-
33
一、微服务网关Spring Cloud Gateway...
-
6
常用限流策略——漏桶与令牌桶介绍 2020年9月13日 |
-
9
为Spring Cloud Gateway加上全局过滤器,并使用其内部自带的令牌桶算法实现(Redis)来进行限流既然是一个网关。那么全局过滤器肯定是少不了的一个存在。像是鉴权、认证啥的不可能每个服务都做一次,一般都是在网关处就搞定了。Zuul他就有很强大...
-
16
分布式系统为了保证系统稳定性,在服务治理的限流中会根据不同场景进行限流操作,常见的限流算法有: 令牌桶:可容...
-
4
Files Permalink Latest commit message Commit time
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK