惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Help Net Security
Help Net Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
SecWiki News
SecWiki News
Webroot Blog
Webroot Blog
AI
AI
S
Secure Thoughts
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Cloudbric
Cloudbric
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
The Exploit Database - CXSecurity.com
Latest news
Latest news
N
News and Events Feed by Topic
Simon Willison's Weblog
Simon Willison's Weblog
Scott Helme
Scott Helme
S
Schneier on Security
H
Hacker News: Front Page
Forbes - Security
Forbes - Security
T
Troy Hunt's Blog
Know Your Adversary
Know Your Adversary
S
Security Affairs
V
Visual Studio Blog
Stack Overflow Blog
Stack Overflow Blog
博客园_首页
腾讯CDC
GbyAI
GbyAI
有赞技术团队
有赞技术团队
Last Week in AI
Last Week in AI
N
News and Events Feed by Topic
WordPress大学
WordPress大学
The Register - Security
The Register - Security
Engineering at Meta
Engineering at Meta
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cisco Talos Blog
Cisco Talos Blog
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
Recent Announcements
Recent Announcements
V
V2EX
S
Securelist
Security Archives - TechRepublic
Security Archives - TechRepublic
B
Blog RSS Feed
PCI Perspectives
PCI Perspectives
Security Latest
Security Latest
I
InfoQ
I
Intezer
L
LangChain Blog
雷峰网
雷峰网
NISL@THU
NISL@THU
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
阮一峰的网络日志
阮一峰的网络日志

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客 在 Electron 中使用 LiveKit 实现屏幕共享-蚊子的前端博客 在 Electron 应用中,如何获取 Mac 的摄像头和麦克风权限-蚊子的前端博客 微说 | 不要总让台湾艺人表态-蚊子的前端博客 微说 | 他们总说要更开放-蚊子的前端博客 自己封装 uuid 踩的一个小坑-蚊子的前端博客 CKEditor5 光标在粘贴的图片下一行-蚊子的前端博客 软考出成绩了,没过-蚊子的前端博客 微说 | 也许有一天我们这个社会会进步-蚊子的前端博客 好久没有更新文章了-蚊子的前端博客 微说 | 中共中央关于进一步全面深化改革,推进中国式现代化的决定-蚊子的前端博客 微说 | 关于惩罚性赔偿-蚊子的前端博客 微说 | 生孩子是谁的事儿!-蚊子的前端博客 微说 | 给群众一些选择电视剧的空间-蚊子的前端博客 微说 | 活着-蚊子的前端博客 微说 | 借用睡前消息里面的一段话-蚊子的前端博客 微说 | 曾经梦寐以求的,现在却没了追求-蚊子的前端博客 微说 | 我小时候的那些书本-蚊子的前端博客 给博客添加了一个微说的功能-蚊子的前端博客 今年的双 11 很平静-蚊子的前端博客 微说 | 贪就是贪-蚊子的前端博客 微说 | 总有人说穿越小说为什么不发展工业和科技!-蚊子的前端博客 微说 | 花了很多时间和精力,-蚊子的前端博客 2025 年终于是增加了 2 天的假期-蚊子的前端博客 微说 | 这是我的第一条微说,-蚊子的前端博客 如何手写 Array 的 forEach 方法-蚊子的前端博客 看到聚美优品的没落,真是唏嘘-蚊子的前端博客 如何手写 Array 的 map 方法-蚊子的前端博客 秋意正浓时-蚊子的前端博客 收藏几个常用但访问比较慢的组件的官网网址-蚊子的前端博客 从 Axios 源码分析如何支持 fetch 方法的-蚊子的前端博客 1024,给博客评论区所有的头像戴上一顶可爱的帽子-蚊子的前端博客 使用 GitHub 搭建了一个静态博客-蚊子的前端博客 在 JavaScript 如何判断变量是否为空-蚊子的前端博客 我为什么还在坚持我的个人博客-蚊子的前端博客 不要再手动拼接 URL 参数,请使用 URLSearchParams-蚊子的前端博客 使用 NodeJs 向百度资源推送链接-蚊子的前端博客 工具链极度内卷,留给开发者的性能优化手段已经不多了-蚊子的前端博客 在 React 中显示多个标签,超出省略并可以 hover 显示更多-蚊子的前端博客 蚊子的前端博客:探索与分享前端技术的奇妙之旅-蚊子的前端博客 互联网程序员前景真的一片黑暗吗?-蚊子的前端博客 Deno2.0 正式发布,向 Node.js 兼容-蚊子的前端博客 React 请求数据别再使用 useEffect 和 useState,试试 SWR 吧!-蚊子的前端博客 React 的 useEffect 的一些使用场景和技巧-蚊子的前端博客 前端趣闻之 JavaScript 语言的诞生和发展-蚊子的前端博客 如何合并同一接口的相同参数的请求-蚊子的前端博客 百度搜索中关于蚊子前端博客的奇怪的检索数据-蚊子的前端博客 使用 React 实现 todo list 的 curd 操作-蚊子的前端博客 React 模板中为什么可以用逻辑与运算符-蚊子的前端博客 评论回复在一张表里的评论系统如何进行分页-蚊子的前端博客 React 组件多次调用时如何区分不同的 div 容器-蚊子的前端博客 安全赋值运算符,再也不用写 try-catch 了-蚊子的前端博客 使用 nextjs 重构我的个人博客-蚊子的前端博客 基于 gitlab 的 webhook 向飞书发通知-蚊子的前端博客 数字转换为更高量级单位的工具方法-蚊子的前端博客 如何避免旧请求的数据覆盖掉最新请求-蚊子的前端博客 JavaScript 中 toString 的冷知识-蚊子的前端博客 React 中 useState 和 useRef 与全局变量的区别-蚊子的前端博客 从面试官的角度分析下简历中存在的问题-蚊子的前端博客 使用 React 实现 6 个输入框的短信验证码功能-蚊子的前端博客 反驳那些要实时刷新页面的前端部署方案-蚊子的前端博客 JSON stringify 的一些不常见使用-蚊子的前端博客 JavaScript 中数组 Array 的常见操作-蚊子的前端博客 前端如何提升用户的交互体验-蚊子的前端博客 没有日的日期在iOS中报 Invalid Date 的探究-蚊子的前端博客 飞书很好,但还是没抗住-蚊子的前端博客 给 Antd 的 DatePicker 组件实现带有至今的功能-蚊子的前端博客 antd 的 InputNumber 输入框添加左右的加减按钮-蚊子的前端博客 给想进国央企的同学介绍一款招聘软件「国聘」-蚊子的前端博客 基于 React 和 antd 实现的图片裁剪压缩功能-蚊子的前端博客 uniapp 中 checkbox 中的 checked 不生效的方案-蚊子的前端博客
NodeJs:腾讯新闻构建高性能的 react 同构直出方案-蚊子的前端博客
author · 2019-11-09 · via 蚊子的前端博客

