漏斗算法(Funnel Algorithm)
一、核心原理(圖示+說明)
+-----------------------+
| |
| 待處理請求 | ← 新請求從頂部注入
| (水量) |
| |
+-----------------------+
| 漏嘴 | ← 按固定速率(leakRate)流出請求
+-----------------------+
↑
超出容量的請求從頂部溢出(被拒絕)
(示意圖說明:漏斗上寬下窄,頂部接收請求,底部以固定速率漏水,超出容量的請求從頂部溢出)
-
核心思想:模擬漏斗盛水→漏水的過程,控制請求流量:
- 頂部:接收新請求(像水流注入漏斗)。
- 容量:漏斗最多能裝的水量(最大積壓請求數(shù))。
- 漏嘴:底部固定大小的出口,以固定速率漏水(單位時間允許通過的請求數(shù))。
- 溢出:當注入速度>漏水速度,水滿后多余的水溢出(請求被拒絕)。
二、核心參數(shù)(4個關(guān)鍵指標)
| 參數(shù)名 | 含義 | 示例值 |
|---|---|---|
capacity |
漏斗總?cè)萘浚ㄗ畲罂煞e壓請求數(shù)) | 100(個) |
leakRate |
漏嘴速率(單位時間允許通過的請求數(shù)) | 10(個/秒) |
water |
當前水量(當前積壓的請求數(shù)) | 30(個) |
lastTime |
上次更新水量的時間戳(秒) | 1620000000 |
三、執(zhí)行流程(動態(tài)調(diào)整邏輯),每一次請求都要執(zhí)行
每次請求到來時,按以下步驟處理(結(jié)合圖示理解):
-
計算時間差
上次更新時間 →——————— deltaTime ———————→ 當前時間(當前時間 - 上次更新時間 =
deltaTime,即兩次請求的間隔) 計算漏水量
這段時間內(nèi)漏嘴漏掉的水量:
漏水量 = deltaTime × leakRate
(例:間隔3秒,漏嘴速率10個/秒 → 漏水量=3×10=30)更新當前水量
水量隨時間減少,且不能為負:
water = max(0, 舊water - 漏水量)
(例:舊water=50,漏水量=30 → 新water=20)-
判斷是否允許請求
+-----------------------+ | 當前水量:20 | | | → 新請求到來,判斷 20+1 ≤ 100? +-----------------------+ → 是 → 允許(水量變?yōu)?1) → 否 → 拒絕- 若
water + 1 ≤ capacity:允許請求(water+1,更新lastTime)。 - 否則:拒絕請求(漏斗已滿,溢出)。
- 若
四、優(yōu)缺點與適用場景
| 維度 | 內(nèi)容 |
|---|---|
| 優(yōu)點 | 1. 長期流量嚴格限制在漏嘴速率內(nèi); 2. 邏輯簡單,易實現(xiàn)。 |
| 缺點 | 1. 高并發(fā)下需頻繁計算時間差,對性能有要求; 2. 分布式場景需中間件保證一致性。 |
| 適用場景 | 接口限流、秒殺流量控制、用戶行為頻率限制(如評論、登錄)等。 |
五、分布式實現(xiàn)關(guān)鍵點(Redis+Lua)
- 原子性保證:用Lua腳本封裝整個流程,避免并發(fā)修改導致的水量誤差。
- 狀態(tài)存儲:Redis的Hash結(jié)構(gòu)存儲4個核心參數(shù)(water、lastTime等)。
- 性能優(yōu)化:連接池復用Redis連接、本地限流前置過濾請求、Redis集群分流。
六、具體的Lua腳本
-- 漏斗限流腳本:key為限流對象(如"user:123"),返回1表示允許,0表示拒絕
local key = KEYS[1]
local capacity = tonumber(ARGV[1]) -- 漏斗容量
local leakRate = tonumber(ARGV[2]) -- 漏嘴速率(請求/秒)
-- 從Redis獲取當前漏斗狀態(tài)(不存在則初始化)
local current = redis.call("HMGET", key, "water", "lastTime")
local water = tonumber(current[1] or 0)
local lastTime = tonumber(current[2] or 0)
local now = tonumber(redis.call("TIME")[1]) -- 當前時間戳(秒)
-- 計算漏水:deltaTime秒內(nèi)漏掉的水量
local deltaTime = now - lastTime
local leakWater = deltaTime * leakRate
water = math.max(0, water - leakWater) -- 水量不能為負
lastTime = now -- 更新時間戳
-- 判斷是否允許新請求
if water + 1 <= capacity then
water = water + 1
redis.call("HMSET", key, "water", water, "lastTime", lastTime)
redis.call("EXPIRE", key, 86400) -- 設置過期時間,避免內(nèi)存溢出
return 1 -- 允許
else
return 0 -- 拒絕
end