1.5 Serverless与前端架构
那么,Serverless 和前端到底有什么关系呢?对前端架构有什么影响呢?在本节中,我们将以前端架构演进为主线,讨论 Serverless 对前端架构所产生的意义、影响,以及 Serverless 自身的价值。
前端架构从出现到现在的演化大致可以分为以下6个阶段,即静态内容展示、可交互页面、Web 2.0、单页面应用、前后端分离和基于 Serverless 的前后端分离。
1.静态内容展示
实际上,我们目前所使用的 HTML,最早是由欧洲核能研究组织(CERN)的蒂姆·伯纳斯-李(Timothy John Berners-Lee)所提出的。
在 1990 年底,他在互联网的基础上提出了万维网(WWW,World Wide Web)的概念,并介绍了万维网应该包含的三大核心功能,即我们现在所使用的 URI、HTML 和 HTTP。虽然在那个时候人们可以在互联网上浏览网页,但其实网页只能通过终端,以命令行的形式进行访问。这对用户来说操作极为不便,因此当时的 Web 站点也主要针对具有相关学术背景的专业人士。在随后的 1993 年,Mosaic 公司(1年后其更名为网景公司,即 Netscape)的第一个图形化Web浏览器 Mosaic(Firefox 的前身)发布。图形化浏览器的诞生,让普通用户也可以轻松地浏览网页。
这一时期的站点均以静态内容展示为主,其中主要是文字展示,图片使用得较少。同时,服务端一般使用 CGI 完成开发,前端只需要编写简单的 HTML 页面即可。此时的 Web 用户以大学的科研人员为主。
2.可交互页面
在 1995 年,刚加入网景公司(Netscape)不久的布兰登·艾克(Brendan Eich)就接受了一项互联网技术中影响最深远的任务之一,即实现一种可以嵌入网页运行的脚本语言。之所以要实现该能力,是因为网景公司发现,当用户填写一个网页表单之后,需要提交并回传整个表单内容,系统才能确认用户是否正确填写。这对当时的带宽来说是一笔不小的流量开销,可能用户需要等待十余秒才能看到验证结果,并且这一过程在一次成功的提交过程中,通常需要反复出现多次。所以,网景公司希望能通过客户端(前端)脚本,让用户在发送请求之前就在客户端中完成对表单内容的检查,以最终提高用户体验和使用效率。
布兰登·艾克在接到任务一个月后,就完成了这门语言的设计工作,并且仅仅花费 10 天时间,就实现了它。其接下来一年的工作,则是把该语言的运行环境集成到了网景(Netscape)浏览器。在这之后,网页便具备了动态运行脚本的能力,而这门语言,即 JavaScript。
同时,在服务端领域先后诞生了ASP、PHP、JSP 三大技术。与原来的 CGI 相比,JavaScript可以更加容易地编写动态网站,结合前端 JavaScript 脚本的能力,成为复杂客户端的 Web 应用。因此,交互方式开始逐渐被探索和挖掘。随着互动能力的增强,论坛(BBS)也开始流行起来。不过在接下来的几年中,Web 应用似乎走上了一条曲折的道路:前端能力的增强导致出现了各种各样的复杂特效,国内前端社区一度充斥着“JavaScript 文字滚动特效”之类的教程;而对真正为用户带来效率提升的交互研究并不多见。然而,更曲折的则是 IE 6 成为主流浏览器之后。由于 IE 6 与 W3C 标准相比,存在不少设计缺陷,加之IE自身缓慢的版本迭代,因此在当时这给前端研发工作带来了大量的额外成本。这一局面持续到了 2006 年的 IE 7 和2009 年的 IE 8 发布后,才稍有缓解。
3.Web 2.0
1998 年,Ajax(Asynchronous JavaScript and XML,异步的 JavaScript 与 XML)技术被提出。在这之前,服务端处理的每一个用户请求都需要在浏览器中重新刷新并加载网页,无论是用户体验还是使用效率都不理想。并且,即使只需要修改页面中的部分内容,也需要重新加载整个页面,这进一步地消耗了服务端的计算资源。而 Ajax 的提出解决了这些问题。它使得网页局部刷新成为可能。但是,Ajax 的真正大规模普及则是到了 2004 年。
2004 年底,Google 的邮箱服务 Gmail 进行了一次大型改版,通过大量使用 Ajax 技术,网页的可交互性和内容动态性跨跃了一个大台阶。自此之后,由 Gmail 所带来的交互理念几乎改变了整个行业网页制作的方式。除了 Gmail,一同大量使用 Ajax 技术的 Google 服务还包括 Google Map、Google Group 等。
2006 年 jQuery 诞生。jQuery 为操作浏览器的 DOM(Document Object Model,文档对象模型)提供了非常强大且易用的 API,同时抹平了不同浏览器之间的差异;因此,其成为使用最广泛的函数库。它的诞生让网页开发的难度大大降低,使得 JavaScript 真正地开始流行起来。在 jQuery 的鼎盛时期,全球有超过一半的站点均依赖 jQuery 库。
在通过 Ajax 提高用户体验和通过 jQuery 降低开发难度的共同促进下,Web 站点开始变得丰富起来,基于浏览器的Web应用也进入了以社交网络应用为代表的 Web 2.0 时代。同时,一些大型互联网产品公司开始分化出专职从事 Web 应用开发的工作岗位,即前端研发工程师。
于是,前后端研发的协作与分工开始了。这时,我们通常有两种研发协作模式。
一种模式是让前端研发人员根据设计师的视觉稿开发对应的静态站点,通常我们将该静态站点称为高仿真实现。然后,将这些静态页面交由服务端研发人员改写为动态页面(如 JSP)。我们将这种协作模式称为套模板。它的优势是前端无须了解任何后端实现,只需要交付仿真的静态页面即可;但缺点是其功能变更或扩展困难。当项目上线后,若有新的研发需求,由于原有页面代码已经与服务端代码融合,因此,服务端(即后端)将很难再根据前端研发提供新版静态页面,并将其替换到已有的代码中。故此,在迭代过程中如何进行同步,以及后期若发现 UI (User Interface,用户界面)上的问题,应该由谁负责,等等,这些问题都是无法避免的。
另一种模式则是让前端研发搭建后端运行环境。在这种协作模式中,由于前端可以直接运行最终的交付应用,因此解决了上述产品功能迭代困难的问题。但同样,它的问题在于将不必要的内容(即后端研发框架)暴露给了前端,前端研发人员不得不学习后端应用的研发、调试方式。若后端存在多个团队以及多种技术框架,则前端研发人员必须支付一笔不小的学习成本。
无论采用哪种模式,前后端(Frontend/Backend)的分工都是十分模糊的(见图1-3)。
图1-3 模糊的前后端协作模式
4.单页面应用
随着 Web 应用和前端技术的发展,SPA(Single Page Application,单页面应用)概念被提出。伴随这个概念的,还有数十个相关前端框架。其中,最早的是以 MVC(Model View Controller)模式为代表的框架 Backbone,以及基于 MVVM(Model View ViewModel)模式的 Knockout.js;随后 Google 在 2009 年推出了 Angular 框架,并且将其称为 MVW 模式(Model View Whatever)。
如图 1-4 所示,在该背景下,原来通过服务端渲染的页面开始向前端迁移。由于 Web 应用体验的大幅改善,页面逻辑以及路由也从服务端转移到了前端。这时,前后端协作的模式发生了变化。前端的所有逻辑都将打包成一个 JS Bundle,因此我们只需要后端提供一个入口文件(即 index.html),即可加载整个应用。这时后端只需要提供 Web API,即可完成数据的交换。当前端发布时,只需要在入口文件中,修改 JS Bundle 的版本号即可。前端负责整个 View 层开发,包括与 API 的对接。这样一来,一些业务逻辑变得既可以在服务端编写,也可以在前端编写。
图1-4 基于 MVC 的前后端协作模式
尽管如此,虽然研发环境相互已经没有了依赖,但前后端的职责仍然难以划分。
5.前后端分离
在 Angular 发布的同一年(2009 年),Node.js 也随之登台。Node.js 的出现带来的第一个好处是前端工程化的成熟,前端构建工具开始百花齐放。这时的前端已经不再是一个简单编写几行 JavaScript 即可完成的事情。专职前端研发人员开始在各个公司中普及,前后端协作问题也在这时进一步地加剧。
随着 Node.js 的成熟,在 2015 年,基于 BFF(Backend For Frontend,服务于前端的后端)的架构理念被提出。关于 BFF 的更多内容,后面会详细介绍。我们在这里只需要了解,BFF 架构通过在 UI 和服务端之间加入中间层,解决了前后端职责难以划分的问题即可。
如图 1-5 所示,由于前端逻辑的复杂性不断增加,增加了专门用于处理用户界面逻辑的服务层;同时后端逻辑也完成下沉,基于微服务架构的后端服务逐渐成型。通过基于 Node.js 的BFF 层,前后端形成了比较清晰的分工。
图1-5 基于 BFF 与微服务的前后端架构
6.基于 Serverless 的前后端分离
从上面的介绍中我们可以看出,由于增加了基于 Node.js 实现的 BFF 服务层,前端研发人员开始需要关注服务器的系统稳定性、可扩展性等指标,同时需要排查如内存溢出等各种异常问题。这些工作给前端研发人员带来了一定的挑战。可喜的是,随着 DevOps 配套工具的成熟,前端研发人员可以比较容易地实现日志监控、异常排查等一系列服务器运维操作了。
虽然各种云计算工具一定程度上解决了 Node.js 的运维问题,稳定性得到了保障;但在BFF 架构的真实实践中,除了稳定性,我们还遇到了其他棘手的问题:部署成本和服务器成本的增加。
随着业务的发展,由于每一个终端应用都会有对应的 BFF 层存在,因此会存在数十个 BFF应用。而这些 BFF 层需要独立的容器实例(即 Docker)进行部署,我们需要构建容器镜像(Docker Image)、编译应用程序代码、进行分批发布和灰度上线、观察线上流量等一系列流程,才能将服务正式发布到线上。由于我们的发布每周都会进行数次,而上面这一系列发布流程,每次都需要消耗数十分钟的时间,因此,仅仅是发布操作,就消耗了我们大量的研发时间。
出于服务稳定性的考虑,我们采用一个机房多个实例和多机房部署的策略。因此,我们的每个应用都至少应该部署4个实例(即至少2个机房,每个机房至少2个实例)。这将让容器实例的数量大幅增加。然而,BFF 层的流量实际上长期处于较低水平,这使得这些容器的平均CPU 利用率甚至不足 1%。也就是说,我们将浪费 99%的计算资源。
如图 1-6 所示,如果能够通过 Serverless 实现 BFF 架构,让前端研发人员仅编写和部署函数(这取代了原来容器实例的方式),则结合我们前面提到的 Serverless 特性,当没有请求时,基于云原生技术的弹性计算能力,这些函数的实例将自动缩容。这样一来,基于Serverless的BFF不仅降低了前端在服务器运维上的成本,还能够快速地完成函数的部署。更重要的是,通过按需计费的方式,Serverless彻底解决了计算资源浪费而最终造成的服务器费用问题。
图1-6 基于 Serverless 的 BFF 架构