java 需要有接口调用次数限制

你想要在 Java 中实现接口调用次数的限制功能,这是一个在接口限流、防止恶意请求或保护系统资源时非常常见的需求。

实现思路

我会基于令牌桶算法(Token Bucket)来实现一个通用的接口限流工具,这种算法简单且实用,适合控制接口的调用频率。核心思路是:

  1. 为每个接口(或接口 + 用户)维护一个令牌桶
  2. 令牌桶以固定速率生成令牌
  3. 每次接口调用需要消耗一个令牌,没有令牌则拒绝调用
  4. 可以自定义限流的时间窗口和最大调用次数

完整实现代码

下面是一个开箱即用的限流工具类和使用示例:

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 "接口调用成功";
    }
}

关键代码解释

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