java - 使用 java 和 redis 的 Rate Limiter 无需锁定

标签 java architecture redis system-design

我试图了解速率限制器的工作原理。

问题陈述是:每个 IP 地址每秒十个请求

Soln:我在博客中看到的是:

public void makeApiCall(String ip){
    Long currentTime=Timestamp timestamp = System.currentTimeMillis();

    String key=ip+":"+currentTime;

    Integer count=redisClient.get(key);

    if(count!=null && count > 10){
         throw LimitExceededException();
    }
    else{
         redisClient.incr(key,1);
         callApi();
    }
}

截至目前,我忽略了删除的旧 key 。 我无法理解这个解决方案将如何运作? 按照我的说法,上面的代码最终将在一秒钟内在多线程环境中进行 10 多个 api 调用。只能通过将 redisClient.get(key) 同步到 callApi() 代码来解决。

我从中获取了这段代码

https://redis.io/commands/incr .

任何人都可以帮助我理解(通过修改上面的代码)在这些场景中使用 redis 的正确方法是什么?

假设在当前second 9个请求已经被服务。现在同时有5个新请求到来,所有这些新线程在这5个线程中的任何一个执行“else” block 之前调用redisClient.get(key)。因此,对于每个线程,count 将是 9,else block 将被执行,incr 将被调用 5 次,并且对于每个线程,api 将被调用。

最佳答案

照原样,代码确实容易受到竞争条件的影响(以及内存膨胀,因为您没有过期)。基本上有两种方法可以解决这个问题:MULTI/EXEC transaction with a WATCH , 或 EVAL编写一个 Lua 脚本。

假设您使用 Jedis 作为您的 Java 客户端,类似下面的东西应该可以处理事务:

public void makeApiCall(String ip){
    Long currentTime=Timestamp timestamp = System.currentTimeMillis();

    String key=ip+":"+currentTime;

    redisClient.watch(key);
    Integer count=redisClient.get(key);

    if(count!=null && count > 10){
         throw LimitExceededException();
    }
    else{
         Transaction t = redisClient.multi();
         t.incr(key,1);
         List<Object> resp = t.exec();
         if(resp != null){
             callApi();
         }
    }
}

Lua 是另一回事,但基本上假设您为以下脚本提供了整个键名 (ip + ts),只要您的代码跟在 callApi 之后,它几乎会做同样的事情获得 OK:

local count = redis.call('GET', KEYS[1])
if count and tonumber(count) > 10 then
    return redis.error('count exceeded')
else
    redis.call('INCR', KEYS[1])
    redis.call('EXPIRE', KEYS[1], 10)
    return 'OK'
end

请注意,使用 Lua,您无需监视更改,因为整个脚本都是原子的。

最后,您在文档中提到的计数器模式似乎缺少 WATCH 东西 - 我已经提交了一个 PR 来纠正它(https://github.com/antirez/redis-doc/pull/888)。

关于java - 使用 java 和 redis 的 Rate Limiter 无需锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47737768/

相关文章:

java - 如果我依赖于缓慢的服务,如何避免花费处理器时间来等待它?

list - 如何遍历redis列表

java - SWT:具有复制/粘贴功能的表格

ruby-on-rails - 如何在移动应用后端处理阅读通知

java - 在java中绘制极坐标图

architecture - Maven多模块切割

Redis - 为什么 redis-server 内存减少?

Redis sentinel 将 slaves 标记为 down

java - Java 7 中的 Files.isSameFile

java - Java中如何不重写子类中的方法?