接口限流的两种方式

需求:单位时间内,比如1s,限制某个接口的调用次数。

RateLimiter

RateLimiter基于令牌桶算法,有一个令牌桶,单位时间内令牌会以恒定的数量(即令牌的加入速度)加入到令牌桶中,所有请求都需要获取令牌才可正常访问。当令牌桶中没有令牌可取的时候,则拒绝请求。
定义一个接口:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {

    //每秒限制次数
    double limit() default 1;

}

定义一个切面:

@Slf4j
@Component
@Aspect
public class RateLimitAspect {

    private static final ConcurrentMap<String, RateLimiter> rateLimiterCache = new ConcurrentHashMap<>();

    @Pointcut("@annotation(com.example.demo.RateLimit)")
    public void rateLimit() {
    }

    @Around("rateLimit()")
    public Object pointcut(ProceedingJoinPoint joinPoint) throws Throwable {
        Object obj = null;
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RateLimit rateLimit = AnnotationUtils.findAnnotation(method, RateLimit.class);
        if (rateLimit != null) {
            if (rateLimiterCache.get(method.getName()) == null) {
                rateLimiterCache.put(method.getName(), RateLimiter.create(rateLimit.limit()));
            }
            RateLimiter rateLimiter = rateLimiterCache.get(method.getName());
            if (rateLimiter != null) {
                if (!rateLimiter.tryAcquire()) {
                    log.info("=====完了没抢到...=====");
                } else {
                    log.info("=====我抢到了!!=====");
                    obj = joinPoint.proceed();
                }
            }
        }
        return obj;
    }
}

controller:

@Slf4j
@RestController
public class TestController {

    @RateLimit(limit = 5)
    @GetMapping("/test")
    public String test() {
        return "请求成功";
    }
}

Redis

利用了Redis的自增和过期时间

@Slf4j
@RestController
public class TestController {

    private int countLimit = 5;

    private int timeLimit = 1;

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    
    @GetMapping("/test")
    public String test(String key) {

        if (stringRedisTemplate.hasKey(key)) {
            long count = stringRedisTemplate.boundValueOps(key).increment(1);
            if (count > countLimit) {
                log.info("请求过于繁忙");
            }

        } else {
            stringRedisTemplate.opsForValue().set(key, "1", timeLimit, TimeUnit.SECONDS);
        }

        return "请求成功";
    }
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理