本文目录导读:

在Java中实现接口限流,常见的方案有单机限流和分布式限流,下面分别介绍几种经典实现,包括本地限流和基于Redis的限流。
本地限流(单机版)
适用于单体应用或不需要跨服务共享限流状态的场景,常用算法有令牌桶和滑动窗口。
使用 Guava RateLimiter(令牌桶)
import com.google.common.util.concurrent.RateLimiter;
public class LocalRateLimiter {
// 每秒产生 10 个令牌,即 QPS 上限为 10
private final RateLimiter rateLimiter = RateLimiter.create(10);
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
public static void main(String[] args) {
LocalRateLimiter limiter = new LocalRateLimiter();
// 模拟 100 个请求
for (int i = 0; i < 100; i++) {
boolean acquired = limiter.tryAcquire();
if (acquired) {
System.out.println("请求通过");
} else {
System.out.println("请求被限流");
}
}
}
}
基于 AOP + 注解 + Redis(自定义滑动窗口)
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
// 每秒最大请求数
int qps() default 10;
// 限流时间窗口(秒)
int time() default 1;
}
// AOP 切面实现
@Aspect
@Component
public class RateLimitAspect {
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
// 模拟本地计数器(实际可用 ConcurrentHashMap + 滑动窗口)
String key = joinPoint.getSignature().toShortString();
// 这里简化为伪代码,实际可使用 Redis + Lua 实现原子计数
if (checkRateLimit(key, rateLimit.qps(), rateLimit.time())) {
return joinPoint.proceed();
} else {
throw new RuntimeException("请求过于频繁,请稍后重试");
}
}
private boolean checkRateLimit(String key, int qps, int time) {
// 真实场景使用 Redis + Lua 脚本保证原子性
// 示例伪逻辑:
// 1. 获取当前时间窗口内的请求次数
// 2. 如果超过上限则返回 false
// 3. 否则计数 +1,返回 true
return true;
}
}
分布式限流(基于 Redis)
适用于微服务架构,多个服务实例共享同一份限流计数器。
Redis + Lua 脚本实现固定窗口
-- Lua 脚本(原子操作)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])
local current = redis.call('GET', key)
if current and tonumber(current) >= limit then
return 0 -- 限流
end
local count = redis.call('INCR', key)
if count == 1 then
redis.call('EXPIRE', key, expire)
end
return 1 -- 允许通过
Java 调用示例(使用 Spring Data Redis):
@Component
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LUA_SCRIPT =
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local expire = tonumber(ARGV[2])\n" +
"local current = redis.call('GET', key)\n" +
"if current and tonumber(current) >= limit then\n" +
" return 0\n" +
"end\n" +
"local count = redis.call('INCR', key)\n" +
"if count == 1 then\n" +
" redis.call('EXPIRE', key, expire)\n" +
"end\n" +
"return 1";
public boolean tryAcquire(String key, int limit, int expireSeconds) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(key),
String.valueOf(limit), String.valueOf(expireSeconds));
return result != null && result == 1;
}
}
Redis 令牌桶(进阶)
使用 Redis 的有序集合(ZSet)实现滑动窗口,或使用 Redis 4.0 的令牌桶模块 redis-cell(需要安装)。
# 直接使用 redis-cell 提供的 CL.THROTTLE 命令 CL.THROTTLE user123 10 10 60 1 # 参数解释:key, 最大burst, 每秒令牌数, 时间窗口, 每个请求消耗令牌数
Java 调用:
// 使用 Jedis 调用 CL.THROTTLE
List<String> result = jedis.clThrottle("user123", 10, 10, 60, 1);
// 结果解析:是否允许、剩余令牌、等待时间等
框架级限流方案
Spring Cloud Gateway / Zuul
只需配置文件即可实现路由级别限流:
# Spring Cloud Gateway + Redis 限流示例
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- name: RequestRateLimiter
args:
# 使用 Redis 存储限流状态
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"
Sentinel(阿里开源)
依赖引入后,只需添加注解即可:
@SentinelResource(value = "getUser", blockHandler = "handleBlock")
public User getUser(String id) {
// 业务逻辑
}
// 限流降级方法
public User handleBlock(String id, BlockException e) {
return new User(); // 返回降级结果
}
限流算法的选择
| 算法 | 特点 | 适用场景 |
|---|---|---|
| 固定窗口 | 实现简单,但临界突变问题 | 对精度要求不高的场景 |
| 滑动窗口 | 精度高,但资源消耗稍大 | 需要平滑限流的 API |
| 令牌桶 | 允许突发流量,平滑速率 | 流量不均匀的接口 |
| 漏桶 | 强制平滑,不允许突发 | 严格控制输入速率(如数据库写入) |
最佳实践总结
- 单机测试 / 非关键接口 → 用 Guava RateLimiter 足够。
- 生产环境微服务 → 优先使用 Sentinel 或 Gateway 限流过滤器,免去复杂配置。
- 高并发自定义场景 → Redis + Lua 脚本实现滑动窗口 / 令牌桶。
- 重要提示:限流策略应包含 降级返回(如 HTTP 429 状态码 + 友好提示),不要直接让服务崩溃。
- 配合熔断:限流只是流量控制的第一步,建议结合 Hystrix / Resilience4j 实现熔断降级。
根据你的业务场景选择合适方案,希望对你有所帮助!