redisson 限流器动态设置rate失效的问题

495 阅读2分钟
官方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中的存储如下图

1713182949415.jpg

  • 当重复使用 RedissonRateLimiter.setRate()方法时
  • lua脚本本中 使用 hset 更新 limiter config的值(rate\interval\type)

  • lua脚本本中 使用 del 删除 valueName和permitsName

  • 当type为RateType.PER_CLIENT的时候,valueName和permitsName 在初次设置的时候 拼接了clientId,而在删除的时候没有拼接上导致问题的出现