V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
JasonLaw
V2EX  ›  程序员

你们是怎么实现 rate limiting 的?

  •  
  •   JasonLaw · 335 天前 · 1717 次点击
    这是一个创建于 335 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    9 条回复    2022-03-11 18:39:50 +08:00
    Jooooooooo
        1
    Jooooooooo  
       335 天前
    需要这么精准吗?

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

    比如各个请求阶段(请求发出没到 redis 集群, 请求发出到了 redis 机器但还没处理, 请求发出到了 redis 机器已经处理但是还没开始同步...等等)过程中 redis 机器宕机怎么办.
    546L5LiK6ZOt
        6
    546L5LiK6ZOt  
       335 天前
    不考虑使用单机限速吗,不依赖网络。如果应用实例数量不是经常变化,单机限速就够了。或者也可以考虑再加上动态配置,这样集群扩容,手动改一下限速就好。
    Gota
        7
    Gota  
       335 天前
    可以参考这个实现, 或者直接用它的库: https://github.com/go-redis/redis_rate/blob/v9/lua.go
    JasonLaw
        8
    JasonLaw  
    OP
       335 天前
    @Gota #7 有空了看看
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   实用小工具   ·   4821 人在线   最高记录 5556   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 09:40 · PVG 17:40 · LAX 01:40 · JFK 04:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.