2024-06-25
服务部署
0

目录

渲染之争
前端渲染方式探讨
爱恨情仇的历史
第一阶段:初期服务端渲染的缺陷
第二阶段:客户端渲染的崛起 - SPA
第三阶段:服务端渲染王者归来
服务端渲染发布困境
如何发布一个可靠的服务端渲染应用?
Serverless:小白利器
如何安全的发布资源
缓存 - 锋利的双刃剑
如何更新资源
那我们是先上线页面,还是先上线静态资源?
覆盖式发布 VS 非覆盖式发布
非覆盖式发布就真的完美了吗
部署发布的发展过程
参考

在前端开发中,发布过程是至关重要的一环。从渲染模式的选择,到如何安全地发布资源,再到整个部署发布的演变,每个环节都直接影响到应用的性能、用户体验和安全性。本次分享带你了解前端发布过程中的关键点和最佳实践。

渲染之争

前端渲染方式探讨

当前,大多数前端应用倾向于采用客户端渲染而非服务端渲染。这背后的原因有哪些呢?

image.png

客户端渲染:

当我们使用客户端渲染时,页面结构组装发生在浏览器端,浏览器会先下载 HTML 文本,紧接着浏览器解析 HTML 文本后,下载 JavaScript 脚本,等待浏览器执行 JavaScript 脚本以后, 请求接口数据,最后,页面的首屏才会展示。

如果加载的资源越多越大,那么白屏的时间也会相应拉长,我们始终是对应用功能做加法,而很少做减法,这也意味着使用客户端渲染开发的每次更新,都可能会使得白屏的时间相应增长一些。

服务端渲染:

那当我们使用服务端渲染时,页面的结构组装发生在服务器端,页面的接口请求将发生在服务器端,通过服务器直接访问接口当然是更快的,并且我们的首屏也不用等待静态资源加载,服务器端返回的 HTML 可以直接用于用户展示,白屏时间会更短,那在如今这个快节奏的社会,应用在 5s 内没有打开,用户就会怀疑应用是不是坏掉了,越短的白屏时间,越能留住客户,相应的转化率也会越高。

image.png

根据谷歌研究表明,随着页面的加载时间从 1秒 增加到 3秒,页面的跳出率会增加 32%,页面每增加 1s 等待时间,就会相应增加 16% 的跳出率。

因此,对于面向消费者(to C)的应用来说,首屏加载时间对一家互联网公司的发展至关重要。

爱恨情仇的历史

image.png

那服务端渲染这么好,可以降低首屏加载时间,提升用户的体验,为什么不用起来呢?

客户端渲染和服务端渲染之间也是有过一段爱恨情仇的历史,

  • 1990年代-2000年代初:以服务端渲染(SSR)为主,服务器生成 HTML 页面并发送到客户端。
  • 2000年代中期-2010年代中期:AJAX 技术兴起,逐渐转向客户端渲染(CSR),提升用户体验和页面交互性。
  • 2015年代后期至今:同构渲染(SSR+CSR)发展,通过框架如 Next.js 和 Nuxt.js,实现服务端和客户端渲染的结合,兼顾 SEO 和用户体验。

第一阶段:初期服务端渲染的缺陷

image.png

每一次变革,都代表着技术的进步和用户需求的演变。

最早期的服务端渲染由 JSP、PHP 领衔主演,当时也属于 WEB1.0 时代,几乎所有的应用都采用服务端渲染,但是当时的服务端渲染已经存在诸多问题:

  1. 页面无法局部刷新:每次更新页面的一小的模块,都需要重新请求一次页面,重新查一次数据库,重新组装一次 HTML,直到浏览器出现了 AJAX,这个问题才得到解决。

  2. 浏览器兼容性问题:2003年后 Internet Explorer 份额逐渐受到其他浏览器蚕食,Mozilla Firefox、Google Chrome、Safari、Opera 纷纷崛起,同时带来的也是不同的 API 标准,当时的纷乱情况和现在的小程序生态也不遑多让,几乎是每家浏览器的标准都不一样,你讲你的,我说我的,并且浏览器所支持的 API 也在日渐增多,开发者的工作量也在日渐增加,并且为了迎合各方浏览器,代码中就会出现大量的 if else 判断,因为需要通过判断是哪个浏览器,然后调用当前浏览器所支持的 API。于是,我们耳熟能详的 jQuery 便出来终结了这一切,我们可能只知道 jQuery 便于操作 DOM,链式操作等,但是它的最大功绩绝对是抹平各方浏览器差异,也因为他的出现,大家更喜欢使用客户端渲染的方式进行开发应用。

  3. 难以维护的工程:前端 JavaScript 代码和后端(JSP、PHP)代码混杂在一起,使得日益复杂的 WEB 应用难以维护,拆分前后端应用迫在眉睫,并且随着 Node.js 的大放异彩,前端已具备开发复杂应用的能力,于是便开启了一场前后端分离的运动,前后端分离,表面上看上去是代码分离,实际上一次职能上的细分。

第二阶段:客户端渲染的崛起 - SPA

image.png

随着前端框架的建立和完善,以及浏览器功能的逐步增强,前后端彻底分离已成为趋势。客户端渲染在这种背景下得到了广泛应用,其中,单页应用(SPA)开发模式成为了客户端渲染的主流。

SPA 单页应用有着良好的用户体验,切换路由时无需刷新页面,操作非常顺滑。而在过去,切换路由需要刷新页面,带来强烈的刷新感。这种体验在当时各类应用和网页争夺市场时,显得尤为重要。不顺畅的使用感往往难以留住用户。

此外,SPA 单页应用的开发也极为便捷和简单,既提升了用户体验(UX),也提高了开发人员的工作效率(PX)。用户使用时感到开心,开发人员也能更加高效地完成工作。因此,SPA 单页应用开发模式迅速占领了市场。

第三阶段:服务端渲染王者归来

客户端渲染的开发方式有这么多优点,那我们为什么又要使用回服务端渲染呢?

image.png

1、 优秀的 SEO 支持: 我们来看下 SPA 单页模式下返回的 HTML 内容

image.png

通篇都是 JavaScript 脚本 和 CSS 资源链接的引用,因为 SPA 单页的内容通常是通过 JavaScript 动态加载的,所以 SPA 下的 HTML 几乎是看不到文字的。

搜索引擎都有自己的爬虫程序,如百度的百度蜘蛛和谷歌的爬虫机器人。它们通过不断地抓取和索引网页内容,帮助搜索引擎建立庞大的网页索引库,使用户在使用搜索引擎时能够快速找到相关的网页内容。

image.png

当爬虫抓取到一个具有 SEO 功能的 HTML 页面,并且页面中包含“男装”、“女装”、“羽绒服”、“衬衫”、“短袖”、“限时促销”等关键词时,搜索引擎会将其归类为卖衣服相关的网站。

然而,当爬虫抓取到一个 SPA 单页应用的 HTML 页面时,由于页面内容主要是各类资源的引用,并且 HTML 中通常没有完整的内容,这会导致爬虫无法有效地建立索引,这也意味着在使用搜索引擎搜索时,根本无法搜索不到我们的页面。

2、 更好的用户体验: 服务端渲染(SSR)显著提升了用户体验,尤其是在首次加载页面时。通过在服务器端生成完整的 HTML 页面并将其发送给客户端,用户可以立即看到完整的内容,而不必等待客户端下载和执行 JavaScript 代码。

这种方式减少了白屏时间,使页面加载更加迅速和平滑。快速呈现内容不仅让用户感到满意,还提高了网站的易用性和可访问性,特别是对于网络状况较差或使用旧设备的用户。

3、 同构渲染: 那现在又回到和之前一样的服务端渲染,不是又要承担之前所产生的问题吗?

不是的,现在的服务端渲染方式和先前也有很大的差别,并不是旧瓶装旧酒,而是旧瓶装新酒。

因为当下的服务端渲染是建立在前后端分离的基础上,不会出现前后端代码写在一起从而强耦合的情况,并且现在采用一种同构的渲染方式:同构是服务端渲染和客户端渲染的结合

image.png

首屏采用服务端渲染,而后续页面的交互体验由客户端渲染承接。这种方式缩短了首屏的白屏时间,因为服务器端生成的 HTML 页面可以立即显示内容,有利于提升用户体验和 SEO。同时,后续的页面交互和路由跳转都由客户端渲染处理,确保用户在后续使用中的流畅体验。这种方法结合了服务端渲染和客户端渲染的优点。

服务端渲染发布困境

同构式渲染有这么多好处,并且现在也有了 Next.js、Nuxt.js 这些成熟的框架,那么,为什么不广泛使用呢?其根本原因在于服务端渲染的发布困境。因为同构渲染包含服务端渲染,首先必须在服务器上运行程序来生成和提供网页内容。相比于客户端渲染,服务端渲染需要更多的服务器资源来支撑其运行,这会增加服务器成本。

此外,对于传统前端开发者来说,发布一个包含服务端渲染的应用也面临着不小的压力和挑战。服务器的配置、维护和扩展都比纯客户端渲染复杂得多,开发人员需要掌握更多的后端知识和技能。同时,部署和监控服务端渲染的应用也需要额外的工作和工具支持,这些都是需要克服的障碍。

如何发布一个可靠的服务端渲染应用?

image.png

服务端渲染应用的高可用性性能是评估其在实际表现中重要指标

高可用性:

  • 系统稳不稳定
  • 出了故障能不能及时发现
  • 出了故障有没有应急策略

性能:

  • 流量超出阀值时有没有保护手段
  • 能不能抗住目标流量

那么我们想要构建一个企业级的服务端应用,就需要做到提升服务的可用性和保障性能,那如何提升服务的可用性呢?

我们先来看下服务可用性的计算公式:

image.png

image.png

我们的服务可用性如果要做到 3个9(99.9%),每年的宕机时间要控制在 8.76 小时内,如果是 4个9(99.99%),每年的宕机时间要控制在 52.56 分内。

从表格上我们可以看出,服务可用性和宕机时间成反比,服务可用性越高,相应的宕机时间就越短,那我们要提升服务可用性,就需要减少宕机时间减少宕机时间的有效方法包括降低宕机的概率以及在宕机后快速恢复

我们来看看什么情况下服务会宕机呢?

image.png

服务器的宕机故障可以简单归类为软件错误或漏洞、硬件故障或网络问题。一旦宕机,托管的应用程序或网站无法将正常访问,对于依赖在线服务的企业和应用程序来说,可能会带来重大的经济损失和声誉影响。我们可以通过部署监控系统监测服务器的运行状态和性能指标,设置警报机制,及时发现并通知运维团队任何潜在的故障或异常情况。

image.png

那我们遇到了流量超出系统上限时,应该怎么办?

image.png

假设我们应用服务所能承受的 QPS 上限是 3000,当高峰期流量来临,QPS 超过3000,我们可以通过限流降级的方式保护系统,以确保平稳度过高峰期

  • 限流:限制每个客户端或每个IP地址单位时间内可以发送的请求数量,限制同时与服务端建立的连接数
  • 降级:当应用宕机或流量过高时,将服务端渲染平滑降级为客户端渲染

Serverless:小白利器

image.png

那讲了这么多,我们到底应该怎么做呢,作为一个普通的传统前端开发者,对于这些都一窍不通,公司运维先前也没有 Node.js 应用的部署经验,我应该怎么去做呢?

