前端微服务在字节跳动的打磨与应用

本文讨论了微前端在字节跳动的应用状况,内容主要分析了微前端具体落地的步骤和两年来的使用状况。其中分析的部分主要讲到一些实际问题和咱们的应对,落地状况强调了实现的过程。特别讲到不少在咱们观念里面务必要提供的微前端基石,这些方面做为基础设施几乎是使用微前端的必要和前提条件。html

传统前端业务一般会根据业务线集成在一个站点上,随着业务复杂度上升,包体积会迅速变的过大。为了适应这个变化每每须要更多的开发者、更细粒度的团队组织。分组开发时你们的模块解耦到各自完成,上线时糅合在一块儿运行,产生出层出不穷的分支合并、代码回滚,都会形成合做效率的骤降。这正是头条号平台在 17 年时面临的问题。前端

过大的代码集合还会形成发布频繁,每一个业务分支和功能点都有必定的更新频率,若是以传统的独石系统开发、验证和上线,每个业务都会让项目全部一块儿升级、测试和上线,发布频率的总和会很是高、很是频繁。若是不解除原有的耦合会完全失去响应能力。react

更进一步来看以如此之高的上线频率、版本迭代速度,开发者极难追溯哪一个版本对应哪一个改动。linux

字节跳动微服务前端解决方案为应对以上挑战而生。通过几年发展已经成功支持了几十个对内和对外的系统。webpack

问题背景

Monolithic 的问题

Monolith 独石就是一块石头的意思。正常翻译通常是“单体”:单体应用。这个在前端届概念不普及,用独石这个翻译更能体现他是什么意思。一整个建筑(或者什么其余东西)是一整块石头刻出来的。好比石狮子。这就是独石的应用。这样作事情在前端工程环境这个快速变化、快速迭代的领域有不少问题。git

上线慢

单体应用的一大问题是发布很是慢。字节跳动的典型业务状况是上一次线须要至少 30 分钟,前端的上线就须要这么长的时间。固然这是咱们在 17 年经历的状况,保持咱们的发展态势若是不升级技术,如今可能更慢。而后 17 年末咱们开始了大改版,开始拼命的拥抱微前端。程序员

本来回滚一次也是 10 分钟的。因此当时天天上线不了几回,风险也很大。逐渐致使变动都要憋着,成了“几天上线一次、一次多个变动”。github

我相信这也是绝大多数听众都会有的问题。尤为是那种传统的后台工程。没事 webpack 一下你懂的。web

上下线会不少吗?不少的,业务多了,有多少更新都要一块儿发布。docker

理解困难

固然本次是工程化的议题。更须要关注的影响更大的实际上是框架问题。你们都是几十个项目合做到一个工程里。工程化在搞什么呢其中很是重要一个点就是要“人能够理解”。更低的认知成本,能收获更低的犯错几率。

那这些项目非得维持彻底一致的组织模型就基本上是必须的。好比 model 是充血的仍是失血的?是 contorller 全都放一块儿、仍是根据 router 与视图们放一块儿?这些事很是鸡毛蒜皮的例子。实际上深层次的问题与之相似的很是多。

还有其它问题好比 debug 的时候到底能不能找到。也不是单体应用不行,单纯是说解决这个问题的时候投入了多少精力、多少设计,以及维持这个设计规范问题不崩坏,须要多少精力。

这一类都是单体应用自己的代码问题。“拆了就没这些事了。”

框架没法调整

真的从架构角度来讲,到底现在的前端项目须要怎么开发、通常是怎么干的呢。从上一段内容读过来,咱们知道大部分出色的架构师工程师都已经解决了好多那些困难了,方式是经过杰出的架构设计。

而后都知道前端的各类框架各类实践实际上很是多。前端工程师有个别名不知道大家听过吗,叫“npm install 工程师”哈哈,还有“github search 工程师”。

到底发生了极端困难的状况是来自框架仍是来自生产框架的方法呢,这个很差说。可是是个值得琢磨的问题。因此你看接手拿到项目什么的别说了你就学吧。反正现有架构确定是挺好的。就是你得学一阵、用对了才好。

微前端在字节跳动

