浅谈软件架构:微服务和微前端

ChlorineC Lv4

之前在另一家公司实习的时候就被微服务架构等一系列词汇搞得晕乎乎的,好在最后也没涉及这方面的事情,也就不了了之了。

最近又刷到了这方面的东西,正好现在公司里有一些相关的事情(在架构上),也正好做一个学习了解,捋一捋微服务(micro-service)微前端(micro-frontend) 还有一个更摸不着头脑的 “无服务”(serverless) 是什么东西。

当然,因为最近做的一直是前端开发这方面,我的重心自然会放在微前端上,但由于这是个历史发展的概念,只有理解了微服务的发展历史才能更好地理解为什么需要、如何利用微前端架构进行开发。因此我不会细究微服务的部署,但会尝试微前端的部署过程与实例

微服务

软件架构分为许多类别,如单体架构、微服务架构、分布式架构等,它们虽有出现的先后顺序之别,但严格意义上并没有先进与否的区别,只是适用于不同的应用场景项目规模

比如你一个小的不能再小的应用,非要上分布式或者微服务,那可能就是脱了裤子放屁,可能RPC开销、管理成本、MapReduce开销等等都会超过你任务本身的开销,因此还是应该按需选择最合适的方案。

要理解微前端本身,我们首先要从软件架构的发展讲起,理解为什么需要微前端架构。

单体架构的发展

单体架构

单体架构(monolithic software)是一种将所有功能和逻辑写在一个项目(或部署在一个项目/容器)中的写法,一个实例中集成了一个系统的所有功能,并通过负载均衡软件/设备实现多实例调用。

img

如我们熟知的MVC、MVVM架构严格意义上都属于单体架构,它们虽然在业务逻辑(如视图层、View Model和逻辑层)上做了区分,但本质上仍是单体应用。

区别一个单体应用最好用的方法就是:你对其中某一个功能进行改动,会不会需要重新编译整个项目?需不需要担心耦合造成的蝴蝶效应? 如果会,那就是单体应用。

单体架构发展的后期

最开始的单体架构是所有功能都耦合在一起,甚至前后端不分离,但随着项目体量的不断扩大,单体模式的弊端逐渐显露出来,如:扩展性差、无法实现复杂业务、技术升级困难等。

