英文:
Using golang.org/x/time/rate to allow N requests per minute
问题
我想要的:
一个允许每分钟n个请求的限制器。
我尝试过的:
(在初始化过程中的某个地方)
limiter = rate.NewLimiter(rate.Every(1*time.Minute/2), 2)
然后在我的HTTP服务器的中间件中:
func (self *Router) limiterMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
if !limiter.Allow(){
http.Error(responseWriter, "Too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(responseWriter, request)
})
}
根据我的理解,这应该允许每分钟2个请求。然后我尝试发送几个请求,前两个请求之间有15秒的间隔,然后大约每秒发送一个请求。
期望的结果:
- 前两个请求成功
- 后续的请求返回HTTP429,直到过了1分钟,并且第一个成功的请求被“清除”
- 再发送一个请求成功
- 后续的请求返回HTTP429,直到过了15秒,并且最初的第二个请求被“清除”
实际结果:
22/10/31 14:02:31 access: 200 POST /some/path
22/10/31 14:02:46 access: 200 POST /some/path
22/10/31 14:02:47 access: 429 POST /some/path
22/10/31 14:02:48 access: 429 POST /some/path
22/10/31 14:02:49 access: 429 POST /some/path
22/10/31 14:02:50 access: 429 POST /some/path
22/10/31 14:02:51 access: 429 POST /some/path
22/10/31 14:02:52 access: 429 POST /some/path
22/10/31 14:02:53 access: 429 POST /some/path
22/10/31 14:02:54 access: 429 POST /some/path
22/10/31 14:02:55 access: 429 POST /some/path
22/10/31 14:02:56 access: 429 POST /some/path
22/10/31 14:02:57 access: 429 POST /some/path
22/10/31 14:02:58 access: 429 POST /some/path
22/10/31 14:02:59 access: 429 POST /some/path
22/10/31 14:03:00 access: 429 POST /some/path
22/10/31 14:03:01 access: 200 POST /some/path
22/10/31 14:03:02 access: 429 POST /some/path
22/10/31 14:03:03 access: 429 POST /some/path
22/10/31 14:03:04 access: 429 POST /some/path
22/10/31 14:03:05 access: 429 POST /some/path
22/10/31 14:03:06 access: 429 POST /some/path
22/10/31 14:03:07 access: 429 POST /some/path
22/10/31 14:03:08 access: 429 POST /some/path
22/10/31 14:03:09 access: 429 POST /some/path
22/10/31 14:03:10 access: 429 POST /some/path
22/10/31 14:03:11 access: 429 POST /some/path
22/10/31 14:03:12 access: 429 POST /some/path
22/10/31 14:03:13 access: 429 POST /some/path
22/10/31 14:03:14 access: 429 POST /some/path
22/10/31 14:03:15 access: 429 POST /some/path
22/10/31 14:03:16 access: 429 POST /some/path
22/10/31 14:03:16 access: 429 POST /some/path
22/10/31 14:03:17 access: 429 POST /some/path
22/10/31 14:03:18 access: 429 POST /some/path
22/10/31 14:03:19 access: 429 POST /some/path
22/10/31 14:03:20 access: 429 POST /some/path
22/10/31 14:03:21 access: 429 POST /some/path
22/10/31 14:03:22 access: 429 POST /some/path
22/10/31 14:03:23 access: 429 POST /some/path
22/10/31 14:03:24 access: 429 POST /some/path
22/10/31 14:03:25 access: 429 POST /some/path
22/10/31 14:03:26 access: 429 POST /some/path
22/10/31 14:03:27 access: 429 POST /some/path
22/10/31 14:03:28 access: 429 POST /some/path
22/10/31 14:03:29 access: 429 POST /some/path
22/10/31 14:03:30 access: 429 POST /some/path
22/10/31 14:03:32 access: 200 POST /some/path
22/10/31 14:03:33 access: 429 POST /some/path
从日志中可以看出,在最初的2个请求之后,第三个成功的请求在30秒后发生。这意味着在30秒的运行时间内,有3个请求成功了,而实际上应该只有2个。第四个成功的请求再次在30秒后(总共60秒)发生。
如果我将突发值设置为1,那么最初只有1个请求成功,每30秒可以成功一个请求。
所以我不确定我应该如何配置限制器来实现我想要的(每分钟n个请求)。
我做错了什么?是否可以使用内置的限制器实现这个任务,还是我需要使用其他库?
英文:
What I want:
A limiter which allows n requests per minute.
What I tried:
(somewhere during init procedure)
limiter = rate.NewLimiter(rate.Every(1*time.Minute/2), 2)
Then in the middleware of my HTTP server:
func (self *Router) limiterMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
if !limiter.Allow(){
http.Error(responseWriter, "Too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(responseWriter, request)
})
}
This should, to my understanding, allow 2 requests per minute. Then I tried to send several requests, with the first two having 15s of pause in between, then roughly 1 request per second.
Expectation:
- first two requests work
- subsequent requests get HTTP429 until 1 minute has passed, and the first succeeding one is "cleared"
- one more request works
- subsequent requests get HTTP429 until 15s have passend, and the initial second request is"cleared"
Actual result:
22/10/31 14:02:31 access: 200 POST /some/path
22/10/31 14:02:46 access: 200 POST /some/path
22/10/31 14:02:47 access: 429 POST /some/path
22/10/31 14:02:48 access: 429 POST /some/path
22/10/31 14:02:49 access: 429 POST /some/path
22/10/31 14:02:50 access: 429 POST /some/path
22/10/31 14:02:51 access: 429 POST /some/path
22/10/31 14:02:52 access: 429 POST /some/path
22/10/31 14:02:53 access: 429 POST /some/path
22/10/31 14:02:54 access: 429 POST /some/path
22/10/31 14:02:55 access: 429 POST /some/path
22/10/31 14:02:56 access: 429 POST /some/path
22/10/31 14:02:57 access: 429 POST /some/path
22/10/31 14:02:58 access: 429 POST /some/path
22/10/31 14:02:59 access: 429 POST /some/path
22/10/31 14:03:00 access: 429 POST /some/path
22/10/31 14:03:01 access: 200 POST /some/path
22/10/31 14:03:02 access: 429 POST /some/path
22/10/31 14:03:03 access: 429 POST /some/path
22/10/31 14:03:04 access: 429 POST /some/path
22/10/31 14:03:05 access: 429 POST /some/path
22/10/31 14:03:06 access: 429 POST /some/path
22/10/31 14:03:07 access: 429 POST /some/path
22/10/31 14:03:08 access: 429 POST /some/path
22/10/31 14:03:09 access: 429 POST /some/path
22/10/31 14:03:10 access: 429 POST /some/path
22/10/31 14:03:11 access: 429 POST /some/path
22/10/31 14:03:12 access: 429 POST /some/path
22/10/31 14:03:13 access: 429 POST /some/path
22/10/31 14:03:14 access: 429 POST /some/path
22/10/31 14:03:15 access: 429 POST /some/path
22/10/31 14:03:16 access: 429 POST /some/path
22/10/31 14:03:16 access: 429 POST /some/path
22/10/31 14:03:17 access: 429 POST /some/path
22/10/31 14:03:18 access: 429 POST /some/path
22/10/31 14:03:19 access: 429 POST /some/path
22/10/31 14:03:20 access: 429 POST /some/path
22/10/31 14:03:21 access: 429 POST /some/path
22/10/31 14:03:22 access: 429 POST /some/path
22/10/31 14:03:23 access: 429 POST /some/path
22/10/31 14:03:24 access: 429 POST /some/path
22/10/31 14:03:25 access: 429 POST /some/path
22/10/31 14:03:26 access: 429 POST /some/path
22/10/31 14:03:27 access: 429 POST /some/path
22/10/31 14:03:28 access: 429 POST /some/path
22/10/31 14:03:29 access: 429 POST /some/path
22/10/31 14:03:30 access: 429 POST /some/path
22/10/31 14:03:32 access: 200 POST /some/path
22/10/31 14:03:33 access: 429 POST /some/path
You can see from the log that after the initial 2 requests, the third successful request happens after 30s. This means, that during 30s of uptime, 3 requests have succeeded, while it should only be 2. The fourth successful request happens again after 30s (60s in total).
If I set the bursts to 1, then initially only 1 request succeeds, and every 30s one more request can succeed.
So I am not sure how I should configure the limiter to achieve what I want (plain n requests per minute).
What am I doing wrong? Can this even be achieved using the built-in limiter, or do I need a different library for this task?
答案1
得分: 2
Limiter
实现了令牌桶算法,本质上按照规定的间隔提供令牌。
你在评论中提到了滑动窗口:我不认为“标准”的速率限制滑动窗口实现会完全符合你想要的行为。在https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm中描述的方法表明应该使用固定窗口上的加权,我见过这种方法的实现。尽管如此,这仍然是一个不错的选择 - 像https://github.com/Narasimha1997/ratelimiter这样的库可能会为你处理这个问题。
你描述的行为似乎更符合链接文章中描述的“滑动日志”,你可以考虑实现它,尽管要记住文章中描述的注意事项。
英文:
Limiter
implements a token bucket algorithm, which in essence feeds tokens at the stipulated interval.
You mentioned sliding window in a comment: I don't believe a "standard" implementations of sliding window for rate limiting will exactly match the behaviour you're after. The approach described under https://konghq.com/blog/how-to-design-a-scalable-rate-limiting-algorithm indicates weighting over fixed windows should be used, and I've seen this type approach implemented. That said, it's still not a bad approach to go for - a library like https://github.com/Narasimha1997/ratelimiter may handle this for you.
The behaviour you're describing seems to better match what is described as "sliding log" in the linked article, which you could look to implement, albeit with the caveats it describes in mind.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论