你们是怎么实现 rate limiting 的?

2022-03-10 23:01:04 +08:00
 JasonLaw

我所实现的 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 ?

2090 次点击
所在节点    程序员
9 条回复
Jooooooooo
2022-03-10 23:03:01 +08:00
需要这么精准吗?

"怎么解决上述问题?" 和 pm 沟通是不是换个需求.
JasonLaw
2022-03-10 23:05:46 +08:00
@Jooooooooo #1 pm 没有这个需求,是我自己实现的😅。但我不知道怎么解决时钟的问题。🤐
sujin190
2022-03-10 23:22:26 +08:00
@JasonLaw 毫秒级,秒级,分钟级,解决方案不一样啊,不要试图用一种方案适配所有场景,分钟级限制那么一两秒差异无所谓,需要秒级就需要高可用强一致的时间源,毫秒级你需要原子钟,据说谷歌为了跨地区一致性搞过这个😅😅
zeni123
2022-03-10 23:22:30 +08:00
告诉用户它的时钟和服务器的不同步。 即使你能解决你的服务器的时间同步不问题 你也解决不了用户和你的服务器时间不同步的问题。 所以你也不需要解决你的服务器时间同步了 直接甩锅给用户。或者换一种算法。
Jooooooooo
2022-03-10 23:27:45 +08:00
@JasonLaw 你如果要考虑这种问题的话那需要考虑的问题就多了去了.

比如各个请求阶段(请求发出没到 redis 集群, 请求发出到了 redis 机器但还没处理, 请求发出到了 redis 机器已经处理但是还没开始同步...等等)过程中 redis 机器宕机怎么办.
546L5LiK6ZOt
2022-03-10 23:31:03 +08:00
不考虑使用单机限速吗,不依赖网络。如果应用实例数量不是经常变化,单机限速就够了。或者也可以考虑再加上动态配置,这样集群扩容,手动改一下限速就好。
Gota
2022-03-11 00:05:13 +08:00
可以参考这个实现, 或者直接用它的库: https://github.com/go-redis/redis_rate/blob/v9/lua.go
JasonLaw
2022-03-11 09:40:47 +08:00
@Gota #7 有空了看看
hankai17
2022-03-11 18:39:50 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/839542

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX