“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案

  • 2021-07-30
  • 本文字数:5101 字

    阅读完需:约 17 分钟

NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案

前言


按照国际惯例,正文开始之前,我们先简单介绍下目前市面上的 NPM 私库开源框架


  • Verdaccio

Verdaccio 是 Sinopia 开源框架的一个分支。它提供了自己的小数据库,以及代理其他注册中心的能力(例如:npmjs.org 网站),配置以及部署相对简单,一步到"胃"。如果公司的私包比较少的话或者你想偷懒,可以考虑一下。


  • Cnpmjs.org

大名鼎鼎的 CNPM,想必各位早就感受到了它的速度之“快”,没错,它的 Register 服务就是淘宝镜像 (https://registry.npm.taobao.org/)。主要是基于 Koa、MySQL 和简单存储服务的企业专用 NPM 注册和 WEB 服务,其中最强大的功能就是它的同步模块机制(定时同步所有源 Registry 的模块、只同步已经存在于数据库的模块、只同步 Popular 模块)。


  • Nexus

后端开发的小伙伴应该比较熟悉。Nexus2 主要是用于 Maven/Gralde 仓库的统一管理,而 Nexus3 则添加了 NPM 插件,可以对 NPM 提供支持,其中 NPM 仓库有三种类型,分别是 Hosted(私有仓库)、Proxy(代理仓库)、Group(组合仓库)。


总体来讲,抛开 Nexus,虽然 Cnpmjs.org 在部署过程以及总体设计方案上相对于 Verdaccio 复杂的多,但是它提供更高的拓展性,定制性,可以支持多种业务使用场景。接下来,我们分别从 Cnpmjs.org 容器化部署数据迁移OSS 容灾备份等内容,层层展开。


Cnpmjs.org 容器化部署


目前,公司的应用部署都是容器化部署,内部搭建了 Ipaas 平台,应用流程化部署以及一键发布。而 Cnpmjs.org 也附带了 Dockerfile 以及 docker-compose.yml 文件,所以,这里大致讲解下怎么用 Docker 部署吧。


  • 首先让我们看看 Dockerfile 文件

FROM node:12MAINTAINER zian yuanzhian@cai-inc.com
# Working enviromentENV \    CNPM_DIR="/var/app/cnpmjs.org" \    CNPM_DATA_DIR="/var/data/cnpm_data" 
# Shell 格式# 在 Docker Build 时运行RUN mkdir -p ${CNPM_DIR}
# 指定工作目录:用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在WORKDIR ${CNPM_DIR}
# 复制指令:从上下文目录中复制目录或文件到容器里指定的路径COPY package.json ${CNPM_DIR}
RUN npm set registry https://registry.npm.taobao.org
RUN npm install --production
COPY .  ${CNPM_DIR}COPY docs/dockerize/config.js  ${CNPM_DIR}/config/
# 声明端口(7001 为 Register 服务、7002 为 web 服务)EXPOSE 7001/tcp 7002/tcp
# 匿名数据卷:在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。VOLUME ["/var/data/cnpm_data"]
RUN chmod +x ${CNPM_DIR}/docker-entrypoint_prod.sh
# Entrypoint # Exec 格式# 在 Docker Run 时运行# Dockerfile 存在多个 CMD 命令,仅最后一个生效# CMD ["node", "dispatch.js"]CMD ["npm", "run", "prod"]
复制代码


这里把 CMD 命令修改为 ["npm", "run", "prod"],因为增加了一层不同环境的 shell 脚本,目前全局变量全都存放在这里。


示例:docker-entrypoint_env.sh


export DB='db_cnpmjs'export DB_USRNAME='root'export DB_PASSWORD='123456'export DB_HOST='127.0.0.1'
export BINDING_HOST='0.0.0.0'
DEBUG=cnpm* node dispatch.js 
复制代码


  • 再修改下 docker-compose.yml 文件,这里把 mysql-db 这个服务删掉了,原因是可通过 /docs/dockerize/config.js 下的配置文件去连接公司测试环境的 MySQL 数据库,则不需要构建生成 mysql-db 镜像


version: '3' # docker版本services: # 配置的容器列表  web: # 自定义,服务名称    build: # 基于 Dockerfile 构建镜像(可增加 args )      context: .      dockerfile: Dockerfile ## 依赖的 Dockerfile 文件    image: cnpmjs.org # 镜像名称或 id    volumes:      - cnpm-files-volume:/var/data/cnpm_data    ports:      - "7001:7001"      - "7002:7002" 
复制代码


注意点:1、全局配置文件路径: /docs/dockerize/config.js;2、bindingHost 为 0.0.0.0。


  • 最后,在控制台敲下 docker-compose up -d,即以守护进程模式形式启动应用,然后打开浏览器入 http://127.0.0.1:7002,就会看到 WEB 页面。执行 npm config set registry http://127.0.0.1:7001 可设置为搭建的私库的镜像源地址,这里推荐使用 nrm,可自由切换 NPM 源。


展示站点如下图:



注意点:1、当你改变本地代码之后,先执行 docker-compose build 构建新的镜像,然后执行 docker-compose up -d 取代运行中的容器。


数据迁移


由于公司之前用的 Verdaccio 搭建的私库,要切换使用新的 NPM 私库,意味着要把之前发布过的私包全部迁移过来。大概统计了下,有 400 多个 Package,总共有  7000 多个版本,按照正常逻辑,做数据迁移首先会从数据库下手,但是 Verdaccio 并不依赖数据库。刚开始没有一点头绪,大概看了下 Cnpmjs.org 的源码,分析了当我们 publish 模块时,它是怎么把 NPM 模块 的元数据存储到数据库。


通过路由文件(/routes/registry.js)我们很容易找到 /controllers/registry/package/save.js,这个文件便是我们想要的。

核心代码:



var pkg = this.request.body; // 这里拿到 npm 模块元数据,即 package.json 文件经过 libnpmpublish模块处理过的 Json 数据var username = this.user.name; // 当前用户名var name = this.params.name || this.params[0]; // NPM 模块名var filename = Object.keys(pkg._attachments || {})[0]; // NPM 模块的压缩后的文件名var version = Object.keys(pkg.versions || {})[0]; // NPM 模块的最新版本

复制代码


// Upload Attachment
// Base64 解码,获取模块文件二进制数据。从 libnpmpublish 模块了解到 tardata.toString('base64'),即NPM 模块文件流转 Base64 字符串var tarballBuffer = Buffer.from(attachment.data, 'base64'); // 默认使用 fs-cnpm,将 NPM 模块文件保存到本地,默认保存路径:path.join(process.env.HOME, '.cnpmjs.org', 'nfs')var uploadResult = yield nfs.uploadBuffer(tarballBuffer, options);
var versionPackage = pkg.versions[version];var dist = {  shasum: shasum,  size: attachment.length};
// If nfs upload return a key, record itif (uploadResult.url) {  dist.tarball = uploadResult.url;} else if (uploadResult.key) {  dist.key = uploadResult.key;  dist.tarball = uploadResult.key;}var mod = {  name: name,  version: version,  author: username,  package: versionPackage};
mod.package.dist = dist;
// 模块数据保存到数据库var addResult = yield packageService.saveModule(mod);
复制代码


即只要我们能够拿到 NPM 模块的元数据(即 package.json 被处理过的 JSON 数据),就能把模块文件上传到文件系统或者 OSS 服务,同时数据落库。Verdaccio 有两个 API 可以拿到其私库 NPM 模块全量数据和当前 NPM 模块的 JSON 数据,路径分别是 /-/verdaccio/packages/-/verdaccio/sidebar/$PKG$,其中有 scope 的模块的请求路径是 /-/verdaccio/sidebar/$SCOPE$/$PKG$。思路已经很明确了,开始动起来吧!新增 save_zcy.js 文件,基于原来的 /controllers/registry/package/save.js 稍加改造下。

核心代码:


// 请求远程文件,并返回二进制流const handleFiles = function (url) {  return new Promise((resolve, reject) => {    try {      http.get(url, res => {        res.setEncoding('binary') // 二进制        let files = ''        res.on('data', chunk => { // 加载到内存          files += chunk        }).on('end', () => { // 加载完          resolve(files)        })      })     } catch (error) {      reject(error)    }  })};
// 获取远程模块文件的二进制数据yield handleFiles(dist.tarball).then(res => {  // 利用 Buffer 转为对象  const tardata = Buffer.from(res, 'binary')  pkg._attachments = {};  pkg._attachments[filename] = {    'content_type': 'application/octet-stream',    'data': tardata.toString('base64'), // 从缓冲区读取数据,使用 base64 编码并转换成字符串    'length': tardata.length,  };}, error => {  this.status = 400;  this.body = {    error,    reason: error,  };  return;});
复制代码


接下来我们把控制器 save_zcy.js 接入到 Registry 服务的 APP 路由上。


// 新增 fetchPackageZcy、savePackageZcy 控制器app.get('/:name/:version', syncByInstall, fetchPackageZcy, savePackageZcy, getOneVersion);app.get('/:name', syncByInstall, fetchPackageZcy, savePackageZcy, listAllVersions);
复制代码


控制器 fetchPackageZcy 作用是请求上面的 API(/-/verdaccio/sidebar// 或 /-/verdaccio/sidebar/)来拉取对应模块的 JSON 数据。



OK,接下来我们写一个定时任务,每隔一段时间执行 npm install [name],这样原来私库的 NPM 包都能够 install 并进入到上面的控制器逻辑,大功告成!


OSS 容灾备份


首先,简单说明下为什么要做 OSS 容灾备份,有以下几点。


  • 如果服务器上磁盘损坏,易丢失文件,有一定的风险

  • 若服务器磁盘爆满,可自动降级上传模块文件到 OSS


基于以上几点,我们整理了下容灾备份方案:


  • package publish



即发布模块文件时本地存储,同时上传到 OSS 作为备份,用到的插件分别是 fs-cnpmoss-cnpm


  • package install



即下载模块文件时,先判断是否是私包(即包名是否有带  scope ),如果不是私包代理到上游 Registry,若是私包先判断服务器本地是否有该私包文件,如果不存在先去 OSS 下载到本地 nfs 目录下,如果存在则直接从 nfs 目录找到模块文件,然后读取并写到 downloads 目录下,最后调用 fs.createReadStream 方法流读取该文件。


isEnsureFileExists 即判断模块文件本地是否存在,代码如下:


const mkdirp = require('mkdirp');const fs = require('fs');
function ensureFileExists(filepath) {  return function (callback) {    fs.access(filepath, fs.constants.F_OK, callback);  };}
复制代码


注意,在 OSS 下载模块文件到 nfs 之前,一定要先创建模块文件目录,方法如下:


const mkdirp = require('mkdirp');
function ensureDirExists(filepath) {  return function (callback) {    mkdirp(path.dirname(filepath), callback);  };}
复制代码


邮件通知


Cnpmjs.org 本来就带有邮件通知的功能,但只应用错误日志上报。由于我们的私包大部分都是业务组件、工具等,有时候发布正式版本的业务组件需要通知到业务组件的使用方。目前,我们采用 Maintainers 来维护,包含模块的维护者及使用者。


示例:

"maintainers": [  {    "name": "yuanzhian",    "email": "yuanzhian@cai-inc.com"  }]
复制代码


邮箱配置如下:


mail: {  enable: true,  appname: 'cnpmjs.org',  from: process.env.EMAIL_HOST,  host: 'smtp.mxhichina.com',  service: 'qiye.aliyun', // 使用了内置传输发送邮件,查看支持列表:https://nodemailer.com/smtp/well-known/  port: 465, // SMTP 端口  secureConnection: true, // 使用了 SSL  auth: {     user: process.env.EMAIL_HOST,     pass: process.env.EMAIL_PSD, //    } }
复制代码


写在文末


未来,我们还可以在 Cnpmjs.org 上做很多定制化开发,比如接入公司内部权限系统WEB 页面重构对接业务组件在线文档等等。如果你正好也需要搭建 NPM 私有库,希望这篇文章对你有所帮助。



头图:Unsplash

作者:梓安

原文:https://mp.weixin.qq.com/s/NUYooqqTklsSs77VDRLwKA

原文:NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-07-30 20:501995

评论

发布
暂无评论
发现更多内容

写作 7 堂课——【7. 复盘式写作】

LeifChen

复盘 写作技巧 8月日更

先到先得!Alibaba最新出版的JDK源码剖析手册(究极奥义版)开源

Java 编程 jdk 面试 程序人生

滴滴最看重的SpringCloud手册曾被疯狂转载,现在学还有用吗?

Java~~~

Java 架构 面试 微服务 Spring Cloud

用区块链技术实现电力供给“三流合一”

CECBC

MinIO Client 使用(二)

耳东@Erdong

Minio client 8月日更 minio client

深耕城市治理场景,百度智能云联合慧联无限推内涝智能检测预警

百度大脑

人工智能 洪水

趁着课余时间学点Python(十一)面向对象的理解(高潮)

ベ布小禅

8月日更

网络攻防学习笔记 Day103

穿过生命散发芬芳

态势感知 网络攻防 8月日更

2021年最佳VUE3 UI框架推荐

devpoint

Vue3 antd vue vue2 8月日更

清华大牛带领20位大厂专家耗时三年整理出这份2000页Java进阶指南

Java~~~

Java MySQL 架构 面试 微服务

熬夜28天肝完这25W字的九大Java核心专题,连续拿下多个大厂offer

Java~~~

Java 架构 面试 JVM 架构师

架构实战营毕业总结

thewangzl

架构训练营 - 模块四 - 作业

姑射仙人

架构训练营

小心这个陷阱:为什么总是你赔钱?

非著名程序员

认知提升 个人提升 投资理财 8月日更

fil挖矿官网有哪些?fil挖矿平台有哪些?

fil挖矿平台有哪些 fil挖矿官网有哪些

极客星球 | 应用开发的性能优化探索

MobTech袤博科技

性能

Hadoop生态系统Hive:SQL执行(一)

正向成长

hadoop hive

博睿数据分布式手机真机监测+两大核心技术,轻松掌控短信服务质量与用户体验

博睿数据

2021年7月券商App行情刷新及交易体验评测报告

博睿数据

我看 JAVA 之 并发编程【四】线程安全与JMM

awen

Java JMM 线程安全

秀到起飞!LeetCode官方推出算法面试指导手册(代码版)限时开源

Java~~~

Java 架构 面试 算法 架构师

DCS_FunTester分布式压测框架更新(二)

FunTester

分布式 性能测试 测试框架 测试开发 FunTester

Java变量使用小技巧~

Bob

Java 变量 8月日更

交易所市值管理机器人搭建,项目方市值管理软件源码

让GitHub低头!这份阿里内部的10W字Java面试手册到底有多强?

Java~~~

Java spring 面试 微服务 JVM

求阿里大神整理的分布式核心笔记出炉了,GitHub刚发布就标星85k

Java~~~

Java 架构 面试 微服务 JVM

【Takin应用日记】记一次TransmittableThreadLocal引起的业务异常

TakinTalks稳定性社区

高可用 性能压测 生产环境全链路压测 takin

员工流动大难管理?织信低代码+人事管理系统轻松掌控员工档案信息

优秀

低代码

聊聊我在腾讯和字节工作感受

程序员鱼皮

Java 互联网 求职 IT 实习

猎杀时刻!疯狂狩猎大厂offer,阿里高工总结698页Spring学习笔记

Java 编程 架构 面试 架构师

未来25年看隐私计算与区块链

CECBC

NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案_语言 & 开发_政采云前端团队_InfoQ精选文章