那想要做成这一套完整的监控体系,是很难的,且在中间会踩大量的坑,不断的试错、不断的前进。如果人员资源不足够的情况下,可以考虑下使用 Serverless,Serverless 在过去几年也是相当火热的,Serverless 直译过来叫做无服务器,可能很多人听到这个词觉得很高大上,就不想去接触,那去研究Serverless具体的底层实现确实是一件很复杂的事情,但是只是作为单纯的使用者来讲,他带来的是绝对的便利,绝对的傻瓜式操作。

image.png

  • 从最早期的自建机房,我们需要自己选择机房地点、购买和维护硬件设备、管理电力和空调设备,若需要搬迁机房,则会面临更加繁重的工作量。

  • 到了云服务器时,我们的关注点主要集中在系统架构、容器化部署、运行时环境等方面,确保应用能够在云环境下安全、稳定、高效地运行。

  • 而到了 Serverless 时,我们只需要关注自己的业务代码编写,无需再关心底层的系统架构和运行时环境。云服务提供商会为我们提供可靠的基础设施和管理服务,保障系统的可用性和性能,从而让开发者能够更专注于业务逻辑的实现和创新。云函数想必大家都不陌生,它也是是 Serverless(无服务器)计算的一部分。

如何使用 Serverless?

阿里云和腾讯云都有 Serverless 的相关服务,我这边使用的是腾讯云,创建Serverless 应用也是相当简单的,填写基础应用信息、关联已有的代码仓库、配置好启动文件,我们的应用就成为了一个 Serverless 应用

image.png

同时,配套的服务也有监控信息、服务日志、限流策略等,并且 Serverless 是真正的按量收费,当无访问量时是不收费的,并且在流量大的情况下,也可以自动扩容。

image.png

感兴趣的 jym,可以自行玩耍学习 腾讯云Serverless传送口

如何安全的发布资源

那无论是客户端渲染还是同构渲染都是需要发布静态资源的,那怎么样才能安全的发布资源。

安全的反义词是危险,既然要讲安全更新资源,当然得提及何为危险更新资源。

我们先来探究下危险的源头 - 缓存。

缓存 - 锋利的双刃剑

浏览器缓存是大家非常熟悉的概念。它的主要作用是缓存资源,从而提升下一次加载的速度。

然而,为什么浏览器缓存有时会带来风险呢?在探讨这个问题之前,我们先来详细了解一下,为什么我们需要缓存。

image.png

在遥远的 4G 年代,网速感人,加载 JS、CSS 等静态资源文件,都需要花费较长时间,浏览器将长时间处于白屏状态,影响用户体验。

并且 JS、CSS 这些静态资源文件,其实在很长的一段时间内,内容都不会更新,如果每次页面刷新都去服务器请求内容,其实是没有必要的,而且也会增加资源服务器的访问压力。试想一下,如果每次加载页面都需要等待十几秒钟的时间,甚至更久,那该是多糟糕的事情。

image.png

所以,HTTP 缓存应运而生,当浏览器使用 HTTP 缓存后,若资源命中缓存,则会从本地缓存中拉取资源,而非从服务器拉取资源。

如何更新资源

好了,请求方面的优化已经达到变态级别,那问题来了:你都不让浏览器发资源请求了,这缓存咋更新?

image.png

这里有一个突破点,刚刚我们也提到了,HTTP 缓存是针对 URL 进行,因为 URL 是资源的唯一标识符。也就是说我们通过修改文件的 URL 访问路径,浏览器并会认为这是全新的资源,从而向服务器发起请求,从而获取最新的数据。

好了,我们现在尝试一次资源发布:

image.png

假设我们当前的线上版本是 1.0.0,更新资源时,将 HTML 文件里面关于静态资源的文件引用修改为 1.1.0,因为 URL 地址发生了变更,所以浏览器并会认为这是新资源,从而发起新的服务器资源请求,至此,我们的页面就得到了更新,更新为了 1.1.0 版本。

OK,问题解决了么?!当然没有!我们思考下这种情况:

image.png

