前言:
服務(wù)上線(xiàn)后,我們一般會(huì)對(duì)自己 服務(wù) 有個(gè) 預(yù)估推溃,這個(gè) 服務(wù)能夠 承受多少請(qǐng)求等浊,當(dāng)單位時(shí)間內(nèi)請(qǐng)求數(shù)過(guò)高超過(guò)我們 預(yù)估 的 閥值坚冀,我們就應(yīng)該 拒絕多余的請(qǐng)求谆刨。我們 一般會(huì)用 漏水桶 和令牌桶算法來(lái)實(shí)現(xiàn) 以上邏輯。
令牌桶算法:
原理:
一個(gè)水桶按照一定速率往桶里放令牌纹坐,每一個(gè)請(qǐng)求進(jìn)來(lái) 都會(huì)去水桶里面嘗試 獲取 令牌枝冀,如果 沒(méi)有獲取到 就 拒絕 該請(qǐng)求。水桶有一定的容量耘子,最多只能放 n個(gè) 令牌果漾,當(dāng)令牌 數(shù)量過(guò)多就會(huì) 溢出。
代碼實(shí)現(xiàn):
var (
lastReqTime = time.Now().Unix() //上個(gè)請(qǐng)求 的時(shí)間戳
lastTokenNum int64 = 1 //上次請(qǐng)求時(shí)剩余的令牌
tokenRate int64 = 1 //每秒增加 1 個(gè) token
bucketCap int64 = 100 //令牌桶的總量
)
func tokenLimiter() bool {
nowT := time.Now().Unix() //當(dāng)前時(shí)間
//(當(dāng)前時(shí)間 減去 上次請(qǐng)求時(shí)間 ) 乘 速度 = 這段時(shí)間 生產(chǎn)的 令牌數(shù) 加上 原來(lái)的 令牌數(shù) 判斷 令牌 數(shù)是否 大于 桶的 容量 拴还,如果 大于 就取桶容量
nowTokenNum := int64(math.Min(float64((nowT-lastReqTime)*tokenRate+lastTokenNum), float64(bucketCap))) //現(xiàn)在令牌還有的數(shù)量
if nowTokenNum > 0 {
lastReqTime = nowT
lastTokenNum--
return true
}
return false
}
漏水桶算法:
原理:
一個(gè)水桶下面有個(gè)洞跨晴,會(huì)按照一定的速度漏水欧聘,每次一個(gè)請(qǐng)求過(guò)來(lái) 就相當(dāng)于 往水桶里面加一滴水 片林,如果 當(dāng)前 水桶 滿(mǎn)了 ,請(qǐng)求 就會(huì)溢出怀骤,溢出的水就相當(dāng)于 是 被拒絕的請(qǐng)求费封。
代碼實(shí)現(xiàn):(沒(méi)有恒定速率消費(fèi),代碼有問(wèn)題)
var (
lastReqTime = time.Now().Unix() //上次請(qǐng)求時(shí)間
lastReqNum int64 = 10 //上次水桶剩余的數(shù)量
leakyRate int64 = 1 //漏水速度每秒漏一滴
bucketCap int64 = 100 //令牌桶的總?cè)萘?)
func leakyLimiter() bool {
nowT := time.Now().Unix() //當(dāng)前時(shí)間
//當(dāng)前水量 = (上次剩余的水量 - 這段時(shí)間流去的水量) 蒋伦, 當(dāng)前水量 最小為 0
nowReqNum := int64(math.Max(float64(lastReqNum-(nowT-lastReqTime)*leakyRate), 0))
//如果當(dāng)前 水量已經(jīng)比桶的大了 弓摘,就直接返回 false 說(shuō)明 水溢出了
if nowReqNum > bucketCap {
return false
}
lastReqNum++
lastReqTime = nowT
return true
}
總結(jié):
- 多方資料都說(shuō)令牌桶 要比 漏水桶 好,因?yàn)?令牌桶 在 限流的同時(shí)還允許 在短時(shí)間內(nèi)的 合理并發(fā)(我個(gè)人覺(jué)得其實(shí)差不多, 漏水桶 從空桶 激增到 溢出 不也并發(fā)么)
- 這個(gè)代碼只是學(xué)習(xí)痕届,線(xiàn)上生產(chǎn)還應(yīng)該考慮到原子操作等.
- 可以 用redis 配合 lua 來(lái)做這塊邏輯