这里开始讲咱们的细节,分别是服务发现、运行隔离、环境一致以及其余架构优点,其实这几件事都讲完就能发如今讲的主要意思是,具体是什么把一个很是特殊、对习惯改变比较大的方案变成可能的。实现这样一个不同凡响的方案不是你们聊一聊以为赞成而且开心就能作成。涉及到过程、成本、风险等方方面面。

“工程师”的任务不是说证实一个结构在理论上是能够存在的就完了,要有建造这个结构的过程。好比你拿化学键能够算出来任何可能存在的分子,画个小人均可以。可是到底怎么合成,按照什么路径能让这种分子被制造,哪一种路径最快最便宜,这个是过程的可能性。一般这才是工程师的任务。

服务发现

服务发现的方面咱们会首先讲一下在整个微服务模式里他做用是什么、有哪些方式。而后第二部分讲到底在解决什么问题,以及多说一些他能提供的新能力。咱们很重视新能力由于咱们的定位不是消防队,灭了火就完成任务,还有不少新目标、不少新好处能够探索实现。

最后是讲一下在字节跳动具体是怎么实现的。

1. 原理

“服务发现”就是原来的单体服务拆分以后,原本一个项目里的方法分开部署了,谁也找不到谁。须要有个统一的注册机构,把提供服务的各个部署都查到。

“发现”就是当你想访问一个微服务,你要怎么找到他。

这样就有两种构型,一个是以 Netflix OSS 为典型的,它在客户端的机器里先拿到一个服务目录,处理逻辑在客户端的代码里。另外一种是服务端的服务发现,AWS 就是如此。

传统微服务的服务发现更像是函数调用的替代,拆了以后怎么调到不一样容器里部署的函数。这里微前端思路很是相似,做用略有区别。微服务的状况是会区分像什么订阅、通知、请求、发布这些,前端极可能都不用或者表现上不在前端运行时使用。还有一些例如“对单”、“对多”这些基本就是前端不太用考虑的东西。

二者一致的地方是都谁也不认识谁了,如何知道哪些服务存在、谁在提供?下面就要讲一下各类构型的服务发现和背后的服务注册分别具体是什么状况。

客户端服务发现 是说客户端——也就是服务的调用者,去请求一个注册的目录,里面包含全部服务和负载均衡的基本信息这些,而后本身决定如何处理,使用哪一种具体的 load balance 策略。好比 Netflix 的 OSS,服务在 Netflix Eureka 注册一下,它心跳给各个客户端。客户端本身搞,简单直观。

服务端服务发现 是相似 AWS Elastic LoadBalancer 这种。客户端请求就完了,服务端决定怎么给你反向代理、负载均衡。

服务注册 分自注册和第三方注册。自注册不言而喻。第三方注册就是一个保活机制,按期检查服务状态,帮你去管控该上了仍是下了。

咱们主要用的是第一种:客户端服务发现,就是你要多请求一个模块列表。这个列表给出的资源是根据用户 session 决定的,有丰富的动态的能力。而后客户端再根据这个列表里的各类信息,去加载模块资源。

2. 给前端带来了什么?

用服务发现的方式去组织微前端,除了使复杂的上线流程变得解耦、快捷,还可使拆散以后的工程版本方便对齐,实现更高的稳定性和可调试性。还对前端工程带来好多其余好处。下面主要讲一下各类收益中的最重要的两个。

快速上线 是什么概念呢,前面说了几十个业务和在一个项目里,一块儿发布,这样发布的频率能有多高?实际考察一下放开限制后的情景,就会发现有超乎意料的高。咱们的一个微前端应用的业务是头条号,它在 2019 年上半年发了 2000 个版本。前面说了传统上线须要 30 分钟才能完成打包升级和容器的重启,而且 10 分钟才能完一个回滚,这就意味着 1000 小时的上下线等待时间。相比之下咱们新的方式点一下 HTTP 请求发出去就生效了,是一个毫秒级的反应速度。

这个搁之前就不是慢、须要干等着的问题了,直接你们就不这样去发了。都学 Native 发版那样火车式发布。结果是响应效率下降了不少,不少需求渐渐变得再也不由开发造成瓶颈,反而是总要等版本排发布。

