戰(zhàn):3 步給 API 網(wǎng)關(guān)裝上流量閘門)
Redisson 分布式限流實(shí)戰(zhàn)3 步給 API 網(wǎng)關(guān)裝上流量閘門【免費(fèi)下載鏈接】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..項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/redisson大促零點(diǎn)單機(jī)限流器明明攔住了 3000 QPS后端還是被打穿——流量攤到 8 臺網(wǎng)關(guān)每臺只看到自己那一份。把配額挪進(jìn) Redis用 Redisson 的分布式限流讓全集群共用一個(gè)桶本文給出從建實(shí)例到網(wǎng)關(guān)過濾器的完整落地路徑。單機(jī)限流器為什么在集群下失效3 個(gè)盲區(qū)定位先排除一個(gè)常見誤區(qū)限流沒生效往往不是限流器壞了而是限流范圍錯(cuò)了。盲區(qū)一配額被實(shí)例數(shù)放大。Guava 的 RateLimiter、JUC 的信號量都是進(jìn)程內(nèi)對象。8 臺網(wǎng)關(guān)每臺放 100 QPS集群實(shí)際放行 800 QPS是配置的 8 倍。后端看到的是 800你以為配的是 100對不上賬。盲區(qū)二配額隨擴(kuò)縮容漂移。網(wǎng)關(guān)按 HPA 自動(dòng)擴(kuò)到 16 臺單機(jī)配額不變集群上限直接翻倍。想臨時(shí)壓回去只能改每臺實(shí)例的配置——8 次發(fā)布或者干脆放棄。盲區(qū)三節(jié)點(diǎn)故障導(dǎo)致配額漏氣。一臺網(wǎng)關(guān)掛了它的配額跟著消失集群總配額被動(dòng)收縮。反過來擴(kuò)容瞬間新節(jié)點(diǎn)還沒預(yù)熱配額又短暫偏高。運(yùn)維側(cè)看不到一個(gè)穩(wěn)定的集群級配額數(shù)字。根因只有一個(gè)配額狀態(tài)散落在各進(jìn)程內(nèi)存里沒有共享存儲。解法也只有一個(gè)——把桶挪到所有節(jié)點(diǎn)都能訪問的地方Redis 是最順手的候選。Redisson 限流器原理令牌桶 Lua 腳本一次原子扣減令牌桶一個(gè)桶按固定速率注水比如每秒 100 枚桶容量 速率。請求來就拿一枚桶空了要么等、要么拒。比喻成水龍頭勻速滴水進(jìn)杯子杯子滿了新水溢出——突發(fā)流量能短時(shí)吸收桶里的存量持續(xù)超載會(huì)被限住注水速率封頂。為什么狀態(tài)放 Redis所有網(wǎng)關(guān)節(jié)點(diǎn)從同一個(gè) key 取令牌配額天然共享擴(kuò)容縮容不影響總量。為什么必須用 Lua 腳本一次取令牌包含 4 步——讀當(dāng)前剩余、判斷夠不夠、扣減、把已用令牌連同時(shí)間戳記進(jìn) ZSet。如果拆成 4 條獨(dú)立 Redis 命令兩個(gè)節(jié)點(diǎn)并發(fā)時(shí)都會(huì)讀到剩 1 枚都判定通過都扣成 0最終超發(fā)。Redisson 把這 4 步打包成一段 Lua 腳本通過EVAL一次性發(fā)給 Redis。Redis 單線程執(zhí)行腳本腳本內(nèi)不可能被別的命令插隊(duì)讀-判-扣-記就是一個(gè)原子事務(wù)。這就是分布式環(huán)境下的扣減一致性來源。令牌怎么補(bǔ)回來已消耗令牌帶時(shí)間戳存在 ZSet 里score 是消耗時(shí)刻。每次取令牌前腳本先用zrangebyscore撈出超過 rateInterval 沒被釋放的舊令牌累加回當(dāng)前值再從 ZSet 里刪掉。不需要后臺線程定時(shí)補(bǔ)補(bǔ)桶邏輯嵌在每次取令牌的路徑里。這套腳本的 Java 封裝就是RedissonRateLimiterredisson/src/main/java/org/redisson/RedissonRateLimiter.java對使用者只暴露幾個(gè)方法。3 步配置 RRateLimiter建實(shí)例、設(shè)速率、取許可第 1 步建實(shí)例。限流器名字就是 Redis key 前綴命名帶業(yè)務(wù)語義方便排查。Config config new Config(); config.useSingleServer().setAddress(redis://127.0.0.1:6379); RedissonClient client Redisson.create(config); RRateLimiter limiter client.getRateLimiter(gw:order:create);第 2 步設(shè)速率。trySetRate只在限流器還沒初始化時(shí)生效已存在就跳過不會(huì)覆蓋線上配置。limiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS); // rate100窗口內(nèi)放行上限 // rateInterval1 SECONDS窗口長度 1 秒第 3 步取許可。tryAcquire()立即返回不等待。if (limiter.tryAcquire()) { // 放行執(zhí)行真實(shí)業(yè)務(wù) } else { // 拒絕返回 429 或直接丟棄 }三個(gè)參數(shù)各管一件事rate定窗口內(nèi)總量rateInterval unit定窗口時(shí)長RateType定配額歸屬范圍。跑通這三步限流器就活了。網(wǎng)關(guān)過濾器怎么接Spring Cloud Gateway 放行與 429 分支Spring Cloud Gateway 是響應(yīng)式的過濾器返回MonoVoid。限流判定塞在filter方法里兩條分支拿到許可 → 走chain.filter(exchange)繼續(xù)后續(xù)過濾器鏈沒拿到 → 直接置 429 并 complete不往后走。public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path exchange.getRequest().getPath().value(); RRateLimiter limiter client.getRateLimiter(gw: path); limiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.SECONDS); if (limiter.tryAcquire()) { return chain.filter(exchange); } exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); return exchange.getResponse().setComplete(); }key 怎么拼全局配額用路徑要按用戶限就把 userId 拼進(jìn)來gw: userId : path。一個(gè)業(yè)務(wù)維度一個(gè) key互不干擾。trySetRate每請求都調(diào)沒關(guān)系它底層是HSETNX已初始化時(shí)直接 no-op不會(huì)把線上速率改掉。所以過濾器里每請求先 trySetRate 再 tryAcquire是安全寫法首次部署自動(dòng)完成初始化后續(xù)請求零副作用。一個(gè)要留意的點(diǎn)tryAcquire()是同步阻塞調(diào)用。在 WebFlux 的 event loop 里調(diào)它會(huì)占住事件循環(huán)線程。生產(chǎn)環(huán)境建議改用tryAcquireAsync()拿到RFuture再包一層Mono.fromFuture把等待交還 reactor 調(diào)度別阻塞 event loop。RateLimiter 配置選型OVERALL 與 PER_CLIENT 對比 調(diào)速避坑兩種 RateType 怎么選維度OVERALLPER_CLIENT配額歸屬所有客戶端共享一個(gè)桶每個(gè) Redisson 客戶端實(shí)例獨(dú)立一個(gè)桶典型場景保護(hù)下游接口集群總吞吐封頂防單租戶刷爆允許整體流量高擴(kuò)容影響配額固定與節(jié)點(diǎn)數(shù)無關(guān)客戶端越多總吞吐線性放大Redis 鍵單一value鍵帶 clientId 后綴的獨(dú)立value鍵適合做網(wǎng)關(guān)全局 QPS 閘用戶級 / 租戶級 API 配額判斷標(biāo)準(zhǔn)一句話你要限制的是總盤子還是每個(gè)玩家的分額。總盤子選 OVERALL分額選 PER_CLIENT。兩者也可以疊加——外層 OVERALL 保下游內(nèi)層 PER_CLIENT 防單點(diǎn)濫用。三個(gè)進(jìn)階提示動(dòng)態(tài)調(diào)速setRate(...)會(huì)強(qiáng)制覆蓋配置同時(shí)DEL掉當(dāng)前令牌狀態(tài)value 和 permits 鍵都清。等價(jià)于發(fā)一個(gè)新滿桶。高流量時(shí)調(diào)用舊請求已經(jīng)扣掉的額度瞬間歸零可能引發(fā)一次放行尖峰。只調(diào)速率、不想清桶目前 API 層面沒有保留狀態(tài)改速率的路徑要么接受清桶副作用要么用trySetRate走未初始化才生效的語義。批量取許可acquire(10)一次扣 10 枚。適合一個(gè)上游請求背后扇出 10 次下游調(diào)用的場景按真實(shí)消耗配額比按請求數(shù)限更貼近后端壓力。異步取許可tryAcquireAsync()返回RFutureBoolean不阻塞當(dāng)前線程。WebFlux 環(huán)境用它套Mono.fromFuture后.subscribe處理兩條分支。容易踩的坑tryAcquire(permits)的 permits 不能大于 rate。Lua 腳本里有assert(tonumber(rate) tonumber(ARGV[1]))超了直接拋錯(cuò)。setRate 會(huì)清桶線上動(dòng)態(tài)調(diào)速前想清楚清空瞬間的放行尖峰能否接受。限流器 key 建議設(shè)過期。trySetRate支持keepAliveTime參數(shù)或事后對 key 調(diào)expire。不設(shè)過期的話Redis 里會(huì)積累一堆歷史業(yè)務(wù)的空桶長期占用內(nèi)存。PER_CLIENT 的客戶端是 Redisson 實(shí)例 ID不是 HTTP 用戶 ID。同一業(yè)務(wù)里所有網(wǎng)關(guān)節(jié)點(diǎn)共享同一個(gè) Redisson 實(shí)例 ID 的話它們會(huì)被視為同一個(gè)客戶端。要按業(yè)務(wù)用戶維度分桶得在 key 名里手動(dòng)拼 userId。收尾分布式限流的核心不是算法而是配額狀態(tài)放哪里、誰保證扣減原子。Redisson 把這兩件事都壓在 Redis Lua 上業(yè)務(wù)側(cè)只剩三行代碼。落地時(shí)先確認(rèn) OVERALL 還是 PER_CLIENT再?zèng)Q定 key 粒度和速率窗口剩下的交給RRateLimiter。官方文檔docs/overview.md【免費(fèi)下載鏈接】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..項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/redisson創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考