前端工程化与质量保障:系统部署

38 阅读16分钟

系统部署指将代码部署到服务器上,它是工程化中的重要环节,同样经历了长时间的发展。

1. 前端部署的发展

最初,网页是用来帮助科学家们查阅文档的,所以全是HTML静态页面。一般将HTML文件直接托管在服务器上,通常不会更新。

后来,网页开始从学术界向普通民众普及,PHP、Java等服务端语言逐渐开始提供模版引擎的渲染能力,网页从静态化走向动态化。但前端尚未独立出来,网页的部署还是和服务端裹挟在一起。

2004-2005年,Google 先后发布了两款重量级的Web产品:Gmail 和 Google Map,它们大量使用了AJAX技术,页面不需要刷新就可以与服务器进行通信,彻底颠覆了用户体验,随后动态页面火速席卷互联网界。AJAX是一项革命性的技术,它使得前端拥有了独立获取数据的能力,与后端实现了很大程度的解耦。而此时的前端部署尚处于空白阶段,前端工程师只能摸索着前进。一般来讲,就是通过FTPshell脚本直接将以下的文件上传到服务器

├── assets // 静态资源
├── css // 样式文件
├── html // 子页面的html文件
├── index.html // 首页
└── js // 脚本文件,包括业务脚本和第三方脚本

后来,随着页面功能复杂度的上升和前端生态圈的不断丰富,前端工程开始引入状态管理、路由管理等方案,项目工程结构也变得越来越复杂。同时,用户量的增加带来的大量页面请求,服务端机器的数量也不得不增加。此时,直接将文件上传到服务器的简单部署方式开始受到各种挑战和质疑,逐渐被弃用了,前端工程师开始寻求更加稳定可靠的工程化部署方案

2. 发布类型

2.1 覆盖式发布

覆盖式发布是指前端将构建好的新的资源文件(如 HTML、CSS、JavaScript 等)直接上传到服务器,覆盖旧的资源文件。上传的方式一般是使用FTP工具进行单点上传,或者使用shell脚本批量上传。

早期的页面应用并不复杂,覆盖式发布不仅简单,还能有效节省硬盘空间。在很长的一段时间里,覆盖式发布都是前端的主要发布手段。

随着页面应用的复杂度和访问量上升,覆盖式发布的问题逐渐暴露。比如:

  • 上传时无法同时覆盖所有文件,文件更新不同步可能会导致样式、接口的不兼容,影响用户体验
  • 没有历史版本的保留,一旦发布出现问题,很难快速回滚到之前的稳定版本

为了避免以上问题,开发人员一般会选择在页面访问量处于低谷时部署系统,以减少出错的概率。通常需要在深夜部署,这增加了部署人员的负担,而且也不能完全避免问题。

2.2 非覆盖式发布

非覆盖式发布将构建好的新的资源文件上传到服务器时只做增量更新,不会覆盖旧的资源。常见的方式有版本化发布,即每次发布的文件带有不同的版本号后缀,服务器会同时保存不同版本的文件。例如,新发布的style.css文件命名为style-v2.css,原来的style-v1.css依然保留,可以被旧的页面访问,这样就可以有效避免资源上传间隔导致的样式错乱问题。

随着文件数量变多、依赖关系变得复杂,手动添加版本后缀的方式也不可靠了,一般会采用文件的哈希值作为后缀。当文件内容更新时,对应的哈希值也会变化,以确保文件名称的唯一性。

非覆盖式发布有效地解决了覆盖式发布的问题:

  • 在资源部署期间不会出现页面问题
  • 旧的资源不会被覆盖,可以快速回滚

非覆盖式发布的问题:

  • 不替换旧的资源,会在服务器上积累大量的无用资源,这个问题只需要定时清理即可解决。

3. 资源管理

3.1 资源加速

起初,前端资源都是直接部署在服务器上的,用户访问时直接向服务器请求资源。随着也年访问量的增加,这样的方式会导致页面加载速度越来越慢:

  • 源服务器通常集中在同一区域,不同地区下载资源的网络延迟不一样
  • 浏览器请求资源时需要与服务器建立连接,在资源下载期间会占用服务器带宽。当并发连接超出服务器负担时,就会导致新的请求无法及时响应

为了解决上述问题,开发人员采用了动静分离的方案对网站部署进行优化。动静分离就是将网站的静态资源托管到其他服务器上,与网站应用的服务器隔离开,从而提高用户访问静态资源的速度,降低服务器的压力。

用于托管静态资源的服务器通常是内容分发网络(Content Delivery Network,CDN)。CDN 是一个分布式服务器网络,它会在全球不同的地理位置设置节点服务器。当将静态资源托管在 CDN 上时,它们会被缓存到离用户地理位置较近的 CDN 节点。当用户请求访问包含这些静态资源的网页时,浏览器会从距离最近的 CDN 节点获取资源,而不是从原始的服务器获取。这样一来,大大缩短了资源传输的距离和时间,能够显著加快网页的加载速度,并且可以有效减轻原始服务器的负载压力,尤其在应对高流量访问静态资源的场景下,能够更好地保障用户体验。

