找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

815

积分

0

好友

109

主题
发表于 3 小时前 | 查看: 0| 回复: 0

在 Spring Boot 项目中,为接口添加限流是保护服务稳定性的重要手段。许多问题往往在线上出现服务被刷、响应变慢或CPU飙升后,才被发现是限流缺失或配置不当所致。

常见的实现方案主要分布在三个层面:

单机限流 → 分布式限流 → 网关限流

下面我们来详细剖析这几种方案,帮助你根据实际场景做出选择。

一、最简单:基于 Guava RateLimiter(单机)

适用场景

  • 单体应用
  • 不要求多实例间限流一致
  • 防止接口被刷、保护资源

实现方式

  • 基于令牌桶算法
  • 每个接口或实例在内存中独立维护限流器

1. 引入依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
</dependency>

2. 使用方式

@RestController
@RequestMapping("/api")
public class TestController {

    // 每秒最多 5 个请求
    private static final RateLimiter rateLimiter = RateLimiter.create(5.0);

    @GetMapping("/test")
    public String test() {
        if (!rateLimiter.tryAcquire()) {
            throw new RuntimeException("请求过于频繁");
        }
        return "ok";
    }
}

优点

  • 实现极其简单,上手成本低
  • 性能非常好(纯内存、无 IO)
  • 适合快速防刷、兜底保护
  • 官方成熟组件,稳定可靠

缺点

  • 只能单机生效,多实例部署时总流量会被放大
  • 无法做到多节点统一限流
  • 不支持动态规则调整
  • 不适合核心业务接口

二、推荐方案:拦截器 / AOP + 本地计数(单机)

这是许多中大型项目在处理非核心接口时采用的做法。

适用场景

  • 单体服务
  • 非核心接口
  • 内部 API / 后台接口

实现方式

  • 自定义注解
  • 通过拦截器或 AOP 统计请求次数
  • 在内存中使用固定窗口或滑动窗口算法进行计数

1. 自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    int count();        // 允许次数
    int time();         // 时间窗口(秒)
}

2. 拦截器实现(滑动窗口/固定窗口)

@Component
public class RateLimitInterceptor implements HandlerInterceptor {

    private final Map<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();
    private final Map<String, Long> timeMap = new ConcurrentHashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod method = (HandlerMethod) handler;
        RateLimit limit = method.getMethodAnnotation(RateLimit.class);
        if (limit == null) {
            return true;
        }

        String key = request.getRequestURI();
        long now = System.currentTimeMillis();

        timeMap.putIfAbsent(key, now);
        counterMap.putIfAbsent(key, new AtomicInteger(0));

        long startTime = timeMap.get(key);

        if (now - startTime > limit.time() * 1000L) {
            timeMap.put(key, now);
            counterMap.get(key).set(0);
        }

        if (counterMap.get(key).incrementAndGet() > limit.count()) {
            response.setStatus(429);
            response.getWriter().write("请求过于频繁");
            return false;
        }

        return true;
    }
}

3. 注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RateLimitInterceptor());
    }
}

4. 使用

@RateLimit(count = 10, time = 60)
@GetMapping("/order/create")
public String createOrder() {
    return "success";
}

优点

  • 控制粒度细(接口级、方法级)
  • 侵入性低,对业务代码影响小
  • 易扩展(IP、用户、接口组合)
  • 不依赖外部组件

缺点

  • 仍然是单机限流
  • 多实例场景存在流量穿透问题
  • 自实现算法容易有边界问题(如时间窗口临界点)
  • 重启应用计数会丢失

三、Redis + Lua(分布式限流)

适用场景

  • 多实例部署
  • 核心业务接口
  • 秒杀、支付、下单接口
  • 外部 API 防刷

实现方式

  • 以 Redis 作为统一的计数器存储
  • 使用 Lua 脚本保证计数和判断的原子性
  • 支持固定窗口、滑动窗口、漏桶等多种算法

1. 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

current = redis.call("incr", key)
if tonumber(current) == 1 then
    redis.call("expire", key, expire)
end

return 1

2. Java 调用

@Service
public class RedisRateLimiter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private DefaultRedisScript<Long> script;

    @PostConstruct
    public void init() {
        script = new DefaultRedisScript<>();
        script.setScriptText(LUA_SCRIPT);
        script.setResultType(Long.class);
    }

    public boolean allow(String key, int limit, int time) {
        Long result = redisTemplate.execute(
                script,
                Collections.singletonList(key),
                String.valueOf(limit),
                String.valueOf(time)
        );
        return result != null && result == 1;
    }
}

3. 使用

@GetMapping("/pay")
public String pay() {
    if (!redisRateLimiter.allow("pay", 5, 60)) {
        throw new RuntimeException("请求太频繁");
    }
    return "ok";
}

优点

  • 支持分布式,多实例一致限流
  • 性能高(Lua 原子执行,减少网络往返)
  • 可扩展维度多(接口 / 用户 / IP)
  • 规则可动态配置
  • 生产环境最常见方案

缺点

  • 依赖 Redis ,需考虑其可用性
  • 实现复杂度较高
  • 需评估 Redis 压力
  • Lua 脚本维护成本稍高

四、Spring Cloud Gateway 网关限流

适用场景

  • 微服务架构
  • 对外开放 API
  • 多服务统一防刷

实现方式

  • 在 API 网关层统一进行限流
  • 通常基于 Redis 的令牌桶算法实现
  • 与后端业务服务完全解耦
spring:
  cloud:
    gateway:
      routes:
        - id: order
          uri: lb://order-service
          predicates:
            - Path=/order/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20

优点

  • 限流逻辑集中,统一管理
  • 服务无感知,零侵入
  • 天然支持分布式
  • 可结合鉴权、熔断等功能一起使用

缺点

  • 只能控制入口流量
  • 无法感知服务内部业务状态
  • 网关压力集中,配置相对复杂
  • 单体项目不适用

总结与选择

以上四种方案各有侧重。对于刚起步的单体应用,使用 Guava RateLimiter 或自定义拦截器是快速有效的选择。当业务增长,服务走向分布式部署时,基于 Redis 和 Lua 的分布式限流方案则成为保障核心接口稳定的基石。而在微服务架构中,将限流能力上移至 Spring Cloud Gateway 等网关层,可以实现流量的统一管控和防护。

在实际的 Java 项目开发中,理解这些方案的区别并灵活运用,是构建健壮后端系统的关键技能之一。如果你对系统设计、高并发处理有更多兴趣,欢迎到 云栈社区 与更多开发者交流探讨。




上一篇:Palantir技术架构解析:Gotham与Foundry如何构建百亿级数据分析帝国
下一篇:RabbitMQ与Kafka深度对比:消息队列选型与集成实战指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-25 16:49 , Processed in 0.248085 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表