腾讯新闻抢金达人活动 node 同构直出渲染方案的总结文章中我们整体了解了下同构直出渲染方案在我们项目中的使用。正如我在上篇文章结尾所说的:

应用型技术的难点不是在克服技术问题,而是在于能够不断的结合自身的产品体验,发现其中存在的体验问题,不断使用更好的技术方案去优化用户的体验,为整个产品发展添砖加瓦。

我们在根据产品的体验效果选择了 react 同构直出渲染方案,必然也要保证当前方案的可用性和可靠性。例如我们的服务能同时支撑多少人访问,当用户量增大时是否可以依然保证用户的正常访问,如何保证 CPU、内存等正常运作,而不被一直占用无法释放等。因此,这里我们应当下我们项目的几项数据:

  1. 项目一天的访问量是多少,高峰期的访问量是多少,即并发的用户量有多少;
  2. 我们的单机服务最大能支持多少 QPS;
  3. 高并发时的服务响应时间如何,页面,接口的失败率有多少;
  4. CPU 和内存的使用情况,是否存在 CPU 使用不充分或者内存泄露等问题;

这些数据,都是我们上线前要知道的。压力测试的重要性就提现出来了,我们在上线前进行充分的测试,能够让我们掌握程序和服务器的运行性能,大致申请多少台机器等等。

