java 需要有接口调用次数限制
·
java 需要有接口调用次数限制
你想要在 Java 中实现接口调用次数的限制功能,这是一个在接口限流、防止恶意请求或保护系统资源时非常常见的需求。
实现思路
我会基于令牌桶算法(Token Bucket)来实现一个通用的接口限流工具,这种算法简单且实用,适合控制接口的调用频率。核心思路是:
- 为每个接口(或接口 + 用户)维护一个令牌桶
- 令牌桶以固定速率生成令牌
- 每次接口调用需要消耗一个令牌,没有令牌则拒绝调用
- 可以自定义限流的时间窗口和最大调用次数
完整实现代码
下面是一个开箱即用的限流工具类和使用示例:
java
运行
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* 接口调用次数限制工具类(基于令牌桶算法)
*/
public class ApiRateLimiter {
// 存储每个key对应的限流信息
private static final Map<String, TokenBucket> RATE_LIMITER_MAP = new ConcurrentHashMap<>();
/**
* 尝试获取调用权限
* @param key 限流标识(可以是接口名、用户ID+接口名等)
* @param maxCount 时间窗口内最大调用次数
* @param timeWindow 时间窗口大小(单位:秒)
* @return true-允许调用,false-拒绝调用
*/
public static boolean tryAcquire(String key, int maxCount, int timeWindow) {
// 初始化令牌桶(线程安全)
TokenBucket tokenBucket = RATE_LIMITER_MAP.computeIfAbsent(key,
k -> new TokenBucket(maxCount, timeWindow));
return tokenBucket.tryAcquire();
}
/**
* 令牌桶实现
*/
private static class TokenBucket {
// 最大令牌数(时间窗口内最大调用次数)
private final int maxTokens;
// 令牌生成间隔(毫秒)
private final long tokenInterval;
// 当前令牌数
private final AtomicLong currentTokens;
// 上次令牌补充时间
private volatile long lastRefillTime;
public TokenBucket(int maxCount, int timeWindow) {
this.maxTokens = maxCount;
// 计算令牌生成间隔:时间窗口(毫秒) / 最大令牌数
this.tokenInterval = (long) (timeWindow * 1000.0 / maxCount);
this.currentTokens = new AtomicLong(maxCount);
this.lastRefillTime = System.currentTimeMillis();
}
/**
* 尝试获取令牌
*/
public boolean tryAcquire() {
// 1. 先补充令牌(根据上次补充时间到现在的时长)
refillTokens();
// 2. 尝试获取令牌
long tokens = currentTokens.get();
if (tokens > 0) {
return currentTokens.compareAndSet(tokens, tokens - 1);
}
// 无令牌可用,拒绝调用
return false;
}
/**
* 补充令牌
*/
private void refillTokens() {
long now = System.currentTimeMillis();
// 计算从上次补充到现在应该生成的令牌数
long elapsedTime = now - lastRefillTime;
long newTokens = elapsedTime / tokenInterval;
if (newTokens > 0) {
// 线程安全地更新令牌数和最后补充时间
synchronized (this) {
// 双重检查,避免重复补充
elapsedTime = System.currentTimeMillis() - lastRefillTime;
newTokens = elapsedTime / tokenInterval;
if (newTokens > 0) {
long current = currentTokens.get();
// 令牌数不能超过最大值
long updated = Math.min(current + newTokens, maxTokens);
currentTokens.set(updated);
lastRefillTime += newTokens * tokenInterval;
}
}
}
}
}
// ========== 使用示例 ==========
public static void main(String[] args) throws InterruptedException {
String apiKey = "user1:orderApi";
int maxCount = 5; // 5次
int timeWindow = 10; // 10秒
// 模拟15次接口调用
for (int i = 1; i <= 15; i++) {
boolean allow = ApiRateLimiter.tryAcquire(apiKey, maxCount, timeWindow);
System.out.printf("第%d次调用:%s%n", i, allow ? "允许" : "拒绝");
// 模拟每次调用间隔1秒
TimeUnit.SECONDS.sleep(1);
}
}
}
Spring Boot 集成示例(更贴近实际开发)
如果你的项目是 Spring Boot,可以结合注解实现更优雅的限流:
java
运行
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class TestController {
@GetMapping("/test")
public String testApi() {
// 定义限流规则:用户user1调用/test接口,10秒内最多5次
String key = "user1:/api/test";
int maxCount = 5;
int timeWindow = 10;
// 检查是否允许调用
if (!ApiRateLimiter.tryAcquire(key, maxCount, timeWindow)) {
return "调用过于频繁,请稍后再试!";
}
// 接口核心逻辑
return "接口调用成功";
}
}
关键代码解释
更多推荐


所有评论(0)