本篇文章主要為大家講述關于ReactSSR之限流,其實我們都知道React SSR是涉及到服務端的,因此,我們先需要考慮到很多的服務器端問題,下面就為大家舉例說明。
當簡單來說, React 的應用進行頁面加載或 SEO 優(yōu)化時,都會想到React SSR。也就會想到服務器端,這是必須考慮到的。
現(xiàn)在我們來說下所謂限流,其實是在我們的服務資源有限、處理能力有限時,通過對請求或并發(fā)數(shù)進行限制從而保障系統(tǒng)正常運行的一種策略。但為何要限流那。
為什么要限流
如下所示是一個簡單的 nodejs 服務端項目:
const express = require('express') const app = express() app.get('/', async (req, res) => { // 模擬 SSR 會大量的占用內存 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 過程會大量的占用內存的情況。
然后,通過docker build -t ssr .指定將我們的項目打包成一個鏡像,并通過以下命令運行一個容器:
docker run \ -it \ -m 512m \ # 限制容器的內存 --rm \ -p 2048:2048 \ --name ssr \ --oom-kill-disable \ ssr
我們將容器內存限制在 512m,并通過--oom-kill-disable指定容器內存不足時不關閉容器。
接下來,我們通過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
此時,容器內存已經(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。該算法以一定的速率(設為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 }
簡單解釋一下這個算法,調用take時,會先執(zhí)行refill先往桶中進行填充。填充的方式也很簡單,首先計算出與上次填充的時間間隔elapse毫秒,然后計算出這段時間內應該補充的令牌數(shù),因為令牌補充速率是rate個/秒,所以需要補充的令牌數(shù)為:
elapse * (this.rate / 1000)
又因為令牌數(shù)不能超過桶的容量,所以補充后桶中的令牌數(shù)為:
Math.min(this.burst, this.tokens + elapse * (this.rate / 1000))
注意,這個令牌數(shù)是可以為小數(shù)的。
令牌桶算法具有以下兩個特點:
當外部請求的 QPSM大于令牌補充的速率rate時,長期來看,最終有效的 QPS 會趨向于rate。這個很好理解,拉的總不可能比吃的多吧。
因為令牌桶可以存下burst個令牌,所以可以允許短時間的激增流量,持續(xù)的時間為:
T = burst / (M - rate) // rate < M
可以理解為一個水池里面有burst的水量,進水的速率為rate,出水的速率為M,則凈出水速率為M-rate,則水池中的水放空的時間即為激增流量的持續(xù)時間。
本文內容到此都講述完畢,歡迎大家關注后續(xù)更多精彩內容。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/130341.html
摘要:實現(xiàn)熔斷降級注解除了可以用來做限流控制之外,還能實現(xiàn)與類似的熔斷降級策略。函數(shù)簽名要求返回值類型必須與原函數(shù)返回值類型一致方法參數(shù)列表需要為空,或者可以額外多一個類型的參數(shù)用于接收對應的異常。若未配置和,則被限流降級時會將直接拋出。 在之前的《使用Sentinel實現(xiàn)接口限流》一文中,我們僅依靠引入Spring Cloud Alibaba對Sentinel的整合封裝spring-clo...
摘要:常見的限流方式,比如適用線程池隔離,超過線程池的負載,走熔斷的邏輯。在令牌桶算法中,存在一個桶,用來存放固定數(shù)量的令牌。,令牌桶每秒填充平均速率。 轉載請標明出處: https://www.fangzhipeng.com本文出自方志朋的博客 在高并發(fā)的系統(tǒng)中,往往需要在系統(tǒng)中做限流,一方面是為了防止大量的請求使服務器過載,導致服務不可用,另一方面是為了防止網(wǎng)絡攻擊。 常見的限流方式,...
摘要:以流量為切入點,從流量控制熔斷降級系統(tǒng)負載保護等多個維度保護服務的穩(wěn)定性分布式系統(tǒng)的流量防衛(wèi)兵。歡迎關注我們獲得更多的好玩實踐 之前分享過 一篇 《Spring Cloud Gateway 原生的接口限流該怎么玩》, 核心是依賴Spring Cloud Gateway 默認提供的限流過濾器來實現(xiàn) 原生RequestRateLimiter 的不足 配置方式 spring: clou...
摘要:以流量為切入點,從流量控制熔斷降級系統(tǒng)負載保護等多個維度保護服務的穩(wěn)定性分布式系統(tǒng)的流量防衛(wèi)兵。歡迎關注我們獲得更多的好玩實踐 之前分享過 一篇 《Spring Cloud Gateway 原生的接口限流該怎么玩》, 核心是依賴Spring Cloud Gateway 默認提供的限流過濾器來實現(xiàn) 原生RequestRateLimiter 的不足 配置方式 spring: clou...
閱讀 547·2023-03-27 18:33
閱讀 731·2023-03-26 17:27
閱讀 630·2023-03-26 17:14
閱讀 591·2023-03-17 21:13
閱讀 520·2023-03-17 08:28
閱讀 1800·2023-02-27 22:32
閱讀 1292·2023-02-27 22:27
閱讀 2177·2023-01-20 08:28