独立切换 咱们如今就分别发,能够一个单页应用分几十个模块,各自上各自的、下各自的。并且后面会说到还能够各自配置本身的 AB 测试版:有 10 个模块就能够产生 1024 个 AB 版的组合,20 个模块 100 万个。跟之前彻底不敢想象——也就是说一块儿发版的时代根本作不了这个事。如今不敢想象的反而是,你说字节跳动某个业务里面不能作 AB 测。

咱们的头条号平台就是刚才一个典型的微前端项目,包含列出的这么多模块,各模块有独立的版本,和对服务版本的 session 控制。每一个模块进去都是版本列表,有一个模块全部的历史版本。经过这个平台配置小流量、AB、上线规则。

运行隔离

1. 耦合开发的严峻形势

17 年咱们推动项目的时候有一个很不错的帖子很流行,红遍朋友圈那种,讲 react-loadable 的。ta 从解耦的维度介绍了这个方向。咱们当时也有一个很明确的业务需求,要把公司不一样部门的人组织到一个项目里。而且这个项目通过经年累月的增肥,已经很是臃肿而且积攒了不少值得推敲的、非直接技术的工程细节。这就意味着要用不一样组织,不一样的技术,不一样的工程规范和打包工具,去合写同一个平台、同一个工程。若是当时用了 iframe 可能就是很是凑合的勉强知足业务,彻底不符合咱们追求极致的习惯。

而后当时咱们很在乎一点就是这种跨团队合做,想融合不一样的技术团队,实现少费力沟通或者不重沟通,运行隔离是个很是绝对的基本前提,咱们其余分享里面也用了不小的篇幅介绍,有对内的也有对外的。当时的效果是什么呢。这个是咱们 18 年 4 月内部培训录制到的当时状况:

咱们把线上的页面(左图)经过调试工具插入脚本,临时移除掉沙盒功能,获得的右图效果。

2. 运行隔离的目标

运行隔离是啥意思,回想一下刚才说的 AB 测的问题,20 个项目是多少个组合。若是把这个对应到 bug 的维度,你们都在一个应用里乱跑会有多恐怖。那么这样的组合对咱们的程序和程序员提出了什么样的要求?

不跑挂 说是“对一切工程师最基本要求”,我以为不算夸张。全部软件工程师的第一个能力层级都应该是不把系统拖垮。微服务以后这个问题不明显了,由于有架构层面的方式解决了绝大多数挑战。我很信服的一个理论是全部程序员都是四个阶段:写完需求,不拖垮别人,能扩容,性能好。

不干扰 也是另外一个大问题,咱们当时西瓜团队和头条是两个独立的 App,他们和咱们的合做彻底跨部门,连 polyfill 的规则都不同。事先也是作了不少公共组件、 CSS 约定之类的。可是规范和约定远远不够。协做的境界从最差到最好应该是:

  • 定规范:谁来了都好好学、好好听,本身对本身的行为负全责。
  • 能 enforce 规范:不凭自觉,而是用工具和流程等手段去发现和强制,实现可靠性。
  • 不须要规范:系统的肯定性由系统解决。靠人去发现和执行规范是消耗大量认知资源的,带来的都是额外的工做量和系统的不肯定性。

3. 沙盒

咱们还有另一篇文章专门介绍沙盒的设计和采坑经验。这篇就快速用几张图示意一下。

① 变量保护: 全局变量、 DOM 和 CSS 基本都是走的这条路。先后两次快照,咱们来比较,以后根据须要帮你恢复现场。这块内容不细说了,看一眼图就不言而喻:一次比较对照全部 key、两次遍历、黑名单 location、白名单 readonly。估计我这样一说你们都懂。

② 沙盒时序: 稍微多说一些。右图是咱们作的 ABCDE 五个模块的加载和混行的时序图。虚线左边是加载,右边是独占线程所占用的时间。也就是说有 ABCDE 五个模块五个沙盒,分别在这个模块编译(下载、建立 js 变量和函数、运行这些语句、最终生成一个 React Component)和运行时(这个模块被打开、渲染对应的全部功能)。

