網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
當(dāng)對(duì) React 應(yīng)用進(jìn)行頁(yè)面加載或 SEO 優(yōu)化時(shí),我們一般繞不開(kāi) React SSR。但 React SSR 畢竟涉及到了服務(wù)端,有很多服務(wù)端特有的問(wèn)題需要考慮,而限流就是其中之一。
所謂限流,就是當(dāng)我們的服務(wù)資源有限、處理能力有限時(shí),通過(guò)對(duì)請(qǐng)求或并發(fā)數(shù)進(jìn)行限制從而保障系統(tǒng)正常運(yùn)行的一種策略。本文會(huì)通過(guò)一個(gè)簡(jiǎn)單的案例來(lái)說(shuō)明,為什么服務(wù)端需要進(jìn)行限流。
為什么要限流
如下所示是一個(gè)簡(jiǎn)單的 nodejs 服務(wù)端項(xiàng)目:
const express = require('express') const app = express() app.get('/', async (req, res) => { // 模擬 SSR 會(huì)大量的占用內(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) })
其中,我們通過(guò)?Buffer
?來(lái)模擬 SSR 過(guò)程會(huì)大量的占用內(nèi)存的情況。
然后,通過(guò)?docker build -t ssr .
?指定將我們的項(xiàng)目打包成一個(gè)鏡像,并通過(guò)以下命令運(yùn)行一個(gè)容器:
docker run \ -it \ -m 512m \ # 限制容器的內(nèi)存 --rm \ -p 2048:2048 \ --name ssr \ --oom-kill-disable \ ssr
我們將容器內(nèi)存限制在 512m,并通過(guò)?--oom-kill-disable
?指定容器內(nèi)存不足時(shí)不關(guān)閉容器。
接下來(lái),我們通過(guò)?autocannon
?來(lái)進(jìn)行一下壓測(cè):
autocannon -c 10 -d 1000 http://localhost:2048
通過(guò),?docker stats
?可以看到容器的運(yùn)行情況:
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
此時(shí),容器內(nèi)存已經(jīng)全部被占用,服務(wù)對(duì)外失去了響應(yīng),通過(guò)?curl -m 5 http://localhost:2048
?訪問(wèn),收到了超時(shí)的錯(cuò)誤提示:
curl: (28) Operation timed out after 5001 milliseconds with 0 bytes received
我們改造一下代碼,使用?counter.js
?來(lái)統(tǒng)計(jì) 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 } // 計(jì)算前一秒的數(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 } }
此時(shí),容器運(yùn)行正常:
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
雖然此時(shí)訪問(wèn)?/
?路由會(huì)收到錯(cuò)誤:
curl -m 5 http://localhost:2048 exceed limit
但是?/another
?卻不受影響:
curl -m 5 http://localhost:2048/another another api
由此可見(jiàn),限流確實(shí)是系統(tǒng)進(jìn)行自我保護(hù)的一個(gè)比較好的方法。
令牌桶算法
常見(jiàn)的限流算法有“滑動(dòng)窗口算法”、“令牌桶算法”,我們這里討論?“令牌桶算法”?。在令牌桶算法中,存在一個(gè)桶,容量為?burst
?。該算法以一定的速率(設(shè)為?rate
?)往桶中放入令牌,超過(guò)桶容量會(huì)丟棄。每次請(qǐng)求需要先獲取到桶中的令牌才能繼續(xù)執(zhí)行,否則拒絕。
根據(jù)令牌桶的定義,我們實(shí)現(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 }
簡(jiǎn)單解釋一下這個(gè)算法,調(diào)用?take
?時(shí),會(huì)先執(zhí)行?refill
?先往桶中進(jìn)行填充。填充的方式也很簡(jiǎn)單,首先計(jì)算出與上次填充的時(shí)間間隔?elapse
?毫秒,然后計(jì)算出這段時(shí)間內(nèi)應(yīng)該補(bǔ)充的令牌數(shù),因?yàn)榱钆蒲a(bǔ)充速率是?rate
?個(gè)/秒,所以需要補(bǔ)充的令牌數(shù)為:
elapse * (this.rate / 1000)
又因?yàn)榱钆茢?shù)不能超過(guò)桶的容量,所以補(bǔ)充后桶中的令牌數(shù)為:
Math.min(this.burst, this.tokens + elapse * (this.rate / 1000))
注意,這個(gè)令牌數(shù)是可以為小數(shù)的。
令牌桶算法具有以下兩個(gè)特點(diǎn):
- 當(dāng)外部請(qǐng)求的 QPS?
M
?大于令牌補(bǔ)充的速率?rate
?時(shí),長(zhǎng)期來(lái)看,最終有效的 QPS 會(huì)趨向于?rate
?。這個(gè)很好理解,拉的總不可能比吃的多吧。 - 因?yàn)榱钆仆翱梢源嫦?
burst
?個(gè)令牌,所以可以允許短時(shí)間的激增流量,持續(xù)的時(shí)間為:
T = burst / (M - rate) // rate < M
可以理解為一個(gè)水池里面有?burst
?的水量,進(jìn)水的速率為?rate
?,出水的速率為?M
?,則凈出水速率為?M-rate
?,則水池中的水放空的時(shí)間即為激增流量的持續(xù)時(shí)間。
原文鏈接:http://www.paradeto.com/2022/07/07/react-ssr-rate-limit/
相關(guān)推薦
- 2023-11-19 docker鏡像run后 ps命令查不到解決辦法;docker 容器顯示exit(1)
- 2022-06-08 Android即時(shí)通訊設(shè)計(jì)(騰訊IM接入和WebSocket接入)_Android
- 2022-09-11 Oracle學(xué)習(xí)筆記之視圖及索引的使用_oracle
- 2022-04-16 C++順序表的基本操作實(shí)現(xiàn)_C 語(yǔ)言
- 2021-12-03 Apache?Log4j2?報(bào)核彈級(jí)漏洞快速修復(fù)方法_Linux
- 2022-09-20 redis的string類(lèi)型及bitmap介紹_Redis
- 2022-01-19 正則——時(shí)間 時(shí)分秒 12小時(shí)制 24小時(shí)制 moment可以轉(zhuǎn)化的時(shí)間 HH:mm:ss hh:m
- 2022-03-16 Oracle數(shù)據(jù)庫(kù)分析函數(shù)用法_oracle
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支