MK初创:Redisson分布式限流~配置变更重置限流拓展(二)

前言

接上一篇文章MK初创:Redisson分布式限流~深度解析(一)中发现的问题,本文对相关问题进行拓展改造讲解。改造功能思路:读–>抄–>测,先读懂源码关键代码及流程,再模仿源码写法抄,再根据拓展功能测试用例进行测试。

限流配置变更重置限流

一、改造原因

此方法无法满足当限流配置参数改动时覆盖并重置限流功能,重写此方法解决配置不同重置限流问题

rateLimiter.trySetRate(RateType.OVERALL, 4, 2, RateIntervalUnit.MINUTES)

//进入RedissonRateLimiter.java,设置限流配置参数
//lua脚本解释:设置rate、interval、type等参数时,原来存在则不设置
@Override
public RFuture<Boolean> trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
    return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            "redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);"
          + "redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);"
          + "return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);",
            Collections.singletonList(getRawName()), rate, unit.toMillis(rateInterval), type.ordinal());
}

二、改造思路

  1. 创建RedissonRateLimiterExpand继承RRateLimiter;
  2. 模仿生成RRateLimiter对象方法,RedissonRateLimiterExpand中创建create静态方法用于生成RedissonRateLimiterExpand对象;
  3. 重写父类RRateLimiter中的rateLimiter.trySetRate()方法。

三、改造代码

1. RedissonRateLimiterExpand类代码

public class RedissonRateLimiterExpand extends RedissonRateLimiter {

    public static RedissonRateLimiterExpand create(RedissonClient redissonClient, String name) {
        Assert.notNull(redissonClient, "redissonClient不能为空!");
        Assert.notBlank(name, "name不能为空!");
        //创建RedissonRateLimiterExpand对象时,需要传入redissonClient获取CommandAsyncExecutor
        Redisson redisson = (Redisson) redissonClient;
        CommandAsyncExecutor commandExecutor = redisson.getCommandExecutor();
        return new RedissonRateLimiterExpand(commandExecutor, name);
    }

    public RedissonRateLimiterExpand(CommandAsyncExecutor commandExecutor, String name) {
        super(commandExecutor, name);
    }

    @Override
    public boolean trySetRate(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
        return get(trySetRateAsync(type, rate, rateInterval, unit));
    }

    /**
     * 滑动时间窗口算法限流模式拓展
     * 设置限流规则,rate、interval、type都不为空时,判断新配置只要有一个值有变动则采用新配置,并删除旧限流数据进行重置
     * @param type  全局限流/单机限流
     * @param rate  速率
     * @param rateInterval 限流时间间隔
     * @param unit 限流时间单位
     * @return
     */
    @Override
    public RFuture<Boolean> trySetRateAsync(RateType type, long rate, long rateInterval, RateIntervalUnit unit) {
        return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                        //获取限流配置中rate、interval、type参数,限流配置存于limiterKey中
                        "local rate = redis.call('hget', KEYS[1], 'rate');"
                        + "local interval = redis.call('hget', KEYS[1], 'interval');"
                        + "local type = redis.call('hget', KEYS[1], 'type');"
                        //其中一个为空,则默认使用原来逻辑,存在值则不替换
                        + "if rate == false or interval == false or type == false then "
                            + "redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);"
                            + "redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);"
                            + "return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);"
                        + "end;"

                        //{limiterKey}:value作为key,存储剩余许可证数量
                        + "local valueName = KEYS[2];"
                        //{limiterKey}:permits作为key,记录所有许可发出的时间戳及当次申请的许可证个数
                        + "local permitsName = KEYS[4];"
                        //单机限流,则valueName、permitsName使用带有当前客户端标记作为key,如:{limiterKey}:value:b474c7d5-862c-4be2-9656-f4011c269d54
                        + "if type == '1' then "
                            + "valueName = KEYS[3];"
                            + "permitsName = KEYS[5];"
                        + "end;"
                        //rate、interval、type都不为空时,判断新配置只要有一个值有变动则采用新配置,并删除旧限流数据进行重置
                        + "if tonumber(rate) ~= tonumber(ARGV[1]) or tonumber(interval) ~= tonumber(ARGV[2]) or tonumber(type) ~= tonumber(ARGV[3]) then "
                            + "redis.call('hset', KEYS[1], 'rate', ARGV[1]);"
                            + "redis.call('hset', KEYS[1], 'interval', ARGV[2]);"
                            + "redis.call('hset', KEYS[1], 'type', ARGV[3]);"
                            + "return redis.call('del', valueName, permitsName);"
                        //新旧配置参数值都相等时,直接返回配置更新失败,返回0
                        //由于命令处理使用RedisCommands.EVAL_BOOLEAN,当返回值为1或"OK"时则为成功,其他存在的值则为失败,所以此处更新失败返回0
                        //可参考RedisCommands中的Convertor对象返回值的处理方式,如RedisCommands.EVAL_BOOLEAN对应的是BooleanReplayConvertor

                        + "else "
                            + "return 0;"
                        + "end;"
                ,
                Arrays.asList(getRawName(), getValueName(), getClientValueName(), getPermitsName(), getClientPermitsName())
                , rate, unit.toMillis(rateInterval), type.ordinal());
    }

    //getPermitsName、getClientPermitsName、getValueName、getClientValueName
    //由于父类RedissonRateLimiter中的访问权限是default,子类无访问权限,所以复制一份到子类即可
    String getPermitsName() {
        return suffixName(getRawName(), "permits");
    }
    String getClientPermitsName() {
        return suffixName(getPermitsName(), commandExecutor.getConnectionManager().getId());
    }
    String getValueName() {
        return suffixName(getRawName(), "value");
    }
    String getClientValueName() {
        return suffixName(getValueName(), commandExecutor.getConnectionManager().getId());
    }
}

2. trySetRateAsync流程

Redisson原理限流流程图-RedissonRateLimiterExpand-trySetRateAsync.png

3. RedissonRateLimiterExpand限流使用

Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redissonClient = Redisson.create(config);
//限流key
String limiterKey = "myRateLimiter";
//根据redissonClient获取RRateLimiter,并设置限流key,注意此处用的是RedissonRateLimiterExpand对象
RRateLimiter rateLimiter = RedissonRateLimiterExpand.create(redissonClient, "limiterKey");
//设置限流参数
//rateType:OVERALL全局限流、PER_CLIENT当前客户端应用限流(即单机限流,自动加上当前JAVA客户端ID做标记)
//rate:限流数量(总许可证数),即时间窗口内超出此数量则限流
//rateInterval:时间窗口间隔,即多少个时间单位内作为时间窗口
//rateIntervalUnit:时间窗口时间单位   
//以下举例配置说明:全局限流、2分钟时间窗口间隔最多支持4个访问数量
boolean trySetRateResult = rateLimiter.trySetRate(RateType.OVERALL, 4, 2, RateIntervalUnit.MINUTES);
//尝试申请许可证
//permits:本次申请的许可证数,不填时默认为1
boolean tryAcquireResult = rateLimiter.tryAcquire(1);
//申请成功执行业务代码
if(tryAcquireResult){
    doBusiness();
}
// 关闭 RedissonClient 连接
redissonClient.shutdown();

4. RedissonRateLimiterExpand 测试用例

Redisson限流配置变更重置限流测试用例.png

(1)首次执行结果截图

image.png

image.png

image.png

(2)二次执行结果截图

image.png

image.png

image.png

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值