React SSR 中的限流案例详解

React SSR 中的限流案例详解

目录

为什么要限流

令牌桶算法

当对 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))

注意,这个令牌数是可以为小数的。

令牌桶算法具有以下两个特点:

当外部请求的 QPS M 大于令牌补充的速率 rate 时,长期来看,最终有效的 QPS 会趋向于 rate 。这个很好理解,拉的总不可能比吃的多吧。

因为令牌桶可以存下 burst 个令牌,所以可以允许短时间的激增流量,持续的时间为:

T = burst / (M - rate) // rate < M

可以理解为一个水池里面有 burst 的水量,进水的速率为 rate ,出水的速率为 M ,则净出水速率为 M-rate ,则水池中的水放空的时间即为激增流量的持续时间。

到此这篇关于React SSR 之限流的文章就介绍到这了,更多相关React SSR内容请搜索易知道(ezd.cc)以前的文章或继续浏览下面的相关文章希望大家以后多多支持易知道(ezd.cc)!

推荐阅读

    React解决长列表方案|react-virtualized

    React解决长列表方案|react-virtualized,元素,过程,github地址高效渲染大型列表的响应式组件 使用窗口特性,即在一个滚动的范围内,呈现你

    ReactOS是什么

    ReactOS是什么,审查,开发者,通用,开放源码,项目,维护,  ReactOS是一个开发与Windows NT和Windows 2000 应用程序、硬件驱动程序兼容的开源操

    用React完成一个图片轮播组件

    用React完成一个图片轮播组件,用,React,完成,一个,图片,轮播,组件,使用,React,,  使用React实现上述功能,首先,划分组件:  <SlideMS><Sli

    初学react用什么书籍

    初学react用什么书籍,初学,react,用,什么,书籍,初学,react,用,什么,,  初学react用什么书籍?  如果你没接触过JS,那么就要从JS基础开始

    Vue3.0API介绍中reactive使用示例

    Vue3.0API介绍中reactive使用示例,Vue3.0API,介绍,中,reactive,使用,示例,Vue3.0API,,Vue3.0API介绍中reactive使用示例reactive返回对象

    Angular 4.x中表单Reactive Forms详解

    这篇文章主要介绍了Angular 4.x中表单Reactive Forms的相关资料,文中通过示例代码介绍的非常详细,相信对大家具有一定的参考价值,需要的朋