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

被折叠的 条评论
为什么被折叠?



