背景介绍


当下devops如日中天,持续集成成为敏捷开发过程中不可或缺的一环,为我们带来了极大的便利。

然而如果运用不当,则持续集成有可能带来较大的性能问题,降低开发效率。

无论是开发应用、组件或工具库,一般都需要经过拉取代码、安装依赖、构建应用、发布等过程。这中间的每一个环节,都有可能存在性能问题。值得去针对性的优化。尤其是安装依赖这个过程,受网络条件、磁盘速度等的影响,有可能会表现出较大的性能问题,或极强的不稳定性。

本文就主要以web开发的场景,谈一下持续集成中“安装依赖”环节中的性能优化。

现存问题


web开发过程中,一般使用 npm 或 yarn 工具管理依赖包。一个复杂的前端应用,依赖的npm包的数量会达到上千个甚至更多,即使在较好的网络条件下,安装上千个包可能也会需要两三分钟甚至更久。然而如果每次持续集成都消耗两三分钟甚至更久的时间在安装依赖上,整体的效率就会大大降低,每次提交变更后,要好几分钟才能看到效果,如果网络条件不佳,就更无法估计了。

解决思路


我们很容易就可以注意到,一个应用的底层依赖不会频繁变化,如果每次都重新安装一次,实际上是浪费了资源的。但是其变化的频率又没有低到可以忽略的地步,因此还是要能自动化处理依赖的变更。

因此,我们需要的是一种能正常处理依赖变更的缓存机制。

说到缓存,其实 npm 和 yarn 等工具已经内置了缓存机制,它们能避免重复下载。不过它们的默认行为还不够完善,需要微调。

网络缓存


网络缓存可以分多个层次进行,比如说本地缓存已经下载过的包,避免二次请求,或者在局域网搭建本地镜像仓库。

搭建本地npm镜像,可以通过cnpm实现,参考其官方网站:https://cnpmjs.org/

通过本地缓存、本地镜像等方式,可以降低网络请求延迟,大幅提升安装速度。这样就满足了吗?其实我们还可以更进一步,将文件复制等操作一并简化。这个秘诀就是Docker Cache。

Docker Cache


Docker镜像不是一个个独立的文件,而是由多层layer组合而成。这样很多类似的镜像,就不需要重复地占用空间。

在docker build过程中,会读取Dockerfile中的一条条指令,建立多层layer,最终集成为一个整体,即为目标镜像。因为每一个步骤执行完之后都有一个阶段成果,即一层layer。当前置条件都没有变化的时候,二次构建可以直接利用现存的layer,跳过构建。

举例来说,一个常见的前端应用构建Dockerfile可能是这样的:

  1. FROM node:10
  2. ADD . /code
  3. WORKDIR /code
  4. RUN npm install


当文件没有变化时,ADD . /code 这个过程会直接利用cache,然后之后的RUN npm install也会直接跳过。这样npm install整个过程直接跳过了,自然也没有网络请求、I/O操作等一堆任务,速度也就大大加快了。

代码变更


Docker cache的前提是文件没有发生变化,这个前提不容易达到。往往是虽然没有修改 package.json,不需要重新安装依赖,但改了其他代码,导致整个项目都无法命中cache,不得不重新安装依赖。

那有什么办法能解决这个问题呢,答案就是关键文件前移。在执行 ADD . /code 这种大概率无法命中缓存的操作之前,先单独拷贝一次 package.json、package-lock.json、.npmrc等影响依赖安装的文件。

  1. FROM node:10
  2. COPY ./package.json ./package-lock.json .npmrc /code
  3. WORKDIR /code
  4. RUN npm install
  5. ADD . /code


以上就是持续集成过程中“安装依赖”部分大概的优化思路,希望对您有所帮助。