3.2 更新延迟

CDN实现静态资源加速的手段主要有两种:一种是就近分配服务器响应下载请求,另一种就是利用浏览器的缓存机制进行资源缓存。对于后者,就需要考虑静态资源缓存导致的更新延迟问题。

当静态资源被托管到CDN上,资源的更新和缓存策略就由CDN服务商制定,从而引发了资源更新问题。CDN通过HTTP响应头中与缓存相关的字段来控制缓存策略,包括协商缓存强缓存

  • 协商缓存:浏览器在加载资源时会向服务器发送一个请求,询问该资源是否进行了更新,如果资源有更新则服务器返回200状态码和新的资源,否则服务器返回304状态码,浏览器直接使用本地的资源缓存。协商缓存的关键在于判断资源是否进行了更新,主要依赖于Last-ModifiedETag这两个响应头信息。

    • Last-Modified:服务器首次返回资源时,会在响应头中带上Last-Modified字段,它记录了资源最后被修改的时间。浏览器在后续请求该资源时,会将这个值放到请求头的If-Modified-Since字段中,服务器会判断这个值与请求资源的Last-Modified是否匹配。

    • ETag:服务器首次返回资源时会带上ETag,表示资源的唯一标识符。浏览器后续请求时会将这个值放到请求头中的If-None-Match字段,服务器会判断这个值与请求资源的ETag是否匹配。

  • 强缓存:浏览器直接判断本地资源缓存是否过期,而不需要向服务器发送请求来验证资源是否更新。强缓存判断资源是否过期主要依赖ExpiresCache-Control响应头信息:

    • Expires:指定了资源过期的具体时间,使用绝对时间
    • Cache-Control:通过max-age指定资源的有效时长,使用相对时间

有两方面的原因可能会导致资源更新延迟

  • CDN厂商使用强缓存代替协商缓存:协商缓存虽然也会使用本地缓存,但是在校验资源是否过期时依然会与服务器建立连接,占用一定的服务器资源。部分CDN厂商为了降低服务器的开销,降低运营成本,会使用强缓存来代替协商缓存。此时,只要资源还没到过期时间,用户在浏览器中加载的就是本地缓存。
  • 资源下发延迟:CDN边缘节点的资源更新很大程度上依赖于源站的资源下发,更新到资源下发到达边缘节点存在延迟,在此期间用户在边缘节点获取到的仍是旧资源。

资源更新延迟可能导致用户访问的页面不是预期的页面,借助文件哈希值可以解决这个问题。文件哈希值是根据文件内容计算的,更新的文件会使用新的文件名。比如,旧的资源是style_12xab.css,更新后的资源是style_2e8hd.css,它们的文件名不一样,不会被视为同一个资源。

  • 不使用本地缓存:即使旧的style_12xab.css资源被强缓存到本地,新的资源style_2e8hd.css由于没有本地缓存,浏览器还是会请求CDN边缘节点加载。
  • 回源获取源站新资源:如果CDN边缘节点尚未收到源站下发的style_2e8hd.css新资源,就会触发CDN回源机制,向源站发出请求并获取新资源。

通过为文件添加哈希值后缀,利用浏览器资源的缓存机制CDN的回源策略,开发人员就可以有效地解决资源更新延迟的问题。

4. 灰度发布

随着网页的用户规模不断增大、版本更新越来越频繁,以及每次版本上线都存在风险,产品人员要承受极大的压力,而灰度发布可以很好地规避风险。

灰度发布(又名金丝雀发布)是指在黑和白之间平滑过渡的发布方式,它先选择一部分用户使用新版本的功能,通过对这部分灰度环境的密切监测,包括系统性能指标、功能是否正常、用户反馈等,来评估新版本的稳定性和可用性。如果在灰度发布过程中没有发现严重的问题,就可以逐步进行灰度放量,直到所有用户都使用新版本;若发现问题,则可以及时进行灰度缩量回滚,控制影响范围。它使得软件发布过程更加平稳和可控。

灰度发布的本质是将新旧版本隔离开,从而降低发布风险。

灰度隔离

灰度发布的本质是将新旧版本隔离开,根据实现方式可以分成硬件隔离软件隔离

硬件隔离

硬件隔离是指在物理硬件层面将新旧版本的系统进行分隔。例如,使用不同的服务器来部署新版本和旧版本。当用户访问页面时,从由流量入口对请求进行解析,如果带有灰度标识,则将请求转发到灰度机器上,否则就转发到正常机器上。

机器分组是最传统的灰度分流手段之一。一方面,它直接从物理层面进行隔离,能最大程度地避免新旧版本之间可能产生的干扰,而且可以清晰地评估新旧版本在实际硬件环境中的性能表现。另一方面,它带来的硬件开销也比较大。适用于对资源占用和性能要求差异较大的新旧版本,或者在金融、电信等对系统稳定性数据安全极为敏感的行业。

软件隔离

