Java案例如何实现接口限流?

wen python案例 3

本文目录导读:

Java案例如何实现接口限流?

  1. 基于令牌桶算法(推荐)
  2. 基于滑动窗口算法
  3. 基于Redis实现(分布式限流)
  4. Spring Boot集成配置
  5. 使用示例
  6. 性能对比与选择建议
  7. 注意事项

我来详细解释Java中实现接口限流的几种常见方式。

基于令牌桶算法(推荐)

使用Guava RateLimiter

import com.google.common.util.concurrent.RateLimiter;
import org.springframework.web.bind.annotation.*;
@RestController
public class TokenBucketLimiterController {
    // 每秒生成2个令牌
    private final RateLimiter rateLimiter = RateLimiter.create(2.0);
    @GetMapping("/api/limited")
    public String limitedApi() {
        // 尝试获取令牌,非阻塞
        if (rateLimiter.tryAcquire()) {
            return "请求成功";
        } else {
            return "请求过于频繁,请稍后再试";
        }
    }
}

自定义AOP实现

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    // 每秒允许的请求数
    double permitsPerSecond() default 1.0;
    // 等待超时时间
    long timeout() default 0;
    // 超时时间单位
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
@Aspect
@Component
public class RateLimitAspect {
    // 存储每个接口的限流器
    private final ConcurrentHashMap<String, RateLimiter> limiterMap = new ConcurrentHashMap<>();
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        String methodName = joinPoint.getSignature().toShortString();
        // 获取或创建限流器
        RateLimiter limiter = limiterMap.computeIfAbsent(
            methodName, 
            k -> RateLimiter.create(rateLimit.permitsPerSecond())
        );
        // 尝试获取令牌
        boolean acquired = limiter.tryAcquire(
            rateLimit.timeout(), 
            rateLimit.timeUnit()
        );
        if (!acquired) {
            throw new RuntimeException("请求过于频繁,请稍后重试");
        }
        return joinPoint.proceed();
    }
}

基于滑动窗口算法

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class SlidingWindowLimiter {
    // 窗口大小(毫秒)
    private final long windowSize;
    // 窗口内的最大请求数
    private final int maxRequests;
    // 子窗口数量
    private final int subWindows;
    // 子窗口大小
    private final long subWindowSize;
    // 存储子窗口的请求计数
    private final TreeMap<Long, AtomicInteger> windowMap;
    public SlidingWindowLimiter(long windowSize, int maxRequests, int subWindows) {
        this.windowSize = windowSize;
        this.maxRequests = maxRequests;
        this.subWindows = subWindows;
        this.subWindowSize = windowSize / subWindows;
        this.windowMap = new TreeMap<>();
    }
    public synchronized boolean tryAcquire() {
        long currentTime = System.currentTimeMillis();
        long subWindowKey = currentTime / subWindowSize * subWindowSize;
        // 清理过期窗口
        cleanExpiredWindows(currentTime);
        // 获取当前子窗口或创建新窗口
        AtomicInteger count = windowMap.computeIfAbsent(
            subWindowKey, 
            k -> new AtomicInteger(0)
        );
        // 计算当前总请求数
        int totalCount = windowMap.values().stream()
            .mapToInt(AtomicInteger::get)
            .sum();
        if (totalCount >= maxRequests) {
            return false;
        }
        count.incrementAndGet();
        return true;
    }
    private void cleanExpiredWindows(long currentTime) {
        long expiredKey = currentTime - windowSize;
        windowMap.headMap(expiredKey, true).clear();
    }
}

基于Redis实现(分布式限流)

Redis Lua脚本实现

-- Lua脚本:令牌桶算法
local key = KEYS[1]  -- 限流key
local rate = tonumber(ARGV[1])  -- 每秒速率
local capacity = tonumber(ARGV[2])  -- 桶容量
local now = tonumber(ARGV[3])  -- 当前时间
local requested = tonumber(ARGV[4])  -- 请求数量
-- 获取当前令牌数和最后刷新时间
local refill_time = tonumber(redis.call('hget', key, 'refill_time') or 0)
local tokens = tonumber(redis.call('hget', key, 'tokens') or capacity)
-- 计算需要补充的令牌
local elapsed = math.max(0, now - refill_time)
local tokens_to_add = math.floor(elapsed * rate)
tokens = math.min(capacity, tokens + tokens_to_add)
-- 判断是否允许请求
local allowed = 0
if tokens >= requested then
    tokens = tokens - requested
    allowed = 1
end
-- 更新状态
redis.call('hmset', key, 'tokens', tokens, 'refill_time', now)
redis.call('pexpire', key, 1000)  -- 设置1秒过期
return allowed

分布式限流工具类

import redis.clients.jedis.Jedis;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
@Component
public class RedisRateLimiter {
    private final StringRedisTemplate redisTemplate;
    private final DefaultRedisScript<Long> limitScript;
    public RedisRateLimiter(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        // 加载Lua脚本
        String script = 
            "local key = KEYS[1]\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 refill_time = tonumber(redis.call('hget', key, 'refill_time') or 0)\n" +
            "local tokens = tonumber(redis.call('hget', key, 'tokens') or capacity)\n" +
            "local elapsed = math.max(0, now - refill_time)\n" +
            "local tokens_to_add = math.floor(elapsed * rate)\n" +
            "tokens = math.min(capacity, tokens + tokens_to_add)\n" +
            "local allowed = 0\n" +
            "if tokens >= requested then\n" +
            "    tokens = tokens - requested\n" +
            "    allowed = 1\n" +
            "end\n" +
            "redis.call('hmset', key, 'tokens', tokens, 'refill_time', now)\n" +
            "redis.call('pexpire', key, 1000)\n" +
            "return allowed";
        this.limitScript = new DefaultRedisScript<>(script, Long.class);
    }
    public boolean tryAcquire(String key, double rate, int capacity) {
        Long result = redisTemplate.execute(
            limitScript,
            Arrays.asList(key),
            String.valueOf(rate),
            String.valueOf(capacity),
            String.valueOf(System.currentTimeMillis()),
            String.valueOf(1)
        );
        return result != null && result == 1L;
    }
}

Spring Boot集成配置

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy
public class RateLimitApplication {
    public static void main(String[] args) {
        SpringApplication.run(RateLimitApplication.class, args);
    }
    @Bean
    public RateLimitAspect rateLimitAspect() {
        return new RateLimitAspect();
    }
}

使用示例

@RestController
@RequestMapping("/api")
public class DemoController {
    @RateLimit(permitsPerSecond = 5.0, timeout = 100)
    @GetMapping("/test")
    public String testLimitedApi() {
        return "请求成功";
    }
}

性能对比与选择建议

方案 优点 缺点 适用场景
Guava RateLimiter 实现简单,性能高 不支持分布式 单机应用
滑动窗口 更精确,防止突发流量 实现复杂,资源消耗大 对精度要求高的场景
Redis分布式限流 支持分布式,配置灵活 网络开销较大 微服务架构,分布式系统

注意事项

  1. 限流粒度:根据业务需求选择接口级别、用户级别或IP级别限流
  2. 异常处理:当触发限流时,返回合适的HTTP状态码(如429 Too Many Requests)
  3. 监控告警:配合监控系统,及时发现限流触发频率异常
  4. 动态配置:支持运行时调整限流参数,避免重启应用

选择合适的限流方案需要考虑你的具体业务场景、系统架构和性能要求。

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