1. 初次压力测试 #

我们这里使用autocannon来对项目进行压测。注意,我们现在还没有进行任何的优化措施,就是要先暴露出问题来,然后针对性的进行优化。

每秒钟 60 的并发,并持续 100 秒:

autocannon -c 60 -d 100

压测后的数据:

初始压测的数据-蚊子的前端博客

从图片中可以看到,每秒 60 的并发请求量时,QPS 平均有 266 左右,不过还有 23 个请求超时了,响应时间还可以,99%的请求在 1817ms 毫秒内完成。就目前这几项数据来看,数据处理能力并不理想,我们还有很大的提升空间。

2. 解决方案 #

针对上面压测出来的数据不理想,我们这里需要采取一些措施了。

来吧-蚊子的前端博客

2.1 内存管理 #

我们现在写纯前端时,几乎已经很少关注内存的使用了,毕竟在前端发展的过程中,内存的垃圾回收机制相对来说比较完善,而且前端页面的生存周期比较短。如果真是要特别注意的话,也是早期在 IE 浏览器中,js 与 dom 的交互过程中可能会产生内存的泄露。而且如果真会真要是泄露的话,也只会影响当前终端的用户,其他的用户暂时不会受到影响。

而服务端则不同,所有用户都会访问当前运行的代码,只要程序有一丁点的内存泄露,在成千上万的访问量下,都会造成内存的堆积,垃圾无法回收,最终造成严重的内存泄露,并导致程序崩溃。为了预防内存泄露,我们在内存管理方面,主要三方面的内容:

  1. V8 引擎的垃圾回收机制;
  2. 造成内存泄露的原因;
  3. 如何检测内存泄露;

Node 将 JavaScript 的主要应用场景扩展到了服务器端,相应要考虑的细节也与浏览器端不同, 需要更严谨地为每一份资源作出安排。总的来说,内存在 Node 中不能随心所欲地使用,但也不是完全不擅长。

2.1.1 V8 引擎的垃圾回收机制 #

在 V8 中,主要将内存分为新生代和老生代两代。新生代的对象为存活时间比较短的对象,老生代中的对象为存活时间较长的或常驻内存的对象。

默认情况下,新生代的内存最大值在 64 位系统和 32 位系统上分别为 32 MB 和 16 MB。V8 对内存的最大值在 64 位系统和 32 位系统上分别为 1464 MB 和 732 MB。

为什么这样分两代呢?是为了最优的 GC 算法。新生代的 GC 算法 Scavenge 速度快,但是不合适大数据量;老生代针使用 Mark-Sweep(标记清除) & Mark-Compact(标记整理) 算法,合适大数据量,但是速度较慢。分别对新旧两代使用更适合他们的算法来优化 GC 速度。

2.1.2 内存泄露的原因 #

内存泄露的情况有很多,例如内存当缓存、队列、重复的事件监听等。

内存当缓存这种情况中,通常有用一个变量来缓存数据,然后没有过期时间,一直填充数据,例如下面一个简单的例子:

let cached = new Map();

server.get('*', (req, res) => {
    if (cached.has(req.url)) {
        return cached.get(req.url);
    }
    const html = app.render(req, res);
    cached.set(req.url, html);
    res.send(html);
});

除此之外,还有闭包也是其中的一种情况。这种使用内存的不好的地方是,它没有可用的过期策略,只会让数据越来越多,最终造成内存泄露。更好的方式使用第三方的缓存机制,例如 redis、memcached 等,这些都有良好的过期和淘汰策略。

同时,也有一些队列方面的处理,例如有些日志的写入操作,当海量的数据需要写入时,就会造成队列的堆积。这时,我们设置队列的超时策略和拒绝策略,让一些操作尽快地释放掉。

再一个就是事件的重复监听。例如对同一个事件重复监听,忘记移除(removeListener),将造成内存泄漏。这种情况很容易在复用对象上添加事件时出现,所以事件重复监听可能收到如下警告:

setMaxListeners-蚊子的前端博客

