官方issue链接
问题的产生
- 版本
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-25</artifactId>
<version>3.16.1</version>
</dependency>
- 源码
注意lua脚本中key的值
public void setRate(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
this.get(this.setRateAsync(type, rate, rateInterval, unit));
}
public RFuture<Void> setRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
return this.commandExecutor.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "redis.call('hset', KEYS[1], 'rate', ARGV[1]);redis.call('hset', KEYS[1], 'interval', ARGV[2]);redis.call('hset', KEYS[1], 'type', ARGV[3]);redis.call('del', KEYS[2], KEYS[3]);", Arrays.asList(this.getRawName(), this.getValueName(), this.getPermitsName()), new Object[]{rate, unit.toMillis(rateInterval), type.ordinal()});
}
}
问题修复方案一
- 版本
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-32</artifactId>
<version>3.26.0</version>
</dependency>
- 源码
注意lua脚本中key的值
public void setRate(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
this.get(this.setRateAsync(type, rate, rateInterval, unit));
}
public RFuture<Void> setRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
return this.commandExecutor.evalWriteAsync(this.getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "local valueName = KEYS[2];local permitsName = KEYS[4];if ARGV[3] == '1' then valueName = KEYS[3]; permitsName = KEYS[5];end redis.call('hset', KEYS[1], 'rate', ARGV[1]);redis.call('hset', KEYS[1], 'interval', ARGV[2]);redis.call('hset', KEYS[1], 'type', ARGV[3]);redis.call('del', valueName, permitsName);", Arrays.asList(this.getRawName(), this.getValueName(), this.getClientValueName(), this.getPermitsName(), this.getClientPermitsName()), new Object[]{rate, unit.toMillis(rateInterval), type.ordinal()});
}
问题修复方案二
- 自定义setRate逻辑,可以参考高版本中源码
- 注意代码中getManagerId,在valueName, permitsName中拼接的使用
- 注意代码中执行lua脚本的时候指定的LongCodec.INSTANCE
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.*;
import org.redisson.client.codec.LongCodec;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
/**
*
*/
@Component
@Slf4j
public class RedissonRateLimiterHelper{
@Resource
private RedissonClient redissonClient;
public static String prefixName(String prefix, String name) {
return name.contains("{") ? prefix + ":" + name : prefix + ":{" + name + "}";
}
public static String suffixName(String name, String suffix) {
return name.contains("{") ? name + ":" + suffix : "{" + name + "}:" + suffix;
}
String getPermitsName(String rawName) {
return suffixName(rawName, "permits");
}
String getClientPermitsName(String rawName) {
return suffixName(getPermitsName(rawName), getManagerId());
}
String getValueName(String rawName) {
return suffixName(rawName, "value");
}
String getClientValueName(String rawName) {
return suffixName(getValueName(rawName), getManagerId());
}
String getManagerId(){
String serviceManagerId = ((Redisson) redissonClient).getConnectionManager().getServiceManager().getId();
log.info("RedissonRateLimiterHelper get serviceManagerId:{}",serviceManagerId);
return serviceManagerId;
}
public void setRate(String rawName,RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
RScript script = redissonClient.getScript(LongCodec.INSTANCE);
String luaScript = "local rate = tonumber(ARGV[1]);"
+ "local interval = tonumber(ARGV[2]);"
+ "local type = tonumber(ARGV[3]);"
+ "local valueName = KEYS[2];"
+ "local permitsName = KEYS[4];"
+ "if type == 1 then "
+ " valueName = KEYS[3];"
+ " permitsName = KEYS[5];"
+ "end "
+"redis.call('hset', KEYS[1], 'rate', rate);"
+ "redis.call('hset', KEYS[1], 'interval', interval);"
+ "redis.call('hset', KEYS[1], 'type', type);"
+ "redis.call('del', valueName, permitsName);";
boolean res = script.eval(RScript.Mode.READ_WRITE, luaScript, RScript.ReturnType.BOOLEAN, Arrays.asList(rawName, getValueName(rawName), getClientValueName(rawName), getPermitsName(rawName), getClientPermitsName(rawName)), new Object[]{Long.valueOf(rate), Long.valueOf(unit.toMillis(rateInterval)),Integer.valueOf(type.ordinal())});
log.info("RedissonRateLimiterHelper set rate:{} res:{}",rate,res);
}
}
总结
- 当使用redissonClient.getRateLimiter(rawName)创建限流器时,redis中的存储如下图
- 当重复使用 RedissonRateLimiter.setRate()方法时
-
lua脚本本中 使用 hset 更新 limiter config的值(rate\interval\type)
-
lua脚本本中 使用 del 删除 valueName和permitsName
-
当type为RateType.PER_CLIENT的时候,valueName和permitsName 在初次设置的时候 拼接了clientId,而在删除的时候没有拼接上导致问题的出现