Java案例如何实现服务限时?

wen python案例 2

本文目录导读:

Java案例如何实现服务限时?

  1. 方案一:单次调用超时控制(Future + Timeout)
  2. 方案二:使用信号量 + 定时器(Semaphore + Timer)
  3. 方案三:Guava RateLimiter(平滑限流)
  4. 方案四:Spring AOP + 注解(企业级常用)
  5. 方案五:分布式限流(Redis + Lua)
  6. 总结对比

在 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 滑动窗口。

抱歉,评论功能暂时关闭!