Warning: Possible EventEmitter memory leak detected. 11 /question listeners added。Use emitter。setMaxListeners() to increase limit

2.1.3 排查的手段 #

内存泄露-蚊子的前端博客

我们从内存的监控图中可以看到,在用户量基本保持不变的情况下,内存是一直在缓慢上涨,说明我们产生了内存泄露,使用的内存并没有被释放掉。

这里我们可以通过node-heapdump等工具来进行判断,或者稍微简单点,使用--inspect命令实现:

node --inspect server.js

然后打开 chrome 链接chrome://inspect来查看内存的使用情况。

chrome-inspect-蚊子的前端博客

通过两次的内存抓取对比发现,handleRequestTimeout()方法一直在产生,且每个 handle 方法中有无数个回调,资源无法被释放。

通过定位查看使用的 axios 代码是:

if (config.timeout) {
    timer = setTimeout(function handleRequestTimeout() {
        req.abort();
        reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', req));
    }
}

这里代码看起来是没任何问题的,这是在前端处理中一个很典型的超时处理解决方式。

由于 Nodejs 中,io 的链接会阻塞 timer 处理,因此这个 setTimeout 并不会按时触发,也就有了 10s 以上才返回的情况。

貌似问题解决了,巨大的流量和阻塞的 connection 导致请求堆积,服务器处理不过来,CPU 也就下不来了。

通过定位并查看axios 的源码

if (config.timeout) {
    // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
    // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
    // At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up.
    // And then these socket which be hang up will devoring CPU little by little.
    // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
    req.setTimeout(config.timeout, function handleRequestTimeout() {
        req.abort();
        reject(
            createError(
                'timeout of ' + config.timeout + 'ms exceeded',
                config,
                'ECONNABORTED',
                req
            )
        );
    });
}

额,我之前使用的版本比较早,跟我本地使用的代码不一样,说明是更新过了,再查看这个文件的9 月 16 日的改动历史

seTimeout-axios-蚊子的前端博客

这里我们就需要把 axios 更新到最新的版本了。而且经过本地大量测试,发现在高负载下 CPU 和内存都在正常范围内了。

2.2 缓存 #

缓存真是性能优化的一把好手,服务不够,缓存来凑。不过缓存的类型有很多种,我们应当根据项目的实际情况,合理地选择使用缓存的策略。这里我们使用了 3 层的缓存策略。

缓存-蚊子的前端博客

在 nginx 中,可以使用 proxy_cache 设置要缓存的路径和缓存的时间,同时可以启用proxy_cache_lock

当 proxy_cache_lock 被启用时,当多个客户端请求一个缓存中不存在的文件(或称之为一个 MISS),只有这些请求中的第一个被允许发送至服务器。其他请求在第一个请求得到满意结果之后在缓存中得到文件。如果不启用 proxy_cache_lock,则所有在缓存中找不到文件的请求都会直接与服务器通信。

不过这个字段的启用也要非常慎重,当访问量过大时,会造成请求的堆积,必须等待第一个请求返回完成后,才能处理后面的请求。

proxy_cache_path /data/cached keys_zone=answer:16m levels=1:2 inactive=60m;

server {
    location / {
        proxy_cache answer;
        proxy_cache_valid 1m;
    }
}

在业务层面,我们可以启用 redis 缓存,来缓存整个页面、页面的某个部分或者接口等等,当穿透 nginx 缓存时,可以启用 redis 缓存。使用第三方缓存的特点我们在之前的文章也说了:多个进程之间可以共享,同时减少项目本身对缓存淘汰算法的处理。

当前面的两层缓存失效时,进入到我们的 node 服务层。二层的缓存机制,能实现不同的缓存策略和缓存粒度,业务需要根据自身场景, 选用适合自己业务的缓存即可。

3. 效果 #

这时我们项目的性能怎样了呢?

autocanon -c 1000 -d 100

压力测试-蚊子的前端博客

从图片里可以看到,99%的请求在182ms内完成,每秒平均处理的请求有15707左右,相比我们最开始只能处理200多个请求,性能足足提升了60倍多。

蚊子的前端博客