为了解决上述问题,人们给单体架构打了一些“补丁”,对代码进行拆分来提高维护性:

  • 分层开发:如MVC、MVVM等设计模式(是的,算设计模式而不是软件架构),将视图层和逻辑层代码分离开来,但还是放在一个项目中
  • 业务拆分:按照业务对模块进行拆分,但模块之间如何相联就成了下一个问题(如果分开开发,一起部署就仍是单体架构;如果分开部署到Web上,则成了后文提到的SOA架构

img

面向服务架构

面向服务架构(SOA,Service-Oriented Architecture)伴随着互联网的兴起而兴起,将功能单元拆分出来放在互联网上来提供“服务”,在某些地方也被称为“分布式架构”,由于出现的年代也比较久远,一般被看作一种“中型架构”,作为“单体架构的并行拓展”。

具体地说,面向服务架构或者说分布式架构,将一个大的系统划分为多个业务模块,业务模块分别部署在不同的服务器上,各个业务模块之间通过接口进行数据交互。数据库也大量采用分布式数据库。通过LVS/Nginx代理应用,将用户请求均衡的负载到不同的服务器上。

所谓服务(service),就是在后台不间断运行、提供某种功能的一个程序。最常见的服务就是 Web 服务,通过80端口向外界提供网页访问,所以我们常见的C/S架构其实也算一种面向服务架构(后端向前端提供某种“服务”)。

img

采用SOA架构有以下优点:

  • 每种服务功能单一,相当于一个小型软件,便于开发和测试;
  • 各个服务独立运行,可以单独开发和部署,可以使用不同的语言和工具开发;
  • 鼓励和支持代码重用,同一个服务可以用于多种目的;
  • 扩展性好,可以容易地加机器、加功能,承受高负载;
  • 面向服务架构默认运行在不同服务器上,每台服务器提供一种服务,多台服务器共同组成一个完整的网络应用,即使一个服务失败了,不会影响到其他服务。

通信协议:SOAP和REST

既然服务是运行在Web上的,那么如何制定通信标准就是一个棘手且紧急的问题。

早期的SOA多以基于SOAP (Simple Object Access Protocol)协议的Webservice实现,因其通过WSDL (Web Service Description Language)实现了严格服务间通讯格式约束,所以它非常适用于对稳定性要求高、不易变、防御式的场景, 这种方式在当下的银行、电信系统中还有很大市场。但SOAP的优势也为其带来了:笨重、不灵活、修改困难、通讯效率低等问题。

我个人是搞前端的,入行的时候也已经REST大行其道了,因此这里也主推RESTful API规范。

REST(Representational State Transfer, 代表性的状态迁移)是Roy Thomas Fielding博士于2000年在他的博士论文中提出来的一种互联网软件架构风格, 目的是便于不同软件/程序在网络中互相传递信息。 使用上看似简单,但背后有着深厚的理论支持。 详见此处

HTTP是REST的一个主要表现协议,但REST本身是协议无关的。在实际开发中,我们可近似地认为REST就是用于HTTP的,使用HTTP的请求头操作字段,如GET/POST/MODIFY等。

REST的核心特征是面向资源(Resource Oriented)、可寻址(Addressability)、连通性(Connectedness)、无状态(Statelessness)、统一接口(Uniform Interface)、超文本驱动(Hypertext Driven)

RESTful API 将所有服务抽象为资源,资源不仅代表服务器中的文件、数据库中的表等具体的东西,它可以是任何对象。 通过URL定位资源,使用 HTTP 方法(HTTP Method)确定对资源执行什么操作,通俗地说(严格而言有一定问题,见下文说明)所有操作都可以抽象为CRUD(Create-创建,Retrieve-查询,Update-更新,Delete-删除),可分别对应于HTTP的 POST、GET、PUT、DELETE。 由于使用标准的HTTP操作、没有Schema约束,REST成为系统间轻量交互的首选方式。

虽然REST的大道理很多,背后有很多理论支持,但我们使用它只需要遵循简单的规范,并不需要去证明高深的理论,就可以构造出简明易懂且灵活的API,这也是REST的魅力所在。

微服务架构

说了这么久终于说到主角了啊,介绍了SOA架构后理应就可以满足大部分需求了,为什么还要引入微服务架构呢?

SOA架构有以下缺点,在项目规模被进一步放大后其劣势将被凸显出来:

  • 传统SOA拆分的粒度较大 一般按业务域划分系统,但很少涉及系统内细粒度地拆分。传统SOA只解决了业务层面的划分和系统之间的交互,但没能从架构层面解决系统内的复杂度、效率、安全等单体架构所存在的问题
  • 传统SOA多需要集中的服务总线,容易产生性能瓶颈 ESB(Enterprise service bus)几乎是传统SOA必备的,它集通信交互、服务编排、认证授权、质量监控等功能于一身,可十分方便地管理各个零散的系统。而一旦涉及总线,那性能瓶颈就一定会受木桶效应的影响,而这个短板在爆发式高并发场景下一般都是总线本身(学过组原的应该都有所体会)。

因此,随着Docker等容器技术的兴起,微服务(micro-service)作为SOA架构的进一步延申出现了。

对SOA架构系统内部的功能再进一步进行抽象和解耦,我们就可以得到一条条独立运行的微服务

我们以一个网上商城系统为例,在SOA架构下如下图所示,每个前端都对应了一系列的服务,按业务群进行划分:

1

可以直观地看出,不同系统之间有较多的重复和耦合部分,增加需求和更改现有功能都比较困难,统一的数据库也容易产生性能瓶颈。

这时我们就可以对其进行进一步地抽象,降低系统内部地复杂度,如独立出用户功能、促销活动、商品查询等一系列微服务,彼此独立运行、维护和开发,每个服务也有自己的数据库,并交给一个或多个负载均衡器进行负载分配,这样系统的可维护性和稳定性就大幅增加了。

img

简单地说:微服务在开发方面,就是对系统功能的进一步解耦,注重不同服务之间的独立性和隔离性,提高系统整体和功能的可维护性;在部署方面,基于容器技术,使部署更灵活和便利。

SOA与微服务的区别

虽说微服务在发展上是SOA的进一步细化,但二者在设计思路上还是有不少的区别。

  • SOA喜欢重用,微服务喜欢重写:由于ESB(统一总线)的存在,SOA总是倾向于重用已有的服务;但微服务的应用场景却更多的是修改或重写某个独立的功能而不改动整个系统
  • SOA喜欢水平服务,微服务喜欢垂直服务SOA设计喜欢给服务分层(如Service Layers模式),形成一个金字塔式的层级调用模式;微服务通常是直接面对用户的,每个微服务通常直接为用户提供某个功能。
  • SOA喜欢自上而下(顶层设计优先),微服务喜欢自下而上(底层隔离优先)SOA架构在设计开始时会先定义好服务合同(service contract)。 它喜欢集中管理所有的服务,包括集中管理业务逻辑,数据,流程,schema,等等;微服务则敏捷得多。只要用户用得到,就先把这个服务挖出来。然后针对性的,快速确认业务需求,快速开发迭代

微服务架构的缺点

没有银弹! ——The Mythical Man-Month By Fred Brooks

一个老生常谈的问题,任何架构或方案的选择并不存在绝对的优劣,而是针对实际开发场景的trade-off。

最显然的问题就是,当应用规模并不需要微服务这样细粒度的服务划分时,对系统的细分和分布式系统的开销就会超过系统本身,成为累赘。

除此之外,微服务的分布式系统包括分布式数据库、负载均衡还有系统之间复杂的依赖网络等特征显著提高了系统的复杂度,最明显的地方就是每年arXiv都有无数和微服务指标诊断和预测还有异常处理的论文产出,个人也在这方面做过一些研究(如AutoMap)。

举个例子,一个庞大的系统内微服务之间的依赖通常是极为复杂的,不仅有直接依赖,还有影响更深远但难以察觉的间接依赖,这就容易导致雪崩效应(一个服务崩溃的蝴蝶效应导致整个系统崩溃)。更糟的是,由于复杂的依赖关系,你无法快速地定位到错误产生的源头,导致服务维护时间被延长,产生巨大损失(这也是为什么这个领域频繁产出论文的原因)。

比如说,你的订单系统崩了,你从订单系统出发,一路检查了商品数据库、负载均衡系统等等,始终没有头绪,最终发现是你的订单服务在读取名称时由于XX客户正在修改用户名触发了写保护,而某个服务恰好没有做一致性回退,直接Crash掉了,导致下一个调用这个服务的服务成了幸运儿,一传十十传百,最后整个服务都崩了。(虽然这种东西不太可能发生啦)

Serverless 架构

当我们还在容器的浪潮中前行时,已经有一些革命先驱悄然布局另外一个云计算战场:Serverless架构

不得不说,serverless真的是一个非常容易引起误会的词,第一次遇到这个词还是在论文里,当时以为是一种触发无服务的异常,后来才知道这是一种服务提供的类型。

Serverless(无服务器计算,或者说no-server)是一种新型云服务提供方式,用户不需要关心基础设施和如何部署,只需要关心服务本身。

个人感觉serverless更像是一种商业概念,它是云服务的一种新型计费方式,也就是说不再按固定的服务器资源计费,而是基于微服务架构更灵活地按量计费。

云计算通常有基础设施即服务(IaaS),平台即服务(PaaS)和软件即服务(SaaS) 等计费模式:

  • IaaS和PaaS时期:云计算主要以虚拟机的形式提供纯粹的硬件资源,并且通过统一化开发平台(PaaS),提供更丰富的计算和开发体验服务。云计算商业模式之所以能够成立,是因为其一直在满足一个基本假设:『如果把云用作出租的计算基础设施,那么云的弹性部署和集中维护,可以让云服务成本低于拥有成本
    • 把虚拟机做作为弹性部署资源的单位其实并不合适,即使运维成本已经通过屏蔽硬件细节,让在整体方案中被降到最低,但是它依然是一笔非常庞大的支出,因为开发人员往往需要过度配置来提高冗余
  • SaaS时期:在这种基础上,云计算用户不需要考虑更多支撑硬件,不需要考虑很多服务器平台,也不需要考虑部署环境是否一致,暴露在外的只是提供的硬件服务。这类平台有非常多而且非常成功的实现方案。但是同样,虽然云计算减轻了物理计算设施的负担,却留给云计算用户越来越多的虚拟资源需要管理,甚至要考虑更新型的架构设计。
  • FaaS和BaaS时期:在微服务和容器化的趋势下,实现了只把代码放在云计算平台上,在被调用时让代码本身按需扩展的服务模式的可能性

因此,「无服务器计算」并不是严格意义上的「无服务器」,因为不论如何抽象或者封装底层设备,程序总是要有一个服务器作为物理载体才能运行。与服务器相对,「无服务器计算」这个概念其实更加强调计算资源自由扩展,无需人工手动配置,相当于云计算用户把设备资源的缩放管理放置在云端,从而可以很方便地维护或者扩展平台。

这也是我把serverless归入微服务这一栏的原因,因为serverless的兴起很大程度上就是和微服务架构的兴起是一致的,它也仅仅是云服务提供商在部署层基于微服务架构提出的概念而已。

微前端

回顾微服务的历史,我们发现微服务的出现是因为整个服务系统过于厚重,而对其进行进一步的拆分和抽象为更小的服务(微服务),可以独立维护、部署,满足敏捷开发需求

然而,越来越重的前端工程也面临同样的问题,自然地想到了将微服务思想应用(照搬)到前端,于是有了「微前端(micro-frontends)」的概念:将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品。当然,具有了微服务的优点,自然也有微服务带来的复杂性和缺点。

微前端架构的出现主要可以满足以下需求:

  • 代码库更小,更内聚、可维护性更高
  • 松耦合、自治的团队可扩展性更好
  • 渐进地升级、更新甚至重写部分前端功能成为了可能(主要需求)
    • 增量升级:解决了没法大刀阔斧地腾出时间对整个系统进行重构的问题,可以只对某个部分进行重构,部分地解决了尾大不掉的问题
    • 独立部署:大型前端项目的部署时间是很长的(此处痛骂webpack一小时),且不同项目之间可能会产生意想不到的影响;微前端架构下每个微前端都应具备有自己的持续交付流水线(包括构建、测试并部署到生产环境),并且要能独立部署,不必过多考虑其它代码库和交付流水线的当前状态
    • 解锁了不同技术栈并存的可能性

实现方案

微前端架构中一般会有个容器应用(container application) 将各子应用集成起来,职责如下:

  • 渲染公共的页面元素,比如 header、footer
  • 解决横切关注点(cross-cutting concerns),如身份验证和导航
  • 将各个微前端整合到一个页面上,并控制微前端的渲染区域和时机

集成方式分为 3 类:

  • 服务端集成:如 SSR 拼装模板
  • 构建时集成:如 Code Splitting
  • 运行时集成:如通过 iframe、JS、Web Components 等方式

服务端集成

服务端集成的关键在于如何保证各部分模板(各个微前端)能够独立发布,必要的话,甚至可以在服务端也建立一套与前端相对应的结构:

img

每个子服务负责渲染并服务于对应的微前端,主服务向各个子服务发起请求

构建时集成

常见的构建时集成方式是将子应用发布成独立的 npm 包,共同作为主应用的依赖项,构建生成一个供部署的 JS Bundle

然而,构建时集成最大的问题是会在发布阶段造成耦合,任何一个子应用有变更,都要整个重新编译,意味着对于产品局部的小改动也要发布一个新版本,因此,不推荐这种方式

运行时集成

将集成时机从构建时推迟到运行时,就能避免发布阶段的耦合。常见的运行时集成方式有:

  • iframe
  • JS:比如前端路由
  • Web Components

虽然直觉上用 iframe 好像不太好(性能、通信成本等),但在这里确实是个合理选项,因为 iframe 无疑是最简单的方式,还天然支持样式隔离以及全局变量隔离

但这种原生的隔离性,意味着很难把应用的各个部分联系到一起,路由控制、历史栈管理、深度链接(deep-linking)、响应式布局等都变得异常复杂,因而限制了 iframe 方案的灵活性

另一种最常见的方式是前端路由,每个子应用暴露出渲染函数,主应用在启动时加载各个子应用的独立 Bundle,之后根据路由规则渲染相应的子应用。目前看来,是最灵活的方式

还有一种类似的方式是Web Components ,将每个子应用封装成自定义 HTML 元素(而不是前端路由方案中的渲染函数),以获得Shadow DOM 带来的样式隔离等好处。

你是否真的需要微前端

这个问题其实是微服务中常问的你真的需要微服务吗 ?,只是作为一个前端我把这部分放在了微前端的部分。

设计系统的架构受制于产生这些设计的组织的沟通结构。 — M.Conway

康威定律 几乎就是微前端(准确来说是微服务架构)的理论基础了。它指出了组织架构越庞大,其系统间沟通成本越高的问题。而解决这一问题的有效手段就是,将大的系统拆分成一个个微小的,可以独立自治的子系统。一旦系统的依赖限制在了内部,功能上更加内聚,对外部的依赖变少,那么就能显著的减少跨系统之间的沟通成本了。

微前端(微服务)的假设是,所有大型系统都逃不过熵增定律,所有大型系统都将从有序变为无序,他们背后的 codebase 的归宿都将是「屎山」

基于此,微前端很多时候是「悲观主义工程师」在工程上的妥协,是一种防御性,有时候甚至是「掩耳盗铃」式的架构策略。当然,微前端倡导的不是消极的、投降主义的去回避系统中的历史遗留问题,而是告诉我们,很多时候我们可以通过分而治之的手段,让「上帝的归上帝,凯撒的归凯撒」。

基于以上两个观点,我们可以概括出,存在以下场景时,你可能就不需要微前端:

  1. 你/你的团队 具备系统内所有架构组件的话语权;简单来说就是,系统里的所有组件都是由一个小的团队开发的。
  2. 你/你的团队 有足够动力去治理、改造这个系统中的所有组件,直接改造存量系统的收益大于新老系统混杂带来的问题。
  3. 系统及组织架构上,各部件之间本身就是强耦合、自洽、不可分离的,系统本身就是一个最小单元的「架构量子」,拆分的成本高于治理的成本。
  4. 极高的产品体验要求,对任何产品交互上的不一致零容忍,不允许交互上不一致的情况出现,这基本上从产品上否决了渐进式升级的技术策略

微前端框架实践

现在流行的微前端框架为micro-app, qiankunsingle-spa,我将采取在现有Vue3 SPA中嵌入React 18的页面和组件的方式来测试这些微前端的架构、性能和使用体验。

img

这部分将在公司项目实践后完成

参考资料

  • 标题: 浅谈软件架构:微服务和微前端
  • 作者: ChlorineC
  • 创建于 : 2023-05-25 11:50:14
  • 更新于 : 2024-06-04 03:50:36
  • 链接: https://chlorinec.top/2023/05/25/Development/microservice-hands-on/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论