本文目录导读:

我来详细解释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)
- 监控告警:配合监控系统,及时发现限流触发频率异常
- 动态配置:支持运行时调整限流参数,避免重启应用
选择合适的限流方案需要考虑你的具体业务场景、系统架构和性能要求。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播