Spring Boot应用如何以键值对形式在缓存中存储数据实现用户限流
基于用户ID的Spring Boot限流实现方案
核心思路
利用分布式缓存(如Redis)存储用户请求计数,以用户ID为键,值为该用户的请求次数,同时为每个键设置1分钟的过期时间。每次处理用户请求时:
- 检查缓存中是否存在该用户ID的计数记录
- 无记录则初始化计数为1,并设置1分钟过期
- 有记录则将计数加1,若超过5次则返回限流错误,否则更新缓存计数
具体实现步骤
1. 引入缓存依赖
如果使用Redis作为缓存,在pom.xml中添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2. 自定义限流注解与AOP切面
通过注解+AOP的方式实现无侵入式限流:
自定义限流注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UserRateLimit { int maxCount() default 5; // 每分钟最大请求次数 long expireSeconds() default 60; // 过期时间(秒) }
AOP切面逻辑实现
@Component @Aspect public class UserRateLimitAspect { @Autowired private StringRedisTemplate redisTemplate; @Around("@annotation(rateLimit)") public Object handleRateLimit(ProceedingJoinPoint joinPoint, UserRateLimit rateLimit) throws Throwable { // 从请求上下文获取当前用户ID(根据实际业务调整,比如从Token、Session中解析) String userId = getCurrentUserId(); if (userId == null) { throw new IllegalArgumentException("用户身份验证失败"); } String cacheKey = "rate_limit:user:" + userId; ValueOperations<String, String> valueOps = redisTemplate.opsForValue(); String countStr = valueOps.get(cacheKey); if (countStr == null) { // 首次请求,初始化计数与过期时间 valueOps.set(cacheKey, "1", rateLimit.expireSeconds(), TimeUnit.SECONDS); } else { int currentCount = Integer.parseInt(countStr); if (currentCount >= rateLimit.maxCount()) { throw new RuntimeException("1分钟内搜索次数已达上限,请稍后再试"); } // 计数加1 valueOps.increment(cacheKey); } return joinPoint.proceed(); } // 示例:从请求头获取用户ID,实际项目需根据登录机制调整 private String getCurrentUserId() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request.getHeader("X-User-Id"); } }
3. 在业务接口上应用注解
在商品搜索接口方法上添加限流注解:
@RestController @RequestMapping("/goods") public class GoodsSearchController { @UserRateLimit(maxCount = 5, expireSeconds = 60) @GetMapping("/search") public ResponseEntity<Map<String, Object>> search(@RequestParam String keyword) { // 商品搜索业务逻辑 Map<String, Object> result = new HashMap<>(); result.put("code", 200); result.put("data", "搜索结果:" + keyword); return ResponseEntity.ok(result); } }
关键注意事项
- 分布式场景适配:多实例部署时必须使用Redis等分布式缓存,不能用本地缓存(如Caffeine),否则各实例计数不统一。
- 异常统一处理:可以自定义限流异常类,通过全局异常处理器返回标准化的JSON错误响应,避免直接抛出RuntimeException。
- 用户ID可靠性:确保用户ID的获取逻辑稳定,比如登录用户从JWT Token、Spring Security上下文解析,避免出现空值或错误标识。
内容的提问来源于stack exchange,提问作者Nihar Ranjan Khatua
相关产品推荐
相关产品推荐

