Redis實(shí)現(xiàn)限流功能的幾種方法總結(jié)
一、問題背景
在實(shí)際業(yè)務(wù)場(chǎng)景中,限流是保護(hù)系統(tǒng)的重要手段:在一段時(shí)間(period)內(nèi),限定某個(gè)行為(action)的最大次數(shù)(max_count)。本文介紹如何基于 Redis 實(shí)現(xiàn)多種限流方案。
二、限流類型總覽
| 限流類型 | 核心思想 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| 固定窗口限流 | 時(shí)間窗口固定,到期自動(dòng)清零 | 實(shí)現(xiàn)簡(jiǎn)單 | 存在窗口邊界突擊流量問題 |
| 滑動(dòng)窗口限流 | 窗口隨時(shí)間滑動(dòng),統(tǒng)計(jì)窗口內(nèi)請(qǐng)求數(shù) | 精確解決邊界問題 | 實(shí)現(xiàn)稍復(fù)雜 |
| 漏斗限流 | 容量固定,速率固定 | 精確控制容量和速率 | 需要 Redis 模塊支持 |
| 令牌桶限流 | 令牌以固定速率放入桶中 | 支持突發(fā)流量 | 實(shí)現(xiàn)復(fù)雜 |
三、固定窗口限流
3.1 什么是固定窗口限流
將時(shí)間劃分為固定的窗口,例如每 5 分鐘為一個(gè)窗口:
|---5min---|---5min---|---5min---|---5min---| 20:00 20:05 20:10 20:15
在每個(gè)窗口內(nèi)獨(dú)立計(jì)數(shù),窗口到期后計(jì)數(shù)清零。
3.2 Redis 實(shí)現(xiàn)
-- 固定窗口限流實(shí)現(xiàn)
local key = "***" .. user_id .. ":" .. action
local limit = 10 -- 最大次數(shù)
local period = 10 -- 時(shí)間窗口(秒)
-- 方式1:INCR + EXPIRE(存在問題)
redis.call('INCR', key)
redis.call('EXPIRE', key, period)
-- 方式2:SET + INCR(正確實(shí)現(xiàn),解決競(jìng)態(tài)條件)
-- 使用 SET + EXPIRE 原子操作,避免窗口切換時(shí)丟失數(shù)據(jù)
redis.call('SET', key, 0, 'EX', period, 'NX')
local count = redis.call('INCR', key)
return count <= limit
關(guān)鍵點(diǎn):使用 SET + EXPIRE 代替單獨(dú) EXPIRE,避免 INCR 和 EXPIRE 之間進(jìn)程崩潰導(dǎo)致數(shù)據(jù)丟失。
可通過 Pipeline 保證兩個(gè)命令同時(shí)發(fā)送:
# Python 示例 pipe = redis.pipeline() pipe.set(key, 0, ex=period, nx=True) pipe.incr(key) res = pipe.execute() return res[1] <= limit
3.3 固定窗口的局限性
假設(shè) 5 分鐘內(nèi)限定 10 次請(qǐng)求:
20:04-20:05 發(fā)生 9 次請(qǐng)求 20:05-20:06 發(fā)生 9 次請(qǐng)求
在 20:04-20:06 這 2 分鐘內(nèi),實(shí)際發(fā)生了 18 次請(qǐng)求,遠(yuǎn)超每 5 分鐘 10 次的限制。
問題根源:固定窗口的邊界不連續(xù),在邊界處可能發(fā)生突發(fā)流量。
四、滑動(dòng)窗口限流
4.1 核心思想
滑動(dòng)窗口的核心是窗口隨時(shí)間連續(xù)滑動(dòng),而非固定邊界:
傳統(tǒng)固定窗口: |-----5min-----|-----5min-----|
20:00 20:05 20:10
滑動(dòng)窗口:
現(xiàn)在時(shí)刻的窗口持續(xù)向前滑動(dòng)
|----5min-----|----5min-----|
20:01 20:06
4.2 Redis 實(shí)現(xiàn)(ZSET)
local function is_action_allowed(red, user_id, action, period, max_count)
local key = "***" .. user_id .. ":" .. action
local now = redis.call('TIME') -- 獲取當(dāng)前時(shí)間戳(毫秒)
-- 1. 記錄當(dāng)前行為(score 和 member 都用時(shí)間戳)
red:zadd(key, now, now)
-- 2. 移除窗口之前的行為記錄
red:zremrangebyscore(key, 0, now - period * 1000)
-- 3. 獲取窗口內(nèi)的行為數(shù)量
local count = red:zcard(key)
-- 4. 設(shè)置過期時(shí)間,避免冷用戶持續(xù)占用內(nèi)存
red:expire(key, period + 1)
return count <= max_count
end
流程圖:
時(shí)間軸:[--窗口period--|---未來---]
↑now
ZSET 存儲(chǔ):score=時(shí)間戳, member=時(shí)間戳
ZREMRANGEBYSCORE:刪除 score < now-period 的舊記錄
ZCARD:統(tǒng)計(jì)剩余元素?cái)?shù)量,即窗口內(nèi)請(qǐng)求數(shù)
4.3 為什么用 ZSET 而非 LIST
| 數(shù)據(jù)結(jié)構(gòu) | 適用場(chǎng)景 |
|---|---|
| ZSET | 支持按時(shí)間范圍刪除,適合滑動(dòng)窗口 |
| LIST | 只能按索引刪除,無(wú)法按時(shí)間范圍清理 |
五、漏斗限流(Redis-Cell)
5.1 什么是漏斗限流
漏斗限流的核心是容量固定 + 速率固定,能精確控制元素的容量和速率:
漏斗模型:
[入口] -> (容量固定) -> [出口]
↓
速率恒定
- 漏斗容量:最多能容納多少請(qǐng)求
- 漏斗速率:?jiǎn)挝粫r(shí)間內(nèi)能處理多少請(qǐng)求
5.2 Redis-Cell 模塊安裝
Redis-Cell 是 Redis 的第三方模塊,采用 Rust 編寫,需要單獨(dú)安裝:
# 下載并編譯 git clone https://github.com/brandur/redis-cell cd redis-cell cargo build --release cp target/release/libredis_cell.so /path/to/modules/ # 啟動(dòng) Redis 加載模塊 redis-server --loadmodule /path/to/modules/libredis_cell.so
5.3 CL.THROTTLE 命令詳解
CL.THROTTLE key capacity operations seconds [quota]
參數(shù)說明:
| 參數(shù) | 含義 | 示例 |
|---|---|---|
| key | 漏斗容器名稱 | user:123:login |
| capacity | 漏斗容量(最大容納請(qǐng)求數(shù)) | 10 |
| operations | 單位時(shí)間內(nèi)的操作次數(shù) | 5 |
| seconds | 單位時(shí)間(秒) | 60 |
| quota | 單次行為消耗的令牌數(shù)(可選,默認(rèn)1) | 1 |
示例:每 60 秒最多 5 次請(qǐng)求,漏斗容量 10
CL.THROTTLE user:123:login 10 5 60
返回結(jié)果:
1) (integer) 0 # 是否被限流(0=允許,1=拒絕) 2) (integer) 7 # 漏斗剩余容量 3) (integer) 7 # 如果被拒絕,還需要等多久(秒) 4) (integer) -1 # 預(yù)留字段 5) (integer) 60 # 下次請(qǐng)求的間隔時(shí)間
5.4 流速計(jì)算
流速 = operations / seconds = 5 / 60 ≈ 0.083 請(qǐng)求/秒
這意味著每秒只能處理約 0.083 個(gè)請(qǐng)求,即約 12 秒處理 1 個(gè)請(qǐng)求。
六、令牌桶限流
6.1 核心思想
令牌桶的核心是令牌以固定速率放入桶中:
令牌桶:
-> [桶容量] -> 請(qǐng)求消耗令牌 -> 通過
↑
固定速率放入令牌
- 桶容量:最大令牌數(shù)
- 令牌添加速率:每秒添加多少令牌
- 請(qǐng)求消耗:每個(gè)請(qǐng)求消耗 1 個(gè)令牌
6.2 特點(diǎn)
| 特點(diǎn) | 說明 |
|---|---|
| 支持突發(fā)流量 | 桶滿時(shí)可一次性處理多個(gè)請(qǐng)求 |
| 令牌非即時(shí)補(bǔ)充 | 需要等待令牌生成 |
6.3 與漏斗限流的區(qū)別
| 對(duì)比維度 | 漏斗限流 | 令牌桶限流 |
|---|---|---|
| 速率 | 勻速 | 勻速(令牌補(bǔ)充) |
| 突發(fā)能力 | 不支持 | 支持(桶滿時(shí)) |
| 實(shí)現(xiàn)難度 | 較簡(jiǎn)單 | 較復(fù)雜 |
七、四種限流方案對(duì)比
| 維度 | 固定窗口 | 滑動(dòng)窗口 | 漏斗限流 | 令牌桶 |
|---|---|---|---|---|
| 實(shí)現(xiàn)復(fù)雜度 | 低 | 中 | 低 | 高 |
| 邊界突擊 | 有 | 無(wú) | 無(wú) | 無(wú) |
| 突發(fā)流量支持 | 不支持 | 不支持 | 不支持 | 支持 |
| 精度控制 | 低 | 中 | 高 | 高 |
| 額外依賴 | 無(wú) | 無(wú) | Redis-Cell | 無(wú) |
八、面試追問 FAQ
| 問題 | 回答要點(diǎn) |
|---|---|
| Q: 為什么固定窗口需要 SET + INCR 組合? | 單獨(dú) INCR + EXPIRE 在進(jìn)程崩潰時(shí)可能丟失數(shù)據(jù),SET+EXPIRE 原子操作保證一致性 |
| Q: 滑動(dòng)窗口為什么要設(shè)置過期時(shí)間為 period+1? | 避免窗口邊界附近過期導(dǎo)致數(shù)據(jù)丟失,確??绱翱诘恼?qǐng)求仍被統(tǒng)計(jì) |
| Q: 漏斗限流和令牌桶限流各適用于什么場(chǎng)景? | 漏斗:需要精確控制速率的 API 限流;令牌桶:允許突發(fā)流量的場(chǎng)景(如秒殺) |
| Q: Redis-Cell 是原子操作嗎? | 是,CL.THROTTLE 整個(gè)命令是原子的,無(wú)需擔(dān)心并發(fā)問題 |
| Q: 滑動(dòng)窗口的 ZSET 會(huì)不會(huì)無(wú)限增長(zhǎng)? | 不會(huì),每次請(qǐng)求都會(huì)清理窗口外的舊數(shù)據(jù),且有 expire 保證清理 |
總結(jié)
| 限流方案 | 實(shí)現(xiàn)難度 | 精度 | 突發(fā)流量 | 推薦場(chǎng)景 |
|---|---|---|---|---|
| 固定窗口 | 低 | 低 | 不支持 | 簡(jiǎn)單場(chǎng)景 |
| 滑動(dòng)窗口 | 中 | 中 | 不支持 | 需要精確控制 |
| 漏斗限流 | 低 | 高 | 不支持 | API 限流 |
| 令牌桶 | 高 | 高 | 支持 | 秒殺/搶購(gòu) |
核心結(jié)論: 根據(jù)業(yè)務(wù)場(chǎng)景選擇合適的限流方案,簡(jiǎn)單場(chǎng)景用固定窗口,精確控制用滑動(dòng)窗口或漏斗限流,需要突發(fā)能力用令牌桶。
以上就是Redis實(shí)現(xiàn)限流功能的幾種方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Redis實(shí)現(xiàn)限流功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
巧用Redis實(shí)現(xiàn)分布式鎖詳細(xì)介紹
大家好,本篇文章主要講的是巧用Redis實(shí)現(xiàn)分布式鎖詳細(xì)介紹,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
解決Redis運(yùn)行時(shí)突然不能讀取數(shù)據(jù)了的問題處理
Redis因configured錯(cuò)誤配置導(dǎo)致無(wú)法持久化數(shù)據(jù),因此在寫數(shù)據(jù)時(shí)被禁止,將stopPersist-ononError設(shè)置為no允許寫入但不停持久化,實(shí)際問題在于rdb文件保存路徑權(quán)限不足,檢查并rdb.file文件所在目錄權(quán)限并確保redis能具有寫寫入該權(quán),此問題通過修改目錄權(quán)限解決2026-05-05
Spring?redis使用報(bào)錯(cuò)Read?timed?out排查及解決過程
項(xiàng)目使用spring集成redis,偶爾會(huì)出現(xiàn)read timed out的情況,剛開始以為是網(wǎng)絡(luò)不穩(wěn)定引起的,后面發(fā)現(xiàn)影響業(yè)務(wù)測(cè)試的準(zhǔn)確性,這篇文章主要給大家介紹了關(guān)于Spring redis使用報(bào)錯(cuò)Read timed out排查及解決過程的相關(guān)資料,需要的朋友可以參考下2024-02-02

