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

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

Claude's Blog

浅谈 npm 依赖治理 记录 Got(Node.js) 代理 HTTP 请求的坑 如何管理桌面窗口 SSR 页面 CDN 缓存实践 从 is-promise 事件我们可以学到什么 分享一个 npm dist-tag 的冷知识 从 Vimium 到 qutebrowser Verdaccio 性能优化:单机 Cluster 让 npm install 不使用缓存的方法 Verdaccio 性能优化:代理分流 CSAPP DataLab 题解 不靠谱的 Egg.js 框架开发指南 如何解决 Debian 系 Elastic apm-server 7.x 启动失败 Hexo NexT 主题升级 7.4 From Journeyman to Master Mac 上移除 EasyConnect 常驻后台进程 Nginx SWRR 算法解读 记一次 Node.js 进程挂起的 BUG 追踪 警惕 Travis CI 的 npm 缓存
Verdaccio 性能优化:上游路径转发
2019-10-22 · via Claude's Blog

背景

这里的 Verdaccio 是指用于搭建轻量级 npm 私有仓库的开源解决方案,以下简称 npm 私服。

近期观察发现,有些项目依赖了名为 npm 的 npm 包,每次项目部署时都会向私服 /npm 发起请求记录,并在监控曲线上呈明显的高耗时,这引起了我们的关注。

有些项目依赖了 npm 自身的包,每次项目部署时都会产生对私服 /npm 路由的请求记录,并在监控曲线上呈明显的高耗时,这引起了我们的关注。

原因

Verdaccio 对公共(外网)npm 包的中转存在不小的性能损耗。

其中一个问题,通过私服下载未经缓存的公共 npm 包,Verdaccio 都要等上游镜像的响应完整结束之后,才开始响应私服用户的请求。这导致 Verdaccio 的整体速度比直接用上游慢了一截。

至于会慢多少呢,要提到另一个 npm 机制:一个依赖 package 下载之前,要先到镜像地址的 =/:package/:version?= 接口获取完整的包信息,之后才会下载所需的版本。而一个模块历史发布过的版本越多,信息量越大。尤其是 npm 自身这个包,访问一下 http://registry.npmjs.org/npm 便知。

Verdaccio 慢就慢在获取包信息这一步,它必须等待上游接口响应完成,才能做相关 JSON 解析和逻辑处理。因此不仅仅是慢的问题了,还有内存和 CPU 的大量消耗。

然而这一步对于 Verdaccio 又很重要,因为它的对于此接口的缓存策略基于文件,只有拿到完整的 JSON 返回值才能将其记录到文件中。只是默认仅 2 分钟的缓存时间,让这一步操作的性价比打了折扣。

思路

从上面看,私服接口性能优化空间还很大,哪怕只是将几个体积较大的“罪魁祸首” npm 包单独优化,也能缓解私服的压力。

首先想到的是让 Verdaccio 不必等待上游全部返回就开始响应私服用户。其次是现有的缓存机制对部分低频率高开销的 package 请求形同虚设,小机器又经不起缓存扩充的资源消耗,网络带宽倒是相对不缺,降低计算成本、纯网络代理转发是一个可行的方向。

Verdaccio 会对下载的 npm 包信息做解析和记录,但其实我们并不关心那些只属于上游的包,只希望它能承担好转发工作,甚至所有公共依赖都不经过私服处理。

退一步讲,就是要弱化在私服中对这些公共依赖的处理,减少解析过程 —— 用 stream 或 buffer 完成请求转发。

实现

遗憾的是 Verdaccio 自身的接口难以复用,只好直接在其基础上增加路由(中间件)。简单粗暴,对项目的熵值影响不大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const _ = require('lodash');
const createError = require('http-errors');
const request = require('request');
const URL = require('url');

const Middleware = require('../../web/middleware');
const Utils = require('../../../lib/utils');

module.exports = function(route, auth, storage, config) {
const can = Middleware.allow(auth);


route.get('/npm', (req, res) => {

const upLinkUrl = _.get(config, 'uplinks.npmjs.url', 'https://registry.npm.taobao.org');
const packageUrl = URL.resolve(upLinkUrl, req.originalUrl);


const npmRes = request(packageUrl)
.on('error', res.report_error);


req.pipe(npmRes).pipe(res);
});

route.get('/:package/:version?', can('access'), function(req, res, next) {

});

};

上面是 stream 方式的修改,也可以把路由改写为中间件。stream 转发减轻了服务的内存压力(节省上百 MB 的临时缓冲),并减少这部分接口 50% 以上的 TTFB 响应时间,不过总体响应时间却因为 stream 有所延长。

降低机器负载的目标达成了,但压力测试证明这会大大拖慢进程的处理效率,在并发较低的情况下才能采用。

作为尝试,目前这个 patch 只用在了特定依赖。Verdaccio 可优化的方向很多,单进程可提升空间有限的情况,该把重心放在横向扩展上了。

待续

转发所有上游 npm 包的念想还未落地,虽然做起来应该很简单,但需要继续摸索 Verdaccio 结构,才好给出更合适的修改方案。

现在能给出的最简单做法就是适当调高 Verdaccio 默认 2 分钟的缓存 TTL。提升最大的做法是扩展 Verdaccio 尚未支持的 Cluster 架构……

1
2
3
4
5
request({ url: packageUrl, encoding: null }, (error, resp, body) => {
if (error) return res.report_error(error);
res.set('Content-Type', resp.headers['content-type']);
return res.send(body);
})

再者,结合自身情况,可以尝试更多玩法。如果系统内存富足,把 stream 稍微改一改,变为回调形式。缺点和 Verdaccio 一样的是必须等 resp 完整返回,但 encoding: null 确保响应结果为 buffer,能省略 JSON 解析,优点是可以基于 buffer 做 LRU Cache。