最后
各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了
lua脚本
– 下标从 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 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.getDeclaringClass().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;
总结
我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。
面试题多多少少对于你接下来所要做的事肯定有点帮助,但我更希望你能透过面试题去总结自己的不足,以提高自己核心技术竞争力。每一次面试经历都是对你技术的扫盲,面试后的复盘总结效果是极好的!
)]