软件隔离是指通过技术手段将代码进行隔离,从而实现灰度分流。采用软件隔离的灰度分流方案可以最大程度利用机器资源,降低运行成本,但是由于灰度版本和普通版本共用机器,灰度版本有时可能会影响普通版本的使用。

软件隔离有各种各样的实践方式,比如资源隔离实例隔离

资源隔离

资源隔离主要用于静态资源的灰度分流,通过文件名将新旧版本的资源进行隔离,其原理类似于非覆盖式发布。比如,当请求从流量入口进入时,带有灰度标识的流量直接转发到index-gray.html,非灰度的流量被转发到index.html,然后再从页面入口请求带有文件哈希值的资源,这样就可以保障资源相互隔离,互不影响。

实例隔离

实例隔离用于服务端的灰度分流,通过启用多个应用实例来分别响应灰度内外的请求。比如使用Node应用实现了接口,则可以创建个应用实例,分别部署在不同的端口用于处理灰度和普通请求。

由于实例隔离共享硬件资源,所以可能会出现灰度版本影响正常版本的情况。比如灰度实例抢占正常实例的计算资源,导致正常实例的请求耗时增加;或者灰度实例引起机器崩溃,导致正常实例也崩溃了。因此实例隔离可以有效利用硬件资源降低成本,但是在稳定性方面不如硬件隔离。

A/B测试

A/B测试也是软件隔离的一种方式,它是指在项目工程中利用代码分支的能力控制代码的执行逻辑,从而为用户提供不同的页面和功能。

if (isGray) {
    // 执行功能A
} else {
    // 执行功能B
}

A/B测试的优点在于它控制的功能颗粒度是最细的,通过逻辑控制让新旧版本并行运行,互不干扰,同时可以收集用户对不同版本的反馈数据。而它的缺点也很明显,就是对代码有侵入性。A/B测试需要在代码中手动埋点,并且在灰度全量后还需要删除A/B的代码,再进行一次全量发布。它适用于对用户体验和功能反馈比较关注的灰度发布场景,如互联网产品的界面优化、功能微调等。

5. 放量策略

百分比放量

百分比放量基于百分比对用户进行灰度放量。例如,在灰度发布开始时,将新版本部署给 10% 的用户,然后根据一定的时间间隔(如每天或者每小时)或者基于对系统性能和反馈的观察,逐步增加这个比例,如增加到 20%、30% 等。这个比例的计算可以是基于总的活跃用户数,也可以是基于总的请求次数。

这种策略适用于用户群体庞大且相对均匀的情况,如大型的互联网应用、电商平台等。它的优势在于操作简单,容易理解和控制。通过逐步增加比例,可以比较平稳地观察新版本在不同规模用户下的表现,及时发现可能出现的问题。同时,根据比例进行放量也便于在出现问题时进行快速的回滚,比如直接将比例调回到上一个稳定的阶段。

进行百分比放量时需要注意以下两点:

  • 百分比不变时,需要保证每次命中的灰度用户是一致的,避免用户访问页面交替出现新旧版本
  • 百分比调整时,需要保证调整前后命中的灰度用户是一致的,保证缩量时异常用户能够有效回滚

基于以上两点,提供一个简单的百分比放量策略。比如可以对用户ID进行哈希映射,这样可以保证每次命中的灰度用户是一致的,在灰度缩量时也可以有效进行回滚。

名单放量

名单放量包括白名单放量黑名单放量

  • 白名单放量:将白名单用户作为灰度发布的对象
  • 黑名单放量:对黑名单用户屏蔽灰度版本。常用于过滤百分比放量中需要屏蔽的用户。当用户同时命中多个灰度放量策略时,黑名单放量一般具有最高优先级。

名单放量的具体方式有枚举放量区间放量

  • 枚举放量:明确地列出用户个体名单,适用于用户数量相对较少且能够明确界定测试对象的情况,比如公司内部软件测试、面向特定合作伙伴的软件试用等。
  • 区间放量:根据用户的某些属性的取值范围来确定用户群体,能够针对特定类型的用户群体进行放量,同时又比逐个枚举用户更加高效。

自定义放量

自定义放量是一种更加灵活的策略,它结合了多种因素来决定放量的方式。可以根据业务规则、系统性能指标、用户行为数据等来动态调整新版本的发布范围。适用于复杂多变的业务场景和系统环境。比如,在一些对性能和稳定性要求极高的金融系统或者云计算系统中,自定义放量可以根据系统的实时性能指标(如 CPU 使用率、内存占用、网络带宽等)来灵活调整灰度发布的范围,确保系统的稳定运行。

6. 发布回滚

每次系统部署都有引入漏洞的可能性,当部署的代码出现问题时,开发人员需要立刻进行回滚,先确保生产环境处于正常可用的状态,再去排查问题原因。

发布回滚时,如果要重新构建,那将浪费大量时间和精力。为了快速回滚,可以将每次构建的资源输出到build目录中,用发布时间、commit hash等标记资源版本,那么回滚时只需解压缩对应的资源并上传到服务器,即可快速完成回滚发布。