这里面两个基础:js 单线程、事件循环

咱们用了很是单纯的单进程操做系统的思路,比喻一下就是 js 的单线程就像单核 CPU 同样。你激活一个模块,至关于激活一个线程,其余都退到背景里。

实际上单核单进程不是必然,你们都知道这个原理。在事件循环的基础上,咱们能够封装全部的异步操做,把回调套在沙盒激活后面。好比 setTimeoutaddEventListener,这样每一个模块看起来就像是在并行。这块能够说的不少,可是就想一下操做系统的比喻就行了。

4. 加载方式

React 的项目用 react-loadable 自己很少说了,VUEraw (也就是不包含展现层框架的原始版)的各类项目,咱们都提供 masterpage 的样例,每一个版本对应的都实现了一套和 react-loadable 类似的效果。

子模块(Modules) 就是一个个的 CMD 包,我用 new Function 来包起来。其余就是具体主工程(MasterPage)项目框架的约定,load 过程分为 5 个钩子:

  • preload 是否预加载,是个 promisefullfill 的时候就会触发 Ajax。各类空闲政策阻塞政策均可以由 master 制定;
  • loadCondition 编译前置条件,fullfill 了才会开始运行这部分,执行结果就是获得那个 CMD 的 exports
  • provider 是一个模块的入口的函数,由模块开发者提供,返回模块的一切输出。这个函数的传入参数由 masterpage 主工程来提供。
  • loaded 完成加载,获得编译结果了。
  • 等等后面不说太细了。

环境一致

由于咱们以前都是在讲微服务是什么和落地效果如何,历来没有讲过 推行一个微服务你得作什么。如今这部份内容是咱们第一次公开分享的,也是一个很独立的维度。

其实就是在讲为何对微前端来讲这个环境一致工具是必须的,是绕不过的必经之路。若是不搞也很容易就栽进坑里,项目失败。而后极可能还不知道是为什么失败的,把问题归结为框架很差啊、人很差啊甚至微前端就很差啊之类的问题上。

1. Serverless vs container

container 就是一个寄生环境,尽管这个环境仍是挺特殊的,不像 linux 这种完整操做系统。相比之下 Serverless 就特殊多了。特殊到连谷歌云都曾经在商业上被击败。

这里举两个 Serverless 的例子,好比 lambda。它的本地工具是一个 CLI 系统:SAM,是一个很是典型的必要基础设施。若是说发展容器化 AWS 全是靠 docker,发展 lambda 就是靠的 SAM。

另外一个典型的例子 firebase。想必前端的同窗们都很是清楚,也都用过他们的开发套件。这些工具都很是重视一点就是本地开发,我作个项目到底能不能先测试再上。或者说先调试在上。

要作的话就是尽量模拟真实环境了,SAM 的话就模拟了 API Gateway,memory limit 这些。有 live debugging、 local debugging。否则的话发什么疯有人敢把线上业务放到一个很是不一样的环境下运行。

2. 你是否是环境有问题(在我这是好的)

标题程序员最常说的一句话对不对,另外一种表达是“我这是好的”。你们都知道绝大多数状况这么说话不对,但经常会忍不住说。甚至更像是实际上是对本身说。一种扪心自问,自我拷问,“我这是好的啊”。

咱们用沙盒把微前端作成了像 container,像浏览器里的 docker。可是不够,咱们仍是把寄生在 masterpage 内这种应用框架的特征,也就是业务具体逻辑,当作是一种 Serverless。

而后咱们还把隔离的思路作到极致,咱们的 dev 命令是经过启动参数启动一个彻底独立的 Chrome 会话,有本身的 cookie 啊缓存啊这些,效果像是装了 2 个 Chrome 乃至多个 Chrome。而后代理工具默认也配到了启动参数,是个 pac 文件。因此也能够单独用或者装 switchy 用。

代理工具 就是调试环境的整个配置,那些走测试环境、哪些走线上请求所有代理管理。生成一个动态的 pac 地址和代理服务。就是刚才说的。

关键请求,好比服务发现的请求,显然是代理掉的。走一个咱们为本地环境定制的返回值。更细节的功能是咱们能够协助调试主工程(MasterPage)、 组合上某个 module,你也能够用指定的 MasterPage 版原本调用你正在编写的模块。

