Claude's Blog

Claude's Blog

马上订阅 Claude's Blog RSS 更新: https://claude-ray.github.io/atom.xml

Verdaccio 性能优化:单机 Cluster

2019年12月31日 19:46

本篇将讨论如何解决 Verdaccio 官方本地存储方案不支持 Cluster 的问题。

前言

标题为什么叫单机 Cluster 呢?

因为多机 Cluster 已经无法使用默认的本地存储,必须配合一套新的存储方案,而官方只提供了 AWS 和 Google Cloud 的支持。这在国内已经是一道门槛,因此大概率是要用上其他云存储服务的,这意味着必须做一个 Verdaccio 插件实现必备的 add、search、remove、get 功能。

糟糕的是,倘若自己的云存储不支持查询功能,还得基于数据库再造一套轮子,甚至再加一套解决读写冲突的轮子。

一句话来说,Verdaccio 是轻量级好手,不适合也不必要承载太重的装备。重度使用的场景下,与其从头定制的存储体系,不如直接上 cnpm、Nexus 等体积更大、相对成熟的系统。

话说回来,作为尝试,我还是基于 Redis 实现了它的单机 Cluster。虽然修改的 Verdaccio 版本较旧,但其新版 V4 的架构并没有太大变化,思路还是一致的。

思路

Verdaccio 默认无法使用 PM2 Cluster 启动,有两大阻碍。

其一,缓存同步。它使用进程级别的内存缓存,没有实现进程间通讯,多进程之间缓存信息不能同步。

其二,写锁。本地存储将内容持久化到本机磁盘,只有进程级别的“锁”,多进程容易出现写文件冲突。

这两个问题处理起来其实非常简单,特别是引入 Redis 之后。

针对第一点,内存缓存可以迁移到 Redis,但是其中有大体积的 JSON 信息,不适合存在 Redis,可以用 Redis 做消息中心,管理各进程的缓存状态。

针对第二点,私服本身属于简单的业务场景,Redis 锁完全可以胜任。

实现

本应该是 Show Code 环节,可念在笔者改的版本不存在普适性,索性改成修改要点的简单罗列吧。

  • 重写 local storage,本地存储依赖一个叫 .sinopia-db.json.verdaccio-db.json 的文件,其中保存所有私服的包。这个文件的内容适合使用 Redis 的 set 结构进行替换。

  • 查找并替换所有 fs.writeFile,加锁处理。在锁的实现上,新手需要多看官方文档,大部分博客的实现都是错误的,比如忽略了解锁步骤的原子化操作。

  • 向上回溯修改的链路。

额外的补充

想来这可能是专题的最后一期,于是把不太相关的几个小问题也堆到下面吧。

只关心 Cluster 改造的看官可跳过此节,直接看末尾总结。

异步风格

由于手上的 Verdaccio 版本较老,整体还是 callback 风格,让改造多了一点工作量。我使用的 Redis 客户端为 ioredis,注意把涉及到的调用链路都改造为 async/await。

发布订阅

另一个坑点是我拿到的 Redis 其实是 Codis 集群,这套方案的一个缺点是无法使用 Redis 弱弱的发布订阅功能,也就不能直接拿来订阅更新内存缓存的消息。只好另辟蹊径,将 Redis 作为“缓存中心”,进程取缓存前先查询标志位,如果标志位存在,代表内存缓存需要更新。以进程号等信息做 key 前缀表示区分。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const os = require('os');

class CacheCenter {
constructor(prefixKey = 'updated') {
this.data = new Map();
// 利用 redis 缓存标志位,为空时表示缓存需要更新
this.prefix = prefixKey;
// 用 pm2 进程号区分缓存状态
this.ip = getIPAddress();
this.id = `${this.ip}...

剩余内容已隐藏

查看完整文章以阅读更多