当对 react 应用进行页面加载或 seo 优化时,我们一般绕不开 React SSR。但 React SSR 毕竟涉及到了服务端,有很多服务端特有的问题需要考虑,而限流就是其中之一。
所谓限流,就是当我们的服务资源有限、处理能力有限时,通过对请求或并发数进行限制从而保障系统正常运行的一种策略。本文会通过一个简单的案例来说明,为什么服务端需要进行限流。
如下所示是一个简单的 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
此时,容器内存已经全部被占用,服务对外失去了响应,通过 curl -m 5 http://localhost:2048 访问,收到了超时的错误提示:
curl: (28) Operation timed out after 5001 milliseconds with 0 bytes received
我们改造一下代码,使用 counter.js 来统计 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 = []
// 删除超出一秒的数据
for (let i = 0, len = arr.length; i < len; i++) {
if (now - arr[i].time > interval) continue
newArr.push(arr[i])
}
arr = newArr
return
}
// 计算前一秒的数据和
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
由此可见,限流确实是系统进行自我保护的一个比较好的方法。
常见的限流算法有“滑动窗口算法”、“令牌桶算法”,我们这里讨论 “令牌桶算法” 。在令牌桶算法中,存在一个桶,容量为 burst 。该算法以一定的速率(设为 rate )往桶中放入令牌,超过桶容量会丢弃。每次请求需要先获取到桶中的令牌才能继续执行,否则拒绝。
根据令牌桶的定义,我们实现令牌桶算法如下:
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 时,会先执行 refill 先往桶中进行填充。填充的方式也很简单,首先计算出与上次填充的时间间隔 elapse 毫秒,然后计算出这段时间内应该补充的令牌数,因为令牌补充速率是 rate 个/秒,所以需要补充的令牌数为:
elapse * (this.rate / 1000)
又因为令牌数不能超过桶的容量,所以补充后桶中的令牌数为:
Math.min(this.burst, this.tokens + elapse * (this.rate / 1000))
注意,这个令牌数是可以为小数的。
令牌桶算法具有以下两个特点:
T = burst / (M - rate) // rate < M
可以理解为一个水池里面有 burst 的水量,进水的速率为 rate ,出水的速率为 M ,则净出水速率为 M-rate ,则水池中的水放空的时间即为激增流量的持续时间。
原文 http://www.paradeto.com/2022/07/07/react-ssr-rate-limit/
一个单页应用(通常也叫做 SPA )是一个客户端渲染的 App 。这是一个仅在浏览器端运行的应用。如果你正在使用框架,比如 React, Vue.js 或者 AngularJS ,客户端将从头开始渲染你的 App 。
随着越来越多新型前端框架的推出,SSR 这个概念在前端开发领域的流行度越来越高,也有越来越多的项目采用这种技术方案进行了实现。SSR 产生的背景是什么?适用的场景是什么?实现的原理又是什么?
有时候,我们会有这样的需求,在项目的前端页面中需要使用一个swiper插件,来实现图片轮播,但是nuxt是在服务端进行编译的,那么问题来了,我们如何像在vue中那样使用第三方模块,封装轮播公用组件呢?答案是:使用nuxt到插件功能。
最近简单的研究了一下SSR,对SSR已经有了一个简单的认知,主要应用于单页面应用,Nuxt是SSR很不错的框架。也有过调研,简单的用了一下,感觉还是很不错。但是还是想知道若不依赖于框架又应该如果处理SSR,研究一下做个笔记。
Vue-SSR相信大家都不陌生,与传统 SPA 相比,服务器端渲染 (SSR) 能够具备更好的SEO,方便搜索引擎爬虫抓取工具可以直接查看完全渲染的页面,除此之外,SSR能够在更短的时间内渲染出页面内容,通过在服务端填充数据吐出到客户端的方式,让用户有更好的用户体验。
Server-side rendering (SSR)是应用程序通过在服务器上显示网页而不是在浏览器中渲染的能力。服务器端向客户端发送一个完全渲染的页面(准确来说是仅仅是 HTML 页面)
当对 React 应用进行页面加载或 SEO 优化时,我们一般绕不开 React SSR。但 React SSR 毕竟涉及到了服务端,有很多服务端特有的问题需要考虑,而限流就是其中之一。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!