写点什么

redis 实现分布式限流 结合 Lua 脚本,Java 开发还不会这些

用户头像
极客good
关注
发布于: 刚刚

-- 下标从 1 开始


local key = KEYS[1]


local now = tonumber(ARGV[1])


local ttl = tonumber(ARGV[2])


local expired = tonumber(ARGV[3])


-- 最大访问量


local max = tonumber(ARGV[4])


-- 清除过期的数据


-- 移除指定分数区间内的所有元素,expired 即已经过期的 score


-- 根据当前时间毫秒数 - 超时毫秒数,得到过期时间 expired


redis.call('zremrangebyscore', key, 0, expired)


-- 获取 zset 中的当前元素个数


local current = tonumber(redis.call('zcard', key))


local next = current + 1


if next > max then


-- 达到限流大小 返回 0


return 0;


else


-- 往 zset 中添加一个值、得分均为当前时间戳的元素,[value,score]


redis.call("zadd", key, now, now)


-- 每次访问均重新设置 zset 的过期时间,单位毫秒


redis.call("pexpire", key, ttl)


return next


end



切面类


@Slf4j


@Aspect


@Component


@RequiredArgsConstructor(onConstructor_ = @Autowired)


public class RateLimiterAspect {


private final static String SEPARATOR = ":";


private final static String REDIS_LIMIT_KEY_PREFIX = "limit:";


private final StringRedisTemplate stringRedisTemplate;


private final RedisScript<Long> limitRedisScript;


@Pointcut("@annotation(com.xkcoding.ratelimit.redis.annotation.RateLimiter)")


public void rateLimit() {


}


@Around("rateLimit()")


public Object pointcut(ProceedingJoinPoint point) throws Throwable {


MethodSignature signature = (MethodSignature) point.getSignature();


Method method = signature.getMethod();


RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class);


if (rateLimiter != null) {


String key = rateLimiter.key();


// 默认用类名+方法名做限流的 key 前缀


if (StrUtil.isBlank(key)) {


key = method.getDeclari


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


ngClass().getName() + StrUtil.DOT + method.getName();


}


String ipAddress = IpUtil.getIpAddr();


key = key + SEPARATOR + ipAddress.replaceAll(":", "-");


long max = rateLimiter.max();


long timeout = rateLimiter.timeout();


TimeUnit timeUnit = rateLimiter.timeUnit();


boolean limited = shouldLimited(key, max, timeout, timeUnit);


if (limited) {


throw new RuntimeException("手速太快了,慢点儿吧~");


}


}


return point.proceed();


}


private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) {


// 最终的 key 格式为:


// limit:自定义 key:IP


// limit:类名.方法名:IP


key = REDIS_LIMIT_KEY_PREFIX + key;


// 统一使用单位毫秒


long ttl = timeUnit.toMillis(timeout);


// 当前时间毫秒数


long now = Instant.now().toEpochMilli();


long expired = now - ttl;


// 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String


Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + "", ttl + "", expired + "", max + "");


if (executeTimes != null) {


if (executeTimes == 0) {


log.debug("【{}】在单位时间 {} 毫秒内已达到访问上限,当前接口上限 {}", key, ttl, max);


return true;


} else {


log.debug("【{}】在单位时间 {} 毫秒内访问 {} 次", key, ttl, executeTimes);


return false;


}


}


return false;


}

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
redis实现分布式限流 结合Lua脚本,Java开发还不会这些