我所实现的 rate limiting 是基于How to implement rate limiting using Redis - Stack Overflow的,rateLimiting.lua
如下:
-- https://stackoverflow.com/questions/13175050/how-to-implement-rate-limiting-using-redis
-- KEYS[1] = rateLimitingKey, ARGV[1] = timePeriodInSeconds, ARGV[2] = allowableNumberOfCalls, ARGV[3] = nowEpochSecond
local timePeriodInSeconds = tonumber(ARGV[1])
local allowableNumberOfCalls = tonumber(ARGV[2])
local nowEpochSecond = tonumber(ARGV[3])
local times = redis.call('RPUSH', KEYS[1], nowEpochSecond)
if times > allowableNumberOfCalls then
local timeStart = redis.call('LINDEX', KEYS[1], 0)
local timeEnd = redis.call('LINDEX', KEYS[1], -1)
redis.call('LTRIM', KEYS[1], -allowableNumberOfCalls, -1)
if timeEnd - timeStart <= timePeriodInSeconds then
return false
else
return true
end
else
return true
end
如果 times <= allowableNumberOfCalls ,直接返回 true ,如果 times > allowableNumberOfCalls ,那么判断一下 timeEnd - timeStart <= timePeriodInSeconds ,如果结果为 true ,那么 return false ,否则 return true 。
但是这有个问题,因为 nowEpochSecond 是服务实例告诉 Redis 的,但多个实例的时钟是无法确保一致的,那么会出现如下情况。
假设 timePeriodInSeconds 为 5 ,allowableNumberOfCalls 为 1 ,当前时间(正确的时钟)为 2022-03-10 22:00:00 ,实例 1 的时钟跟正确的时钟一样,实例 2 的时钟比正确的时钟慢了 3 秒。
用户此时访问了资源,该请求被实例 1 处理,能获取到资源,因为 ta 之前从没访问过,所以不会被限制。过了 6 秒,ta 又访问了资源,正常的结果是用户可以访问到资源,但是结果并不是,因为这次请求被实例 2 处理,而实例 2 此时的时间为 2022-03-10 22:00:03 ,rateLimiting.lua 最后会返回 false 。
怎么解决上述问题?或者有什么更好的方法实现 rate limiting ?
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.