Spring Boot 接口如何限流?推荐4种实现方案

AI 概述
SpringBoot 项目接口限流方案分三个层级:一是单机限流,如基于 Guava RateLimiter,实现简单、性能好,但无法多节点统一限流;拦截器/AOP + 本地计数,控制粒度细、侵入性低,但重启应用计数会丢失。二是分布式限流,采用 Redis + Lua,支持分布式、性能高,但依赖 Redis、实现复杂。三是网关限流,在 Spring Cloud Gateway 层统一限流,服务无感知,但只能控制入口流量,网关压力集中,单体项目不适用。
目录
文章目录隐藏
  1. 一、最简单:基于 Guava RateLimiter(单机)
  2. 二、推荐方案:拦截器 / AOP + 本地计数(单机)
  3. 三、Redis + Lua(分布式限流)
  4. 四、Spring Cloud Gateway 网关限流

Spring Boot 接口如何限流?推荐 4 种实现方案

在 SpringBoot 项目中实现接口限流,其实方案并不少。不过很多问题都是在线上出问题之后才被会被重视起来。

比如:接口被刷、服务被拖慢、CPU 飙高,最后才发现:原来限流没写,或者做错了。

常见的方案可以分为 3 个层级:

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

一、最简单:基于 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

优点

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

缺点

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

如果你正在维护一个项目,不妨对照以下本文中的几种方案。

以上关于Spring Boot 接口如何限流?推荐4种实现方案的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » Spring Boot 接口如何限流?推荐4种实现方案

发表回复