使用java和redis而不锁定的速率限制器

使用java和redis而不锁定的速率限制器,java,architecture,redis,system-design,Java,Architecture,Redis,System Design,我试图了解限速器是如何工作的 问题陈述是:每个IP地址每秒10个请求 Soln:我在博客中看到的是: public void makeApiCall(String ip){ Long currentTime=Timestamp timestamp = System.currentTimeMillis(); String key=ip+":"+currentTime; Integer count=redisClient.get(key); if(count!=

我试图了解限速器是如何工作的

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

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();
    }
}
到现在为止,我忽略了删除旧钥匙。 我无法理解这个soln将如何工作? 根据我的说法,上述代码将在一秒钟内在多线程环境中进行10次以上的api调用。只能通过将redisClient.get(键)同步到callApi()代码来解决此问题

我已经把这个代码从

有谁能帮助我理解(通过修改上面的代码)在这些场景中使用redis的正确方法是什么


假设在当前中,9个请求已被服务。现在同时出现5个新请求,所有这些新线程在这5个线程执行“else”之前调用redisClient.get(key)因此,对于每个线程,计数将为9,否则将执行block,调用incr的次数将增加5倍,对于每个线程,将调用api。

照目前的情况,代码确实容易受到竞争条件的影响(由于忽略了过期,内存会膨胀)。基本上有两种方法可以解决这个问题:一种是编译,另一种是编译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时,您不需要监视更改,因为整个脚本是原子的


最后,您在文档中提到的计数器模式似乎缺少了
WATCH
这个东西-我已经提交了一份PR来纠正它()

就目前而言,代码确实容易受到竞争条件的影响(并且由于忽略了过期,内存会膨胀)。基本上有两种方法可以解决这个问题:一种是编译,另一种是编译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时,您不需要监视更改,因为整个脚本是原子的


最后,您在文档中提到的计数器模式似乎缺少了
WATCH
这个东西-我已经提交了一份PR来纠正它()

您可以使用与Redis java客户端绑定的
RRateLimiter
对象

RRateLimiter limiter = redisson.getRateLimiter("rate-limiter:" + ip);
// Initialization required only once.
// 5 permits per 2 seconds
limiter.trySetRate(RateType.OVERALL, 5, 2, RateIntervalUnit.SECONDS);

// acquire 3 permits or block until they became available        
limiter.acquire(3);

您可以使用Redis java客户端绑定的
RRateLimiter
对象

RRateLimiter limiter = redisson.getRateLimiter("rate-limiter:" + ip);
// Initialization required only once.
// 5 permits per 2 seconds
limiter.trySetRate(RateType.OVERALL, 5, 2, RateIntervalUnit.SECONDS);

// acquire 3 permits or block until they became available        
limiter.acquire(3);

您在上面提供的代码(尽管缺少EXPIRE)将限制为在一毫秒内调用10次
callApi()。您的问题是什么?我已经更新了问题。您在上面提供的代码(尽管没有过期)将限制为在一毫秒内调用10次
callApi()。你的问题是什么?我已经更新了问题。