Spring Boot Redis 限流 自定义注解

在分布式系统中,限流是一种常见的应用场景,它可以保护系统免受过多的请求压力。在这篇文章中,我们将介绍如何使用Spring Boot和Redis实现一个简单的限流功能,并通过自定义注解来简化代码的编写。

限流算法

在开始介绍具体实现之前,我们先来了解一下常见的限流算法。限流算法的目标是限制对系统的访问速率,以防止系统负载过高而导致的性能下降或服务不可用。

计数器算法

计数器算法是一种基本的限流算法,它简单地统计在一段时间内进入系统的请求数量,并与设定的阈值进行比较。如果超过阈值,则拒绝进入系统的请求。该算法的缺点是无法应对瞬时流量的突增,因为在计数器达到阈值之前,所有请求都会被接受。

滑动窗口算法

滑动窗口算法是一种更加灵活的限流算法,它将时间划分为固定大小的窗口,并在每个窗口内计数请求的数量。随着时间的推移,旧的窗口被移除,新的窗口被添加。如果某个窗口内的请求数量超过阈值,则拒绝进入系统的请求。该算法可以平滑处理瞬时流量的突增,但需要更复杂的数据结构来记录窗口内的请求数量。

使用Spring Boot和Redis实现限流

现在让我们开始使用Spring Boot和Redis实现一个简单的限流功能。

首先,我们需要添加以下依赖到我们的pom.xml文件中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

接下来,我们需要配置Redis连接信息和Redis模板的Bean。在application.properties文件中添加以下配置:

spring.redis.host=127.0.0.1
spring.redis.port=6379

然后,我们创建一个名为RateLimiter的接口,并定义一个名为limit的方法。这个方法将用于限制对某个方法的访问速率。我们将使用自定义注解@RateLimit来标注需要进行限流的方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    int value() default 100;
}

接下来,我们创建一个切面类RateLimitAspect,它将拦截带有@RateLimit注解的方法,并进行限流处理。

@Aspect
@Component
public class RateLimitAspect {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        // 获取目标方法的签名信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取目标方法
        Method method = signature.getMethod();
        // 获取目标方法所属的类
        Class<?> targetClass = method.getDeclaringClass();
        // 构造限流的Key
        String key = targetClass.getName() + "." + method.getName();

        // 获取限流阈值
        int limit = rateLimit.value();

        // 获取当前时间戳
        long timestamp = System.currentTimeMillis();

        // 获取当前窗口的起始时间戳
        long windowStart = timestamp - 60 * 1000; // 1分钟

        // 统计窗口内的请求数量
        String count = redisTemplate.opsForValue().get(key);
        if (count == null) {
            // 如果之前没有记录,则初始化为1
            redisTemplate.opsForValue().setIfAbsent(key, "1");
        } else {
            // 如果之前有记录,则递增1
            redisTemplate.opsForValue().increment(key, 1);
        }

        // 获取窗口内的请求数量
        int windowCount = Integer.parseInt(redisTemplate.opsForValue().get(key));

        if (windowCount > limit) {
            // 如果窗口内的请求数量超过阈