页面引用了 3 个 CSS,而某次上线只改了其中的a.css,如果所有链接都更新版本,就会导致b.css,c.css的缓存也失效,那岂不是又有浪费了?!

我们不难发现,要解决这种问题,必须让 url 的修改与文件内容关联,也就是说,只有文件内容变化,才会导致相应 url 的变更,从而实现文件级别的精确缓存控制。

什么东西与文件内容相关呢?我们会很自然的联想到利用数据摘要要算法对文件内容进行求摘,将摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据了。 好了,我们把 url 改成带摘要信息的:

qq.png

这回再有文件修改,就只更新那个文件对应的 url 了,想到这里貌似很完美了,但实则不然,

现代互联网企业,为了进一步提升网站性能,会把静态资源和动态网页分集群部署,静态资源会被部署到 CDN 节点上,也就是说我们的 HTML 页面和静态资源需要分开部署。

那我们是先上线页面,还是先上线静态资源?

先部署页面,再部署资源:

image.png

我们优先部署 HTML 页面,紧接着部署静态资源,如JS、CSS,但是在二者部署的时间间隔内,可能就会有用户访问页面,此时仅更新了页面,资源未被更新,就会在新的页面结构中加载旧的资源。那在新的页面结构中加载旧资源会有什么问题呢 ?

假设我们这次上线的代码经过了重构,将原先的 a.jsb.jsc.js 的内容进行重构,那重构之前,必须按照 a、b、c 的顺序来加载,那重构之后,必须按照 c、b、a 的顺序来加载,因为后者依赖前者的代码,那现在我们更新了页面,没有更新代码。 其结果就是:用户访问到了一个白屏的页面,除非手动刷新,否则在资源缓存过期之前,页面会一直执行错误。

如果先部署资源,再部署页面:

image.png

我们在将前面那个情况搬过来讲,优先部署态资源,紧接着部署 HTML 页面,两者部署时间间隔之内,资源先被更新了,此时重构后的资源,加载顺序必须是 c、b、a, 但是由于 HTML 页面还是老页面,需要按照 a、b、c 的顺序来加载,那此时新用户访问页面时就会出现白屏。

但如果是老用户的话,是不受影响的(因为缓存仍然生效),HTML 中静态资源引用地址并未变更,所以即使我们更新了静态资源,老用户的浏览器仍然会使用当前缓存文件。

所以,归根结底:先部署谁都不行!本质的问题是页面和资源的新老程度不匹配导致的问题,

image.png

访问量不大的项目,可以让研发同学苦逼一把,等到半夜偷偷上线,先上静态资源,再部署页面,看起来问题少一些。但是,没有这样的绝对低峰期,只有相对低峰期,晚上冲浪的用户仍然会受到影响。为了稳定的服务,还得继续追求极致啊!

image.png

每次到了发布,工程师就要熬夜,身体也是越熬越差、头发也是越熬越少,下定决心,我们一定要解决这个办法。

覆盖式发布 VS 非覆盖式发布

这个奇葩问题,起源于资源的覆盖式发布,用待发布资源覆盖已发布资源,就会出现这种问题。解决它也好办,就是实现非覆盖式发布

覆盖式更新:覆盖式更新是一种通过在资源文件的 query 中添加摘要信息(如哈希值)来强制浏览器更新资源文件的方法。这种方式会导致每次更新时直接覆盖旧文件,因此浏览器会下载新的资源版本。这种方法虽然简单,但存在一些问题:

  • 风险高:每次更新都会直接覆盖旧版本文件,如果新版本存在问题,会导致整个应用无法正常运行。
  • 回滚困难:由于旧版本文件被覆盖,若需要回滚到旧版本,需要额外的备份和恢复操作。
  • 用户体验差:用户在更新期间可能会遇到资源加载失败或中断的情况,影响使用体验。

因此,覆盖式更新虽然可以强制升级,但在实际应用中并不推荐。

