Redisson 限流:3 次调用给 API 网关接上分布式限流
2026/9/5 16:54:49 网站建设 项目流程

Redisson 限流:3 次调用给 API 网关接上分布式限流

【免费下载链接】redissonRedisson: Valkey & Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson

生产里最扎心的一次:下游服务在 QPS 突破 2w 后开始雪崩,日志全是超时重试,最后定位到网关层压根没有限流。我们的做法是用 Redisson 分布式限流(Redisson 是 Valkey/Redis 的 Java 客户端,内置令牌桶等 50 多种分布式对象)在 API 网关层给每个接口挂上准入控制——整篇就是复盘这次接入:最小调用、网关集成、以及踩过的三个坑,看完能直接在你自己的网关上落地。

为什么本地计数器撑不住

Guava RateLimiter 之类的本地限流器在单机上没问题,但网关通常是多实例水平扩容的:每个实例各自记一个桶,A 实例放过 5000 QPS,B 实例也放 5000 QPS,下游实际承受的是两倍承诺值;再叠加实例发布重启时计数归零,限流形同虚设。Redisson 的RRateLimiter相当于把 Guava RateLimiter 重写到 Redis 上——配额状态存在共享的 Redis 键里,所有网关实例看到的是同一个桶,而且限流原子脚本和键的过期清理(空闲桶自动失效)都由它封装好了,不用自己裸写 Lua。接口定义在 RRateLimiter,文档见 RRateLimiter 章节。

跑通 15 行最小限流

拿到RRateLimiter后,trySetRate配速率、tryAcquire取令牌:

RRateLimiter limiter = redisson.getRateLimiter("api:order:create"); // 首次配置生效;每秒 500 个令牌 limiter.trySetRate(RateType.OVERALL, 500, 1, RateIntervalUnit.SECONDS); // 快失败取 1 个令牌:拿得到放行,拿不到直接 false boolean pass = limiter.tryAcquire();

只有三次调用,就是分布式限流的全部主干。RateType是两个词:OVERALL表示所有网关实例共享同一个桶,配多少就是全局多少;PER_CLIENT则按每个 Redisson 实例(大致相当于每个 JVM)单独给额,适合给本地磁盘 IO 这类"每实例各管各的"资源限流。注意trySetRate是"没配过才配"的语义,重复调用返回false且不覆盖旧速率,所以启动时多实例并发初始化不会互相打架。

接进 Spring Cloud Gateway

网关侧就是一个GlobalFilter,按路由 ID 取桶、取令牌、拒绝时统一出口:

public Mono<Void> filter(ServerWebExchange ex, GatewayFilterChain chain) { String key = "api:" + ex.getRequest().getHeaders().getFirst("X-Route-Id"); RRateLimiter limiter = redisson.getRateLimiter(key); limiter.trySetRate(RateType.OVERALL, 500, 1, RateIntervalUnit.SECONDS); boolean pass = limiter.tryAcquire(1, 500, TimeUnit.MILLISECONDS); if (!pass) { ex.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); return ex.getResponse().writeWith(Mono.just( ex.getResponse().bufferFactory().wrap("{\"code\":\"RATE_LIMITED\"}"))); } return chain.filter(ex); }

两个决策点值得先想清楚再动手。第一,限流 key 按什么维度取:我们用的是路由 ID(等价于接口维度),需要多租户隔离就拼上租户 ID;千万别直接用完整 URL,带查询参数的请求会让 key 空间无限膨胀。第二,拒绝时返回什么:统一 429,body 带上稳定错误码(示例里是RATE_LIMITED),让客户端能识别"被限流"而不是"服务挂了",配合客户端退避策略才不至于把重试流量再灌回来。

排查三个容易踩的坑

  • trySetRate是幂等且"先到者赢"的。多个网关实例并发启动时只有一个实例的trySetRate返回true,其余拿到false就跳过,这没问题;但如果配置中心想强推新速率,得用setRate,它无条件覆盖。
  • key 粒度太粗会饿死冷门接口。全网关共用一个桶时,一个热门接口就能把全局限额直接怼满,下单、支付这种关键链路反而排队。按接口或租户分桶,热门接口的配额才不会吃掉别人的。
  • tryAcquire超时设太短会诱发重试风暴。50ms 拿不到令牌就放弃,客户端 100ms 重试一轮,QPS 不降反升;要么把超时长一点,要么干脆 429 快失败,把重试决策权留给上游。

需要时再展开的几件事

动态调整速率

setRate覆盖旧配置,速率热更新:

limiter.setRate(RateType.OVERALL, 1000, 1, RateIntervalUnit.SECONDS);

接上配置中心推送就能做到"运维在控制台改数,线上令牌桶立即变快",不用发版。

非阻塞取令牌

同步tryAcquire会占用一个 Netty 线程,量级上来后改用tryAcquireAsyncRFuture,把等待交给线程池,不占 IO 线程。

Redisson 把分布式限流从"自己维护 Lua 脚本"降级成三次 Java 调用,网关接上它之后,下游服务的上限第一次变得可控且全局一致。更多细节见官方文档 docs/data-and-services/objects.md,实现源码在 redisson/src/main/java/org/redisson/。

【免费下载链接】redissonRedisson: Valkey & Redis Java Client and Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Spring, Tomcat, Scheduler, JCache API, Hibernate, RPC, local cache..项目地址: https://gitcode.com/GitHub_Trending/re/redisson

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询