Redis 应用-简单限流应用(每段时间内限制最多某次数)问题

Redis限流策略与原子性操作实现
本文探讨了两种基于Redis的限流策略:一是利用Redis的自增和过期策略,但指出该方法存在非原子性问题;二是通过Redis的list集合结合系统时间判断,确保在指定时间周期内的操作次数限制。两种策略都旨在防止恶意登录或方法调用过于频繁。

1、方案 利用 redis自增和expire 过期策略

该方案存在不是原子性操作的小瑕疵


/**
*time 时间间隔
*maxCount 最大次数
*/
public Boolean checkMethodInvokeLimit(String caller, String method, int time, int maxCount,Integer sysType) {
        if (StringUtils.isBlank(caller) || StringUtils.isBlank(method) || time < 1 || maxCount < 1) {
            return false;
        }
        try {
            String key = CacheConfCenter.MethodCheck.METHOD_LLMIT_CHECK.getKey() + sysType + ":" + caller + ":" + method;
            long count = redisSentinelService.incr(key);
            if (count == 1) {
                redisSentinelService.expire(key, time); //redis自增 与expire 命令 不是同时使用,可能存在 多线程下的错误问题。 解决方案:改用lua脚本执行 原子性操作命令
                return true;
            } else if (count > maxCount) {
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
            LOGGER.error("校验方法调用次数限制异常,caller:{},method:{}", caller, method, e);
            return true;
        }
    }

2、利用redis 的list 集合+系统时间判断策略

代码逻辑:

1、先判断redis 该key 的list 长度是否不超过最大次数,是则跳到2 否则跳到3

2、不超过最大次数,则把该值 rpush 到list

3、若长度超过 则判断时间 是否是该时间周期内  若是则跳到4,否则跳到5

4、直接返回不允许操作逻辑

5、不在该时间周期内允许操作,移除掉list 中最老的(lpop),rpush 当前时间最新值。保证长度是最多限制次数。

/**
 * @author
 * @date 2020/5/12 21:30
 * @since V1.0
 */
public class LoginValidate {
    //设置成10s是因为方便测试
    //周期时间为毫秒
    final static int EXPIRE_TIME_MILLISECOND = 10*1000;
    //一个周期内最多的操作次数
    final static int MOST_TIMES_IN_TIME_LIMIT = 5;
    /**
     * 用Redis和任意语言实现一段恶意登录保护的代码,限制1小时内每用户Id最多只能登录5次
     *
     * @param jedis
     * @param userId
     * @return
     */
    public static boolean loginValidateWithQueue(Jedis jedis, String userId) {
        Long currentTime = System.currentTimeMillis();
        //1.判断List列表长度是否超过操作次数
        if (jedis.llen(userId) < MOST_TIMES_IN_TIME_LIMIT) {
            //2.将当前操作时间放入到用户list中
            jedis.rpush(userId, currentTime.toString());
            System.out.println(currentTime + " 登入成功!" + jedis.llen(userId));
            return true;
        } else {
            //3.如果超过操作次数则取出list中最早操作时间,即list下标为0的
            String earliestTimeStr = jedis.lindex(userId, 0);
            //将字符串转为长整形
            Long earliestTime = Long.valueOf(earliestTimeStr);
            //4.判断最早操作时间是否在周期内,如果在周期内说明在这个周期内操作数已到阀值不允许再操作。
            if (currentTime - earliestTime <= EXPIRE_TIME_MILLISECOND) {
                System.out.println(EXPIRE_TIME_MILLISECOND/1000 + " 秒内只能操作" + MOST_TIMES_IN_TIME_LIMIT + " 次");
                return false;
            } else {
                //5.如果最早操作时间不再周期,说明可以继续操作。
                //为了使用户列表长度为操作次数。需要删除最早的操作时间,保存本次操作时间。
                //6.删除列表尾段数据,即最早操作时间。
                jedis.lpop(userId);
                //7.插入当前操作时间
                jedis.rpush(userId, currentTime.toString());
                System.out.println(currentTime + " 登入成功!" + jedis.llen(userId));
                return true;
            }
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值