非覆盖式更新:非覆盖式更新通过将摘要信息(如哈希值)放到资源文件的发布路径中来实现。这种方式在每次更新时,旧文件和新文件都会共存,不会相互覆盖,从而实现平滑升级。具体优势包括:

  • 风险低:旧版本文件仍然存在,如果新版本有问题,可以快速回滚到旧版本,确保服务的稳定性。
  • 平滑升级:用户可以在不同时间段逐步获取新版本资源,不会因为版本更新而导致资源加载失败或中断。
  • 更好的用户体验:在后台更新文件,不影响用户的当前使用,确保应用的连续性和稳定性。

image.png

从上图中,我们可以明显看到两者的区别。在覆盖式发布场景下,即使进行多次发布,文件 a 始终只有一个版本,文件内容会被覆盖更新。

而在非覆盖式发布场景下,文件的摘要信息被直接打入文件名中,即使进行多次发布,也会保留多个版本,文件不会被覆盖,始终保持多个版本共存。

在这种情况下,先全量部署静态资源,再部署页面,可以确保资源的版本与页面代码配套,这样可以完美解决版本管理的问题。

image.png

在资源非覆盖式发布的场景下,回滚过程相对简单友好。我们可以将每次页面发布的最近一次 Git CommitId 作为文件夹的名称,并将对应的资源统一存入该文件夹中。

当需要回滚时,只需将 Nginx 的资源目录指向需要回滚的文件夹,即可快速、安全地完成回滚操作。

非覆盖式发布就真的完美了吗

image.png

  • 磁盘空间占用: 非覆盖式发布需要同时保留多个版本的资源文件,这会占用大量的磁盘空间,尤其是在资源更新频繁的情况下。这可能导致存储成本增加,并需要定期清理旧版本的资源文件以释放空间。

  • 安全和权限管理: 多版本资源的管理需要严格的权限控制,确保只有授权用户才能发布和回滚版本。同时,旧版本资源的保留可能会引发安全隐患,需要定期检查和清理过期资源。

  • 文件迁移的复杂性: 当我们的服务器发生变更或者我们不得不将当前文件迁移至其它服务器时,因为文件的数量众多,对于如何安全迁移也是一项非常大的挑战。

部署发布的发展过程

zz.png

最早期,我们使用 FTP 更新资源的方式进行发布应用。开发人员在本地完成开发和测试后,通过 FTP 客户端手动将 HTML、CSS、JavaScript 等资源文件上传到服务器,并覆盖旧版本。然而,这种方法缺乏版本控制,依赖手动操作,容易出现错误且存在安全风险,且整体的发布流程时间较长,

一包烟,一杯茶,一个bug能修一整天。但是,随着应用规模和复杂度的增加,这种手动发布方式逐渐被更先进的自动化工具和流程所取代。

z1.png

现在,我们常用 Jenkins + Docker 的组合来进行应用发布。Jenkins 负责持续集成和持续部署(CI/CD)、自动化地构建、测试和部署代码,而 Docker 则提供了容器化的环境,确保应用在不同环境中一致运行。通过这种现代化的发布方式,我们大大提高了发布的效率和可靠性,同时也增强了系统的可扩展性和安全性。 但是每次创建应用时,仍然需要找运维配置 Jenkins,还不够去中心化。

img_5.png

因此,更为先进的一站式平台化部署方案应运而生。平台提供成熟的部署方案,有效地将开发人员从繁琐的部署技术细节中解放出来,开发人员无需关心平台部署采用什么技术栈。

在这样的平台上,开发人员能够自主创建应用,并在完成创建后,专心投入于愉快的编码工作。通常互联网公司都会有自己的部署平台,如果公司内部没有部署平台的小伙伴,可以参考下 云效CODING 这类的平台化部署网站。

看到这里,今天的分享就已经进入尾声了,希望能给大家带来一些启发和收获。欢迎大家在评论区分享自己的看法和建议。

参考

本文作者:BARM

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!