本文目录导读:

- 方案一:单次调用超时控制(Future + Timeout)
- 方案二:使用信号量 + 定时器(Semaphore + Timer)
- 方案三:Guava RateLimiter(平滑限流)
- 方案四:Spring AOP + 注解(企业级常用)
- 方案五:分布式限流(Redis + Lua)
- 总结对比
在 Java 中实现“服务限时”主要有两种思路:一是单次调用超时控制(防止某个请求卡死整个线程),二是整体接口限流(防止服务被瞬间大量请求冲垮,比如要求某个接口每秒最多调用 100 次)。
鉴于问题可能是想问“如何让一个服务或接口在某段时间内只能被调用有限次数”,下面提供几个最常用的方案,从简单到工业级。
单次调用超时控制(Future + Timeout)
这是最基础、最常见的“接口调用限时”场景,例如调用一个第三方 API,3 秒内没返回就放弃。
import java.util.concurrent.*;
public class TimeoutExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
// 模拟耗时操作
Thread.sleep(5000);
return "result";
});
try {
// 最多等待 2 秒
String result = future.get(2, TimeUnit.SECONDS);
System.out.println("成功: " + result);
} catch (TimeoutException e) {
System.out.println("服务超时");
future.cancel(true); // 取消任务
} catch (Exception e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
适合场景:单个耗时操作的控制,如 RPC 调用、数据库查询。
使用信号量 + 定时器(Semaphore + Timer)
对某个服务接口设定“每分钟最多调用 10 次”。
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class ServiceLimiter {
// 令牌数上限
private final Semaphore semaphore;
private final long periodMillis;
private final int maxPermits;
public ServiceLimiter(int maxPermits, long period, TimeUnit unit) {
this.maxPermits = maxPermits;
this.periodMillis = unit.toMillis(period);
// 初始全部可用
this.semaphore = new Semaphore(maxPermits);
// 启动定时重置线程
startResetTimer();
}
private void startResetTimer() {
Thread resetThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(periodMillis);
} catch (InterruptedException e) {
break;
}
// 重置信号量(注意:这样不是精确的平滑限流,但简单直观)
semaphore.drainPermits(); // 清空剩余
semaphore.release(maxPermits);
}
});
resetThread.setDaemon(true);
resetThread.start();
}
public boolean tryAccess() {
return semaphore.tryAcquire();
}
// 使用示例
public static void main(String[] args) throws InterruptedException {
ServiceLimiter limiter = new ServiceLimiter(10, 1, TimeUnit.SECONDS);
for (int i = 0; i < 15; i++) {
boolean allowed = limiter.tryAccess();
System.out.println("第" + (i+1) + "次调用" + (allowed ? "允许" : "拒绝"));
Thread.sleep(100);
}
}
}
适合场景:简单的自制限流,不需要引入第三方依赖。
Guava RateLimiter(平滑限流)
Google Guava 提供的令牌桶算法实现,非常适合在 JVM 内部做限流。
Maven 依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
示例代码:
import com.google.common.util.concurrent.RateLimiter;
public class GuavaRateLimiterExample {
public static void main(String[] args) {
// 每秒最多放行 5 个请求(平均 200ms/个)
RateLimiter limiter = RateLimiter.create(5.0);
for (int i = 0; i < 10; i++) {
// 尝试获取令牌,如果没有令牌会阻塞等待
double waitTime = limiter.acquire();
System.out.println("第" + (i+1) + "次调用, 等待了 " + waitTime + " 秒");
}
}
}
特点:
- 内置令牌桶算法,突发流量会自动平滑。
- 有
tryAcquire()重载可以设置超时时间,不阻塞直接返回 false。
适合场景:单机或分布式节点内部限流。
Spring AOP + 注解(企业级常用)
可以将限流逻辑与业务代码解耦,定义一个注解 @ServiceLimiter,然后在 AOP 切面中接入 Guava/RateLimiter 或 Redis。
定义注解:
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceLimiter {
double permitsPerSecond() default 10; // 每秒许可数
}
编写 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.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
@Aspect
@Component
public class RateLimitAspect {
// 缓存每个方法的 RateLimiter
private final ConcurrentHashMap<String, RateLimiter> limiterMap = new ConcurrentHashMap<>();
@Around("@annotation(serviceLimiter)")
public Object limit(ProceedingJoinPoint joinPoint, ServiceLimiter serviceLimiter) throws Throwable {
String methodName = joinPoint.getSignature().toLongString();
RateLimiter limiter = limiterMap.computeIfAbsent(methodName,
k -> RateLimiter.create(serviceLimiter.permitsPerSecond()));
// 尝试获取令牌(等待最多 1 秒)
if (!limiter.tryAcquire(1, TimeUnit.SECONDS)) {
throw new RuntimeException("服务繁忙,请稍后再试");
}
return joinPoint.proceed();
}
}
用法:
@RestController
public class MyController {
@ServiceLimiter(permitsPerSecond = 5)
@GetMapping("/api/data")
public String getData() {
return "data";
}
}
适合场景:Spring Boot 项目,需要统一管理限流策略。
分布式限流(Redis + Lua)
如果服务部署在多台机器上,需要在全局层面限流,可以用 Redis 实现滑动窗口或令牌桶。
Redis + Lua 脚本示例(固定窗口限流):
-- KEYS[1] = key 如 "limit:user_api"
-- ARGV[1] = 窗口大小(毫秒)
-- ARGV[2] = 最大请求数
local key = KEYS[1]
local window = tonumber(ARGV[1])
local maxRequests = tonumber(ARGV[2])
local now = redis.call('TIME')[1] * 1000 -- 毫秒级时间戳
-- 清理窗口外的旧记录
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 当前请求数
local count = redis.call('ZCARD', key)
if count < maxRequests then
-- 加入当前请求
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('EXPIRE', key, window / 1000 + 1)
return 1 -- 允许
else
return 0 -- 拒绝
end
Java 调用:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
public class RedisRateLimiter {
private final Jedis jedis;
private final String luaScript;
public RedisRateLimiter() {
jedis = new Jedis("localhost");
luaScript = "--- 上方的 Lua 脚本 ---";
}
public boolean tryAcquire(String key, long windowMillis, int maxRequests) {
Object result = jedis.eval(luaScript, 1, key, String.valueOf(windowMillis), String.valueOf(maxRequests));
return "1".equals(result.toString());
}
}
适合场景:微服务架构、网关层限流。
总结对比
| 方案 | 特点 | 适用规模 |
|---|---|---|
| Future 超时 | 单次调用控制 | 单个请求 |
| Semaphore + Timer | 简单自制 | 小型单机 |
| Guava RateLimiter | 平滑、高性能 | 单机中大型 |
| Spring AOP + RateLimiter | 企业级、解耦 | 单机/集群节点 |
| Redis + Lua | 精确、分布式 | 分布式全局 |
建议:如果是简单场景,直接用 Future.get(timeout);如果是单机接口限流,优先使用 Guava RateLimiter;如果涉及分布式,建议用 Redis 滑动窗口。