JavaJavaSpringBoot实现接口防刷
violet在SpringBoot中实现接口防刷(限流)可以有效保护系统免受恶意攻击或过度请求,以下是几种常见的实现方案:
一、基于Redis的令牌桶算法(推荐)
令牌桶算法允许一定程度的突发请求,适合大多数业务场景。通过Redis实现分布式限流:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component;
import java.util.Arrays; import java.util.List;
@Component public class RateLimiter {
@Autowired private RedisTemplate<String, Object> redisTemplate;
private static final String TOKEN_BUCKET_SCRIPT = "local tokens_key = KEYS[1]\n" + "local timestamp_key = KEYS[2]\n" + "local rate = tonumber(ARGV[1])\n" + "local capacity = tonumber(ARGV[2])\n" + "local now = tonumber(ARGV[3])\n" + "local requested = tonumber(ARGV[4])\n" + "local last_tokens = tonumber(redis.call('get', tokens_key) or capacity)\n" + "local last_refreshed = tonumber(redis.call('get', timestamp_key) or 0)\n" + "local delta = math.max(0, now - last_refreshed)\n" + "local filled_tokens = math.min(capacity, last_tokens + (delta * rate / 1000))\n" + "local allowed = filled_tokens >= requested\n" + "local new_tokens = filled_tokens\n" + "if allowed then\n" + " new_tokens = filled_tokens - requested\n" + "end\n" + "redis.call('set', tokens_key, new_tokens)\n" + "redis.call('set', timestamp_key, now)\n" + "return allowed";
private final RedisScript<Boolean> script = new DefaultRedisScript<>(TOKEN_BUCKET_SCRIPT, Boolean.class);
public boolean isAllowed(String key, int rate, int capacity) { List<String> keys = Arrays.asList(key + ".tokens", key + ".timestamp"); long now = System.currentTimeMillis(); return redisTemplate.execute(script, keys, rate, capacity, now, 1); } }
|
使用示例:
@RestController @RequestMapping("/api") public class UserController {
@Autowired private RateLimiter rateLimiter;
@GetMapping("/user/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { String limitKey = "user:" + id; if (!rateLimiter.isAllowed(limitKey, 2, 10)) { return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); } return ResponseEntity.ok(userService.getUserById(id)); } }
|
二、基于Spring AOP的注解式限流
通过自定义注解和AOP实现更优雅的限流控制:
1. 定义限流注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimit { int rate() default 10; int capacity() default 100; String key() default ""; }
|
2. AOP切面实现
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method;
@Aspect @Component public class RateLimitAspect {
@Autowired private RateLimiter rateLimiter;
@Around("@annotation(com.example.demo.RateLimit)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); RateLimit rateLimit = method.getAnnotation(RateLimit.class);
String key = generateKey(rateLimit, joinPoint);
if (!rateLimiter.isAllowed(key, rateLimit.rate(), rateLimit.capacity())) { throw new RuntimeException("请求过于频繁,请稍后再试"); }
return joinPoint.proceed(); }
private String generateKey(RateLimit rateLimit, ProceedingJoinPoint joinPoint) { if (!"".equals(rateLimit.key())) { return rateLimit.key(); } HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String ip = request.getRemoteAddr(); String methodName = joinPoint.getSignature().getName(); return "rate_limit:" + ip + ":" + methodName; } }
|
3. 使用注解限流
@RestController @RequestMapping("/api") public class OrderController {
@RateLimit(rate = 5, capacity = 20) @PostMapping("/order") public ResponseEntity<?> createOrder(@RequestBody OrderDTO order) { return ResponseEntity.ok().build(); } }
|
三、基于Sentinel的流量控制
集成Alibaba Sentinel实现更强大的限流、熔断和降级:
1. 添加依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-starter</artifactId> <version>1.8.6</version> </dependency>
|
2. 配置规则
import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List;
@Configuration public class SentinelConfig {
@PostConstruct public void initFlowRules() { List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("/api/user/*"); rule.setCount(20); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setLimitApp("default"); rules.add(rule);
FlowRuleManager.loadRules(rules); } }
|
3. 处理限流异常
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@Component public class SentinelBlockHandler implements BlockExceptionHandler {
@Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception { response.setContentType("application/json;charset=UTF-8"); if (ex instanceof FlowException) { response.setStatus(429); response.getWriter().print("{\"code\":429,\"message\":\"请求过于频繁\"}"); } else { response.setStatus(500); response.getWriter().print("{\"code\":500,\"message\":\"系统繁忙,请稍后再试\"}"); } } }
|
四、基于Guava的本地限流(单机环境)
如果是单机应用,可使用Guava的RateLimiter:
import com.google.common.util.concurrent.RateLimiter; import org.springframework.stereotype.Component;
@Component public class LocalRateLimiter {
private final RateLimiter rateLimiter = RateLimiter.create(10.0);
public boolean tryAcquire() { return rateLimiter.tryAcquire(); } }
|
使用示例:
@RestController @RequestMapping("/api") public class TestController {
@Autowired private LocalRateLimiter localRateLimiter;
@GetMapping("/test") public ResponseEntity<?> test() { if (!localRateLimiter.tryAcquire()) { return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); } return ResponseEntity.ok().build(); } }
|
五、总结与最佳实践
- 分布式系统推荐方案:Redis + 令牌桶算法(方案一)或 Sentinel(方案三)
- 单机应用推荐方案:Guava RateLimiter(方案四)
- 注解式开发:结合AOP和自定义注解(方案二)提升代码可维护性
- 监控与告警:Sentinel提供可视化监控界面,方便查看限流情况和调整规则
- 熔断与降级:复杂场景下,考虑结合熔断机制(如Sentinel的熔断策略)防止级联故障
根据系统规模和需求选择合适的限流方案,通常分布式系统优先考虑Redis或Sentinel,而中小型应用可使用Guava实现简单限流。