test content
在高并发场景中,接口防抖与限流是保护系统的两道重要防线。本文结合实战,讲解如何用Redis+Spring Boot实现一套灵活的接口保护机制。## 一、为什么需要防抖与限流?防抖(Debounce) :用户连续点击提交按钮,只执行最后一次请求。比如点赞、评论场景,防止重复提交。限流(Rate Limit) :限制接口在单位时间内的调用次数。比如秒杀、抢票场景,保护后端服务不被冲垮。两者相辅相成:防抖解决「用户手速」问题,限流解决「流量洪峰」问题。 ## 二、技术方案设计### 2.1 引入依赖
xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId></dependency>### 2.2 自定义注解 @RateLimitjava@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface RateLimit { // 限流key前缀 String key() default "rate:limit:"; // 时间窗口(秒) int window() default 60; // 最大请求次数 int maxRequests() default 100; // 是否启用防抖 boolean debounce() default false;}### 2.3 Redis+Lua脚本实现原子操作java@Componentpublic class RateLimitService { @Autowired private StringRedisTemplate redisTemplate; private static final String LUA_SCRIPT = "local key = KEYS[1]\n" + "local limit = tonumber(ARGV[1])\n" + "local window = tonumber(ARGV[2])\n" + "local current = redis.call('get', key)\n" + "if current and tonumber(current) >= limit then\n" + " return 0\n" + "end\n" + "current = redis.call('incr', key)\n" + "if tonumber(current) == 1 then\n" + " redis.call('expire', key, window)\n" + "end\n" + "return 1"; public boolean isAllowed(String key, int limit, int window) { DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class); Long result = redisTemplate.execute(script, Collections.singletonList(key), String.valueOf(limit), String.valueOf(window)); return result != null && result == 1; }}### 2.4 AOP切面统一处理java@Aspect@Component@Slf4jpublic class RateLimitAspect { @Autowired private RateLimitService rateLimitService; @Around("@annotation(rateLimit)") public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable { String key = rateLimit.key() + getClientIp(); // 限流检查 if (!rateLimit.debounce()) { if (!rateLimitService.isAllowed(key, rateLimit.maxRequests(), rateLimit.window())) { throw new BusinessException("请求太频繁,请稍后再试"); } } else { // 防抖:使用Redis SETNX保证只有一个请求被执行 String debounceKey = "debounce:" + key; Boolean success = redisTemplate.opsForValue().setIfAbsent( debounceKey, "1", Duration.ofSeconds(rateLimit.window())); if (!success) { return Result.fail("操作太频繁"); } } return joinPoint.proceed(); } private String getClientIp() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request.getRemoteAddr(); }}## 三、使用示例java@RestControllerpublic class OrderController { // 限流:60秒内最多访问200次 @RateLimit(key = "order:create:", window = 60, maxRequests = 200) @PostMapping("/api/order/create") public Result createOrder(@RequestBody OrderDTO order) { // 业务逻辑 return Result.success(orderService.create(order)); } // 防抖:点赞场景,5秒内只执行一次 @RateLimit(key = "post:like:", window = 5, maxRequests = 1, debounce = true) @PostMapping("/api/post/like") public Result likePost(@RequestParam Long postId) { return Result.success(postService.like(postId)); }}## 四、线上部署注意事项1. Redis集群部署 :单机Redis在极端高并发下可能成为瓶颈2. 限流策略分级 :普通用户 vs VIP用户,差异化限流3. 降级策略 :Redis不可用时,自动放行请求,避免服务不可用4. 监控告警 :接入Prometheus,监控限流触发次数## 五、总结通过@RateLimit注解+AOP+Redis Lua脚本,我们实现了一套轻量、可复用、易配置 的接口保护方案:| 特性 | 防抖 | 限流 ||------|------|------|| 解决问题 | 重复提交 | 流量冲击 || 核心机制 | SETNX | INCR+EXPIRE || 适用场景 | 点赞、评论 | 秒杀、查询 |希望这篇实战指南对你有帮助!有问题欢迎评论区交流~---往期推荐: - Spring Boot 异步任务详解- Redis分布式锁的七种避坑指南