Java接口限流案例如何实现

wen java案例 27

本文目录导读:

Java接口限流案例如何实现

  1. 基于RateLimiter的令牌桶限流
  2. 基于Spring AOP的注解限流
  3. 基于Redis的分布式限流
  4. 基于拦截器的限流实现
  5. 最佳实践建议

Java接口限流的实现方式有很多种,这里我为你梳理几种常用的方案,并附上核心代码示例。

基于RateLimiter的令牌桶限流

使用Guava的RateLimiter实现,适合单机限流。

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
@RestController
public class TokenBucketLimiterController {
    // 每秒生成2个令牌,即QPS限制为2
    private RateLimiter rateLimiter;
    @PostConstruct
    public void init() {
        rateLimiter = RateLimiter.create(2.0);
    }
    @GetMapping("/api/limit")
    public String limitApi() {
        // 尝试获取令牌,等待超时时间为500ms
        if (rateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS)) {
            return "请求成功,处理业务逻辑";
        } else {
            return "请求过于频繁,请稍后再试";
        }
    }
}

基于Spring AOP的注解限流

这种方式更灵活,可以自定义限流策略。

1 自定义限流注解

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
    // 每秒允许的请求数
    double limit() default 10.0;
    // 获取令牌的超时时间
    long timeout() default 500;
    // 超时时间单位
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    // 限流key,支持SpEL表达式
    String key() default "";
}

2 AOP切面实现

import com.google.common.util.concurrent.RateLimiter;
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.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
@Aspect
@Component
public class RateLimitAspect {
    // 存储每个方法对应的RateLimiter
    private final ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
    @Around("@annotation(com.example.annotation.RateLimit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RateLimit rateLimit = method.getAnnotation(RateLimit.class);
        if (rateLimit != null) {
            // 生成限流key
            String key = getKey(joinPoint, rateLimit);
            RateLimiter limiter = rateLimiterMap.computeIfAbsent(key, 
                k -> RateLimiter.create(rateLimit.limit()));
            // 尝试获取令牌
            if (limiter.tryAcquire(rateLimit.timeout(), rateLimit.timeUnit())) {
                return joinPoint.proceed();
            } else {
                throw new RuntimeException("请求过于频繁,请稍后再试");
            }
        }
        return joinPoint.proceed();
    }
    private String getKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
        StringBuilder key = new StringBuilder(joinPoint.getSignature().toString());
        // 支持SpEL表达式的key扩展
        if (!rateLimit.key().isEmpty()) {
            key.append(":").append(rateLimit.key());
        }
        return key.toString();
    }
}

3 使用示例

@RestController
public class UserController {
    @RateLimit(limit = 5, key = "#userId")
    @GetMapping("/user/{userId}")
    public String getUserInfo(@PathVariable String userId) {
        // 处理业务逻辑
        return "用户信息";
    }
    @RateLimit(limit = 1.0) // 每秒1次
    @PostMapping("/order/create")
    public String createOrder() {
        return "创建订单成功";
    }
}

基于Redis的分布式限流

适用于分布式系统,使用Redis Lua脚本保证原子性。

1 Redis Lua限流脚本

-- 限流脚本
local key = KEYS[1]           -- 限流key
local limit = tonumber(ARGV[1]) -- 最大请求数
local windowSize = tonumber(ARGV[2]) -- 窗口大小(秒)
local currentTime = tonumber(ARGV[3]) -- 当前时间戳
-- 移除过期的请求记录
redis.call('zremrangebyscore', key, 0, currentTime - windowSize * 1000)
-- 获取当前窗口内的请求数
local currentCount = redis.call('zcard', key)
if currentCount < limit then
    -- 添加当前请求
    redis.call('zadd', key, currentTime, currentTime .. math.random())
    redis.call('expire', key, windowSize)
    return 1  -- 允许通过
else
    return 0  -- 限流
end

2 分布式限流实现

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;
@Component
public class RedisRateLimiter {
    @Autowired
    private StringRedisTemplate redisTemplate;
    private DefaultRedisScript<Long> rateLimitScript;
    @PostConstruct
    public void init() {
        rateLimitScript = new DefaultRedisScript<>();
        rateLimitScript.setScriptText(
            "local key = KEYS[1]\n" +
            "local limit = tonumber(ARGV[1])\n" +
            "local windowSize = tonumber(ARGV[2])\n" +
            "local currentTime = tonumber(ARGV[3])\n\n" +
            "redis.call('zremrangebyscore', key, 0, currentTime - windowSize * 1000)\n\n" +
            "local currentCount = redis.call('zcard', key)\n\n" +
            "if currentCount < limit then\n" +
            "    redis.call('zadd', key, currentTime, currentTime .. math.random())\n" +
            "    redis.call('expire', key, windowSize)\n" +
            "    return 1\n" +
            "else\n" +
            "    return 0\n" +
            "end"
        );
        rateLimitScript.setResultType(Long.class);
    }
    public boolean tryAcquire(String key, int limit, int windowSize) {
        List<String> keys = Arrays.asList(key);
        long currentTime = System.currentTimeMillis();
        Long result = redisTemplate.execute(
            rateLimitScript,
            keys,
            String.valueOf(limit),
            String.valueOf(windowSize),
            String.valueOf(currentTime)
        );
        return result != null && result == 1;
    }
}
@RestController
public class RedisLimitController {
    @Autowired
    private RedisRateLimiter rateLimiter;
    @GetMapping("/api/redis-limit")
    public String redisLimitApi() {
        String userId = "user_123";
        String key = "rate_limit:" + userId;
        boolean allowed = rateLimiter.tryAcquire(key, 5, 1); // 每秒5次
        if (allowed) {
            return "分布式限流-请求成功";
        } else {
            return "请求过于频繁,请稍后再试";
        }
    }
}

基于拦截器的限流实现

通过Spring拦截器实现,适合全局限流。

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
    // 存储不同API的限流器
    private final ConcurrentHashMap<String, RateLimiter> limiters = new ConcurrentHashMap<>();
    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        // 根据不同接口配置不同限流策略
        RateLimiter limiter = limiters.computeIfAbsent(requestURI, uri -> {
            if (uri.contains("/api/important")) {
                return RateLimiter.create(1.0); // 重要接口每秒1次
            } else if (uri.contains("/api/common")) {
                return RateLimiter.create(10.0); // 普通接口每秒10次
            } else {
                return RateLimiter.create(5.0); // 默认每秒5次
            }
        });
        if (limiter.tryAcquire()) {
            return true;
        } else {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁\"}");
            return false;
        }
    }
}

最佳实践建议

  1. 选择适合的方案

    • 单机应用:使用RateLimiter
    • 分布式系统:使用Redis限流
    • 需要灵活配置:使用注解方式
  2. 限流粒度

    • 按接口限流
    • 按用户限流
    • 按IP限流
  3. 限流策略组合

    • 可以组合使用多个限流策略
    • 全局QPS限制 + 单个用户限制
  4. 降级处理

    • 限流后提供友好的提示信息
    • 可以使用降级服务或缓存数据
  5. 监控告警

    • 记录限流日志
    • 监控限流触发次数
    • 设置告警阈值

选择哪种方案取决于你的具体业务场景和系统架构,建议先确定需求,再选择合适的实现方式。

抱歉,评论功能暂时关闭!