SpringBoot中基于AOP和Semaphore实现API限流

文摘   2024-10-18 20:26   广东  

限流是 Web API 中的常见要求,旨在防止滥用并确保公平使用资源。借助Spring Boot 中的 AOP,我们可以通过拦截方法调用并限制在特定时间范围内允许的请求数量来实现速率限制。

为了在 Spring Boot 中使用 AOP 实现速率限制:

  • 定义自定义注释来标记应该限速的方法。
  • 创建一个方面类,拦截用自定义注释注释的方法调用。
  • 使用速率限制器组件来跟踪和执行速率限制。
  • 处理速率限制超出的情况,如通过抛出自定义异常。

Spring Boot API 中的速率限制

可以使用各种技术在 Spring Boot API 中实现速率限制。一种常见的方法是使用 Spring AOP来拦截传入的请求并实施速率限制。

步骤 1 - 定义速率限制配置

创建一个配置类,在其中定义速率限制参数,例如允许的请求数和时间段。

@Configuration
public class RateLimitConfig {
    @Value("${rate.limit.requests}")
    private int requests;

    @Value("${rate.limit.seconds}")
    private int seconds;

    // Getters and setters
}
第 2 步 - 创建速率限制方面

使用 Spring AOP 实现一个方面来拦截方法调用并强制执行速率限制。

@Aspect
@Component
public class RateLimitAspect {
    @Autowired
    private RateLimitConfig rateLimitConfig;

    @Autowired
    private RateLimiter rateLimiter;

    @Around("@annotation(RateLimited)")
    public Object enforceRateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
        String key = getKey(joinPoint);
        if (!rateLimiter.tryAcquire(key, rateLimitConfig.getRequests(), rateLimitConfig.getSeconds())) {
            throw new RateLimitExceededException("Rate limit exceeded");
        }
        return joinPoint.proceed();
    }

    private String getKey(ProceedingJoinPoint joinPoint) {
        //为正在调用的方法生成唯一密钥
        //方法签名、用户ID、IP地址等。

    }
}
步骤 3 — 定义 RateLimited 注释

创建自定义注释来标记应受速率限制的方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
  public @interface RateLimited {
}
步骤 4 - 实施速率限制器

创建速率限制器组件,使用令牌桶算法或任何其他合适的算法来管理速率限制。

@Component
public class RateLimiter {
    private final Map<String,RateLimitedSemaphore> semaphores = new ConcurrentHashMap<>();

    public boolean tryAcquire(String key, int requests, int seconds) {
        
        long currentTime = System.currentTimeMillis();

        // 计算时间窗口
        long startTime = currentTime - seconds * 1000;

        // 过期删除
        cleanupExpiredEntries(startTime);

        // 获取semaphore 
        RateLimitedSemaphore semaphore = semaphores.computeIfAbsent(key, k -> {
            RateLimitedSemaphore newSemaphore = new RateLimitedSemaphore(requests);
            newSemaphore.setLastAcquireTime(currentTime); // Set last acquire time
            return newSemaphore;
        });

        // 校验 semaphore 
        boolean acquired = semaphore.tryAcquire();
        if (acquired) {
            semaphore.setLastAcquireTime(currentTime); 
            // 更新
        }
        return acquired;
    }

    private void cleanupExpiredEntries(long startTime) {
        Iterator<Map.Entry<String, RateLimitedSemaphore>> iterator = semaphores.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, RateLimitedSemaphore> entry = iterator.next();
            String key = entry.getKey();
            RateLimitedSemaphore semaphore = entry.getValue();
            if (semaphore.getLastAcquireTime() < startTime) {
                iterator.remove();
            }
        }
    }

    private class RateLimitedSemaphore extends Semaphore {
        private volatile long lastAcquireTime;

        public RateLimitedSemaphore(int permits) {
            super(permits);
        }

        public long getLastAcquireTime() {
            return lastAcquireTime;
        }

        public void setLastAcquireTime(long lastAcquireTime) {
            this.lastAcquireTime = lastAcquireTime;
        }
    }
}
步骤 5 - 注释控制器方法

用注解来注释应该进行速率限制的控制器方法 @RateLimited。

@RestController
public class MyController {
    @RateLimited
    @GetMapping("/api/resource")
    public ResponseEntity<String> getResource() {
        // Implementation
    }
}
步骤 6 - 配置速率限制属性

application.properties在您的 或  中配置速率限制属性 application.yml。

rate.limit.requests=10
rate.limit.seconds=60

要按 IP 地址限制请求,可以从传入请求中提取 IP 地址并将其用作速率限制的密钥:

private String getKey(HttpServletRequest request) {
    String ipAddress = request.getRemoteAddr();
    return ipAddress; //用ID做key
}

还需要修改enforceRateLimit 中的方法 RateLimitAspect 以将对象传递 HttpServletRequest 给 getKey 方法:

@Around("@annotation(RateLimited)")
public Object enforceRateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
   
    ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = requestAttributes.getRequest();

    String key = getKey(request);
    if (!rateLimiter.tryAcquire(key, rateLimitConfig.getRequests(), rateLimitConfig.getSeconds())) {
        throw new RateLimitExceededException("Rate limit exceeded");
    }
    return joinPoint.proceed();
}

太强 ! SpringBoot中出入参增强的5种方法 : 加解密、脱敏、格式转换、时间时区处理

太强 ! SpringBoot中优化if-else语句的七种绝佳方法实战

SpringBoot使用EasyExcel并行导出多个excel文件并压缩zip下载
提升编程效率的利器: Google Guava库中双向映射BitMap
从MySQL行格式原理看:为什么开发规范中不推荐NULL?数据是如何在磁盘上存储的?
SpringBoot中使用Jackson实现自定义序列化和反序列化控制的5种方式总结

提升编程效率的利器: Google Guava库之RateLimiter优雅限流

深入JVM逃逸分析原理:且看其如何提高程序性能和内存利用率

必知必会!MySQL索引下推:原理与实战

深入解析JVM内存分配优化技术:TLAB

SpringBoot中基于JWT的双token(access_token+refresh_token)授权和续期方案
SpringBoot中基于JWT的单token授权和续期方案
SpringBoot中Token登录授权、续期和主动终止的方案(Redis+Token)
微服务中token鉴权设计的4种方式总结
提升编程效率的API利器:精通Google Guava库区间范围映射RangeMap
SpringBoot中Jackson控制序列化和反序列化的注解和扩展点总结【收藏版】

SpringBoot中大量数据导出方案:使用EasyExcel并行导出多个excel文件并压缩zip后下载

提升编程效率的API利器:精通Google Guava库之IO工具类
提升编程效率的API利器:精通Google Guava库二维映射表Table
提升编程效率的API利器:精通Google Guava库区间范围映射RangeMap
提升编程效率的利器: Google Guava库中双向映射BitMap
提升编程效率的利器: Google Guava库之RateLimiter优雅限流
基于Guava布隆过滤器的海量字符串高效去重实践
加密算法理论总结:分类与典型算法

关注『 码到三十五 』,日有所获
                     点赞 和 在看 就是最大的支持

码到三十五
主要分享正经的开发技术(原理,架构,实践,源码等),以输出驱动输入;当然偶尔会穿插点生活琐碎,顺便吃个瓜,目的嘛,搞点精准流量,看能不能发发广告。
 最新文章