Java案例如何实现接口限流?

wen java案例 4

本文目录导读:

Java案例如何实现接口限流?

  1. 本地限流(单机版)
  2. 分布式限流(基于 Redis)
  3. 框架级限流方案
  4. 限流算法的选择
  5. 最佳实践总结

在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
令牌桶 允许突发流量,平滑速率 流量不均匀的接口
漏桶 强制平滑,不允许突发 严格控制输入速率(如数据库写入)

最佳实践总结

  1. 单机测试 / 非关键接口 → 用 Guava RateLimiter 足够。
  2. 生产环境微服务 → 优先使用 SentinelGateway 限流过滤器,免去复杂配置。
  3. 高并发自定义场景 → Redis + Lua 脚本实现滑动窗口 / 令牌桶。
  4. 重要提示:限流策略应包含 降级返回(如 HTTP 429 状态码 + 友好提示),不要直接让服务崩溃。
  5. 配合熔断:限流只是流量控制的第一步,建议结合 Hystrix / Resilience4j 实现熔断降级。

根据你的业务场景选择合适方案,希望对你有所帮助!

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