使用 java 和没有锁定的 redis 的速率限制器
Rate Limiter using java and redis without locking
我试图了解速率限制器的工作原理。
问题陈述是:每个 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();
}
}
截至目前,我忽略了删除旧密钥。
我无法理解这个解决方案将如何运作?
按照我的说法,上面的代码最终会在一秒钟内在多线程环境中进行超过 10 api 次调用。它只能通过将 redisClient.get(key) 同步到 callApi() 代码来解决。
我从
中获取了这段代码
https://redis.io/commands/incr.
任何人都可以帮助我理解(通过修改上面的代码)在这些场景中使用 redis 的正确方法是什么?
假设在当前秒9个请求已经served.now并发5个新请求,所有这些新线程在任何一个之前调用redisClient.get(key)这 5 个线程执行 "else" block.So 每个线程 count 将是 9,否则块将被执行并且 incr 将被调用 5 次并且对于每个线程 api 将被调用。
照原样,代码确实容易受到竞争条件的影响(以及内存膨胀,因为您没有过期)。基本上有两种方法可以解决此问题:MULTI/EXEC
transaction with a WATCH
, or EVAL
ing 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),只要您的代码跟在 [= 之后,它几乎会做同样的事情15=] 获得 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)。
您可以使用与 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);
我试图了解速率限制器的工作原理。
问题陈述是:每个 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();
}
}
截至目前,我忽略了删除旧密钥。 我无法理解这个解决方案将如何运作? 按照我的说法,上面的代码最终会在一秒钟内在多线程环境中进行超过 10 api 次调用。它只能通过将 redisClient.get(key) 同步到 callApi() 代码来解决。
我从
中获取了这段代码https://redis.io/commands/incr.
任何人都可以帮助我理解(通过修改上面的代码)在这些场景中使用 redis 的正确方法是什么?
假设在当前秒9个请求已经served.now并发5个新请求,所有这些新线程在任何一个之前调用redisClient.get(key)这 5 个线程执行 "else" block.So 每个线程 count 将是 9,否则块将被执行并且 incr 将被调用 5 次并且对于每个线程 api 将被调用。
照原样,代码确实容易受到竞争条件的影响(以及内存膨胀,因为您没有过期)。基本上有两种方法可以解决此问题:MULTI/EXEC
transaction with a WATCH
, or EVAL
ing 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),只要您的代码跟在 [= 之后,它几乎会做同样的事情15=] 获得 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)。
您可以使用与 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);