你也能够指定是否加载完整的线上模块列表、只替换你正在调试的模块。

咱们也还有完整的植入 webpack dev server 的服务供选择。前面说了支持任意打包工具,这块是解耦的,只不过你用了咱们能够帮你 reload,部分刷新动态刷新。后面再细说。

发布检查 是针对服务注册这一块。这块的一部分,咱们的 build 命令有一套检查,对应 git 钩子。

方便调试 咱们还在必定程度上支持了 HMR。咱们能够像开发一个普通前端应用同样开发主工程(MasterPage)和子模块(Module),子模块更新后改变模块管理器状态,并由内置的 eventbus 机制来从新渲染 HMR,这个机制也能够用到盛传环境。

咱们的公共库能够经过在 MasterPage 项目里引入、子模块里 external 的方式实现模块间共享。 也支持子模块使用特定版本的基础库。

Vue 用到了全局变量及原型链扩展,暂时还不支持 Hot Reload 的调试。

其余的框架优点

框架上看就是 serverless 的方向。不是真的 serverless 是前端 serverless,业务 module 开发者不少东西都不用再关心了。举个例子就是 console.log。如今你们都知道线上业务要干干净净体体面面,把 console 都收拾整齐。这是咱们以前提到的规范的层面,咱们能够作到吸取全部 console,存储错误堆栈。而后用户反馈的时候做为 trace 元数据提交到反馈后台等等。

这些都是 masterpage 层面的框架了。固然不是必然关系。可是能够说微前端给了一个很是方便像这样组织项目的渠道。

咱们线上的 sourcemap 也是根据服务发现的管理后台权限控制的,只有开发者能看。

下一代前端展望

前面讲了,服务发现是一个对前端可用资源的整体管理。这个能力是不局限于运行时的微服务前端的。对一切资源都适用,下面说一下这块。

服务发现 + CDN

抽象一个完整的前端访问,首先拆成 3 步:A 页面加载,B“服务发现”,C 根据服务发现结果加载资源。那就有不一样的变种。最直观的就是 AB 结合,SSR 画上去,把 html 请求下来,module list 资源列表已经全了。这个系统咱们这代号 GOOFY。固然也能够 ABC 都组装进去。这个后面细说。

另一个思路就是 BC 结合,我请求一个列表,不用说我能够把 js 内容都 combo 进去。少一些额外的请求。

总之大意就是这个 ABC。

Token 解析

中心服务从中心机房把规则心跳给边缘节点,边缘节点接收客户请求,就近解析出基本的 token,这个过程当中不依赖其余服务。

这个 token 由一样在边缘的页面服务提供。由于脱离了到中心机房验证的步骤因此 token 时效有必定依赖前端 SDK。

高可用

高可用能够说是边缘计算的一个极大的好处,额外给了咱们一个收益。这套系统的容灾基本等同于智能 DNS 对应的探针保活这一套成熟技术了。

咱们须要的就是把边缘节点心跳到一个监控服务上,他们会分钟级动态修改 DNS。若是没有足够的边缘节点生存,还能够 DNS 到传统的中心机房。

这样绝大多数流量都不须要进出中心机房,资源都是就近的、多播的。

结尾

以上就是本次分享的所有内容,咱们从落地的细节分享了字节跳动两年来使用微前端的经验,以及面对这些挑战时的思考过程。很是幸运咱们的项目有足够多给力的伙伴们支持,最终得到了比较大的成功,也很是明显地提高了重量级的产品的质量。

微前端和不少前沿和刚刚发展的概念同样,自己还在快速的演进和验证的过程当中,咱们的具体实践也一直在快速的变化,在不断地发现弱点和纠正它们,也在努力发展更多的可能。在这个从种种不完美到更完美的奋斗过程当中,能给读者分享咱们的成果是咱们的一种荣幸。并且在分享后,若是能收到指教、讨论和建议咱们会更加感激,而且很是欢迎。也欢迎更多的有识之士加入咱们,具体可参见 内推连接

相关文章
相关标签/搜索