React?SSR?中的限流案例詳解
當對 React 應用進行頁面加載或 SEO 優(yōu)化時,我們一般繞不開 React SSR。但 React SSR 畢竟涉及到了服務端,有很多服務端特有的問題需要考慮,而限流就是其中之一。
所謂限流,就是當我們的服務資源有限、處理能力有限時,通過對請求或并發(fā)數(shù)進行限制從而保障系統(tǒng)正常運行的一種策略。本文會通過一個簡單的案例來說明,為什么服務端需要進行限流。
為什么要限流
如下所示是一個簡單的 nodejs 服務端項目:
const express = require('express')
const app = express()
app.get('/', async (req, res) => {
// 模擬 SSR 會大量的占用內(nèi)存
const buf = Buffer.alloc(1024 * 1024 * 200, 'a')
console.log(buf)
res.end('end')
})
app.get('/another', async (req, res) => {
res.end('another api')
})
const listener = app.listen(process.env.PORT || 2048, () => {
console.log('Your app is listening on port ' + listener.address().port)
})其中,我們通過 Buffer 來模擬 SSR 過程會大量的占用內(nèi)存的情況。
然后,通過 docker build -t ssr . 指定將我們的項目打包成一個鏡像,并通過以下命令運行一個容器:
docker run \ -it \ -m 512m \ # 限制容器的內(nèi)存 --rm \ -p 2048:2048 \ --name ssr \ --oom-kill-disable \ ssr
我們將容器內(nèi)存限制在 512m,并通過 --oom-kill-disable 指定容器內(nèi)存不足時不關(guān)閉容器。
接下來,我們通過 autocannon 來進行一下壓測:
autocannon -c 10 -d 1000 http://localhost:2048
通過, docker stats 可以看到容器的運行情況:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS d9c0189e2b56 ssr 0.00% 512MiB / 512MiB 99.99% 14.6kB / 8.65kB 41.9MB / 2.81MB 40
此時,容器內(nèi)存已經(jīng)全部被占用,服務對外失去了響應,通過 curl -m 5 http://localhost:2048 訪問,收到了超時的錯誤提示:
curl: (28) Operation timed out after 5001 milliseconds with 0 bytes received
我們改造一下代碼,使用 counter.js 來統(tǒng)計 QPS,并限制為 2:
const express = require('express')
const counter = require('./counter.js')
const app = express()
const limit = 2
let cnt = counter()
app.get(
'/',
(req, res, next) => {
cnt(1)
if (cnt() > limit) {
res.writeHead(500, {
'content-type': 'text/pain',
})
res.end('exceed limit')
return
}
next()
},
async (req, res) => {
const buf = Buffer.alloc(1024 * 1024 * 200, 'a')
console.log(buf)
res.end('end')
}
)
app.get('/another', async (req, res) => {
res.end('another api')
})
const listener = app.listen(process.env.PORT || 2048, () => {
console.log('Your app is listening on port ' + listener.address().port)
})
// counter.js
module.exports = function counter(interval = 1000) {
let arr = []
return function cnt(number) {
const now = Date.now()
if (number > 0) {
arr.push({
time: now,
value: number,
})
const newArr = []
// 刪除超出一秒的數(shù)據(jù)
for (let i = 0, len = arr.length; i < len; i++) {
if (now - arr[i].time > interval) continue
newArr.push(arr[i])
}
arr = newArr
return
}
// 計算前一秒的數(shù)據(jù)和
let sum = 0
for (let i = arr.length - 1; i >= 0; i--) {
const {time, value} = arr[i]
if (now - time <= interval) {
sum += value
continue
}
break
}
return sum
}
}此時,容器運行正常:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 3bd5aa07a3a7 ssr 88.29% 203.1MiB / 512MiB 39.67% 24.5MB / 48.6MB 122MB / 2.81MB 40
雖然此時訪問 / 路由會收到錯誤:
curl -m 5 http://localhost:2048 exceed limit
但是 /another 卻不受影響:
curl -m 5 http://localhost:2048/another another api
由此可見,限流確實是系統(tǒng)進行自我保護的一個比較好的方法。
令牌桶算法
常見的限流算法有“滑動窗口算法”、“令牌桶算法”,我們這里討論 “令牌桶算法” 。在令牌桶算法中,存在一個桶,容量為 burst 。該算法以一定的速率(設(shè)為 rate )往桶中放入令牌,超過桶容量會丟棄。每次請求需要先獲取到桶中的令牌才能繼續(xù)執(zhí)行,否則拒絕。
根據(jù)令牌桶的定義,我們實現(xiàn)令牌桶算法如下:
export default class TokenBucket {
private burst: number
private rate: number
private lastFilled: number
private tokens: number
constructor(burst: number, rate: number) {
this.burst = burst
this.rate = rate
this.lastFilled = Date.now()
this.tokens = burst
}
setBurst(burst: number) {
this.burst = burst
return this
}
setRate(rate: number) {
this.rate = rate
return this
}
take() {
this.refill()
if (this.tokens > 0) {
this.tokens -= 1
return true
}
return false
}
refill() {
const now = Date.now()
const elapse = now - this.lastFilled
this.tokens = Math.min(this.burst, this.tokens + elapse * (this.rate / 1000))
this.lastFilled = now
}
}然后,按照如下方式使用:
const tokenBucket = new TokenBucket(5, 10)
if (tokenBucket.take()) {
// Do something
} else {
// refuse
}簡單解釋一下這個算法,調(diào)用 take 時,會先執(zhí)行 refill 先往桶中進行填充。填充的方式也很簡單,首先計算出與上次填充的時間間隔 elapse 毫秒,然后計算出這段時間內(nèi)應該補充的令牌數(shù),因為令牌補充速率是 rate 個/秒,所以需要補充的令牌數(shù)為:
elapse * (this.rate / 1000)
又因為令牌數(shù)不能超過桶的容量,所以補充后桶中的令牌數(shù)為:
Math.min(this.burst, this.tokens + elapse * (this.rate / 1000))
注意,這個令牌數(shù)是可以為小數(shù)的。
令牌桶算法具有以下兩個特點:
- 當外部請求的 QPS
M大于令牌補充的速率rate時,長期來看,最終有效的 QPS 會趨向于rate。這個很好理解,拉的總不可能比吃的多吧。 - 因為令牌桶可以存下
burst個令牌,所以可以允許短時間的激增流量,持續(xù)的時間為:
T = burst / (M - rate) // rate < M
可以理解為一個水池里面有 burst 的水量,進水的速率為 rate ,出水的速率為 M ,則凈出水速率為 M-rate ,則水池中的水放空的時間即為激增流量的持續(xù)時間。
到此這篇關(guān)于React SSR 之限流的文章就介紹到這了,更多相關(guān)React SSR內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react-three-fiber實現(xiàn)炫酷3D粒子效果首頁
這篇文章主要介紹了react-three-fiber實現(xiàn)3D粒子效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08
React報錯之Parameter event implicitly has a
這篇文章主要為大家介紹了React報錯之Parameter event implicitly has an any type,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
如何使用 React Native WebView 實現(xiàn) App&nb
通過 react-native-webview,我們可以輕松實現(xiàn) App 與 Web 的雙向通訊,這種技術(shù)非常適合需要在移動應用中嵌入復雜網(wǎng)頁功能的場景,感興趣的朋友一起看看吧2024-12-12
react中的watch監(jiān)視屬性-useEffect介紹
這篇文章主要介紹了react中的watch監(jiān)視屬性-useEffect使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
解決React報錯You provided a `checked` prop&n
這篇文章主要為大家介紹了React報錯You provided a `checked` prop to a form field的解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
React父組件數(shù)據(jù)實時更新了,子組件沒有更新的問題
這篇文章主要介紹了React父組件數(shù)據(jù)實時更新了,子組件沒有更新的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
React Native設(shè)備信息查看調(diào)試詳解
這篇文章主要為大家介紹了React Native設(shè)備信息查看調(diào)試詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11

