本文目录导读:

我来详细解释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分布式限流 | 支持分布式,配置灵活 | 网络开销较大 | 微服务架构,分布式系统 |
注意事项
- 限流粒度:根据业务需求选择接口级别、用户级别或IP级别限流
- 异常处理:当触发限流时,返回合适的HTTP状态码(如429 Too Many Requests)
- 监控告警:配合监控系统,及时发现限流触发频率异常
- 动态配置:支持运行时调整限流参数,避免重启应用
选择合适的限流方案需要考虑你的具体业务场景、系统架构和性能要求。