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

时间:2017-12-10 10:11:36

标签: 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!=null && count > 10){
         throw LimitExceededException();
    }
    else{
         redisClient.incr(key,1);
         callApi();
    }
}

截至目前,我无视删除旧密钥。 我无法理解这个soln是如何工作的? 按照上面的代码,代码将在一秒钟内在多线程环境中进行超过10次的api调用。它只能通过将redisClient.get(key)同步到callApi()代码来解决。

我从

获取此代码

https://redis.io/commands/incr

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

假设在当前的第二次中已经提供了9个请求。同时发出5个新请求,所有这些新线程在这5个线程中的任何一个执行&#34之前调用redisClient.get(key);否则& #34;对于每个线程, count 将为9,否则将执行block并且将调用incr 5次以上,并且将调用每个线程api。

2 个答案:

答案 0 :(得分:3)

原样,代码确实容易受到竞争条件的影响(并且因为你已经过期而导致内存膨胀)。基本上有两种解决方法:MULTI/EXEC transaction with a WATCHEVAL 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)。

答案 1 :(得分:0)

您可以使用与Redisson 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);