导读:Gundam 是快手主站前端工程化脚手架,目标在于建设统一标准的工程化解决方案,提升研发效率,保障稳定性底线。本文会结合Gundam在新春除夕项目中的落地实践来聊一聊在工程化领域中碰到的问题和思考,以及未来的发展方向。
一、背景与目标
1.1 背景
Gundam 作为快手主站前端的脚手架,成立于2022年底。当时主站前端的整体工程化建设,从开发准备阶段到开发、联调、测试、部署、运维整个全流程, 其中偏后链路的持续集成和持续部署、错误监控排障,依赖于公司的天琴(服务变更工具)静态部署和雷达系统,云构建、部署、运维的能力建设都已经初步具备。但同时存在着以下几个问题:
基建能力没有场景特化:基建工具为了适应公司内部不同的部门、业务场景,其能力是开放的, 并没有一个结合主站业务场景做的标准化方案,研发同学在使用基建能力时的效率必然会大打折扣。
工程能力与业务强耦合:同时,在前链路的开发阶段以及更前置的开发准备阶段,并没有一个统一的工程化方案。这一阶段涉及到了工程代码领域,与业务场景耦合性更强, 而当时主站前端的业务场景繁多、差异较大,业务场景落到前端领域的工程化能力难以对齐。
缺乏统一标准:主站前端支撑的业务范围比较广,涵盖了直播、生产、消费、开放平台等多个独立的业务。当时的前端团队中,前端框架的选型没有统一的规范,有些直接继承历史方案选型,有些选择用社区当前流行的框架,脚手架、构建工具都没有一个统一的规范。在此基础上想要沉淀总结团队的工程优秀实践并且在全团队流通赋能是非常困难的。
1.2 目标
因此,为了建设主站前端统一的工程化解决方案, 我们成立了Gundam 项目。目标在于建设统一标准的工程化解决方案,提升研发效率,保障稳定性底线。
为什么能提升研发效率?
统一的工程化方案能提供标准工程解决方案、团队沉淀的优秀实践,将工程的复杂度由业务层面转移到基建工具层面, 理想情况下,研发同学只关注业务逻辑的开发,省去了调整适配项目工程能力的时间,整体的研发效率自然就提升了。
为什么能提升研发质量?
统一的工程化方案提出了当前组织结构和技术架构下的工程规范,同时借助脚手架和基建工具保障了规范的落地,能在发布前置环节,以自动化的方式在检测并发现项目中存在的质量风险,减少了线上问题的概率。
在24新春除夕项目的活动中,Gundam 作为预热、除夕、留存多个会场的前端脚手架选型,提供整个研发流程的工程化支持,覆盖了开发前资源准备、开发、联调、测试到部署上线,提供了诸多工程领域的能力, 例如:
稳定性领域: CDN容灾、IDC az调度、sig3加签
性能领域:接口预请求、fmp自动上报、图片压缩、字体裁剪
效率领域: CI/CD 流水线标准化
也收到不少新春除夕项目开发同学的好评。
除了作为工程能力的提供方,Gundam 另一个重要的价值是作为技术标准化落地的关键工具,接下来我会从标准化这个方向切入,结合Gundam 在本次新春除夕项目的实践经验来聊一聊在工程化领域中Gundam选择的中庸之道和相关实践。让大家对Gundam的设计理念和部分底层原理有更清晰的认识,也能让大家在实际开发中更好的使用Gundam提供的能力。
二、关于工程化的思考
2.1 标准化:统一与隔离
统一与隔离,从稳定性角度来选择,哪一个方向应该是做稳定性选型时更倾向的?
有经验的朋友一下就能回答上,当然是隔离。在高可用的架构设计中,服务隔离是一个重要的保障措施。
隔离的本质在于让不同系统间不会相互发生干扰,减少资源竞争,在系统发生故障时,也能限定故障的影响范围,避免雪崩。
众所周知,信息差跟范围是正相关的,涉及范围越大,可能存在的信息盲区就越多,跨组织之间甚至存在了信息壁垒, 信息盲区会导致方案不全面,会引入更多的稳定性风险。因此合理的隔离是能减少信息差,将变更的控制范围尽量缩小,从而减小稳定性风险的一种有效手段。
那么,能不能遇事不决就隔离?
所有事情都按隔离原则来就好了吗,当然是不行的,隔离有什么样的缺点呢?
最典型的问题就在于:更高的成本,更低的效率。
拿前端静态部署来说,隔离的极端情况是,一个物理机上部署一个前端应用。这种情况下,10个应用就需要10台机器,这个对成本是极大的占用,利用率也很低, 要怎么解决这个问题呢:引入容器的概念,在一台物理机上通过对进程和资源的隔离做了容器化,然后一台机器上就能部署多个应用了。这也是当前用容器云直接部署前端应用的情况,他带来了什么问题呢,虽然已经比上面的物理机情况好不少了, 在大部分场景下,一个前端应用几乎消耗不了 1c2g的硬件资源,资源利用率仍然很低。同时,容器的发布效率也不够快,是分钟级的,前端的静态部署本质上是在做文件替换,根本不需要像服务端应用那样做服务重启,这也就是隔离带来的低效和高成本。
进一步的统一能够有效减少这一问题,也就是当前前端KFX静态部署的托管模式。KFX将多个静态服务托管在了一个容器上,由内部的服务来接管应用变更这一逻辑,容器长期存在不会随应用发布而重启。 这样的好处是,能让发布的时间达到秒级,同时资源的利用率也上去了。
整个演进过程如下所示:
可以看到,静态服务托管这种统一化的模式,优势是高资源利用率,高效率,劣势是不隔离,服务间是有可能存在相互影响的,可能存在更多的稳定性风险。
在方案设计中,我们会认为静态部署本身产生的耦合和影响是可接受的,并不会出现server服务混布高频出现的硬件资源、网络的竞争,因此用这一稳定性来换取效率是合适的。 同时,在新春除夕项目场景下,为了进一步提升稳定性,我们为新春除夕项目活动场景单独部署的私有化集群,从而跟其他非活动应用隔离开来,提升该场景下的稳定性。
什么时候应该统一
统一和隔离,虽然是两个对立的概念,但是在实际的应用过程中,是可以相互应用的,简单的来讲就是:在该统一的地方统一,该隔离的地方隔离。那么Gundam在新春除夕项目里是怎么做的呢,对于预热、除夕、留存三大会场,都采用了以下的标准化:
用CDN方案展开来讲,在新春除夕项目中具体带来了那些优势呢:
在生产侧业务无需关心CDN应用和流水线上传配置,Gundam 提供了标准统一的CD流水线。
在消费侧无需关心工程中 CDN 打包配置,只需要引入 Gundam CDN插件
容灾能力无需业务关心,统一CDN应用大大简化各会场容灾方案的配置和管理复杂度,能将容灾场景能力做完全插件化,在容灾演练时也不需要每个会场能力单独验证。
CDN侧无需分配多个会场的预估资源。
那么,为什么这次所有会场前端可以采用相同的CDN应用:
什么时候应该隔离
新春除夕项目采用了统一的部署方案和集群, 但是新春除夕项目的KFX集群和网关集群会跟其他集群隔离,保障在新春除夕项目期间,不会受其他业务和活动的影响。
不同会场的项目工程,CI/CD 流程一致,但是实例隔离, Gundam 为每个活动会场的子应用不同环境自动创建了独立的CI/CD流程,保障各应用间发布变更流程不会相互干扰, 不同的应用可以按需配置管控方案。
总结
统一与隔离,看似对立的概念其实在实际应用中并不是对立的,我们需要根据实际情况做权衡。新春除夕项目活动中,我们根据活动特性对框架/构建工具、网络库、CI/CD流水线方案、部署方案、CDN方案进行了统一,能让工程能力、物料在各会场间快速复用,相同的CI/CD方案还保障了devops流程的稳定性, 能有效的提升活动整体研发效率,保障活动稳定性底线。整体来说 Gundam 旨在让业务同学不需要投入时间解决繁琐的工程化问题,专注于业务逻辑实现。
2.2 透明化:封装与扩展
上面一直在强调说,对业务无感知。业务完全无感知,这真的是好事吗?
业务对这个方案无感知,那个方案无感知,一旦出了问题,业务不就两眼一黑了。
用大家熟悉的 ts-node举例(首先我认为 ts-node 是一个很不错的框架), ts-node是用来作为ts文件不能直接执行的解决方案, 被很多人认为“too much magic”。众所周知,ts文件是不能直接执行的,但是ts-node造成了这种错觉,相比node, ts-node 的“执行” 是将正常ts的编译和执行两个流程封装为了一次流程,在内存中编译并执行。
这种方式好不好呢,大部分情况下是好的:能够丝滑的不产生js文件直接执行,方便开发调试效率,但是 “如果封装得很好,业务无感知,什么问题都不会有。但是一旦出问题,就会非常让人头疼。” 我们可能遇到过,使用ts-node执行同样的模块,声明成cjs能正常执行,但是 esm 就不行,模块引入必须要改成严格.js 后缀(后续提供了experimentalSpecifierResolution 来抹平这个问题), ts-node为了给你丝滑的直接执行ts的体验,内部自行实现了对于ts文件的模块加载方式, 跟typescript原生方案不是完全一致的,所以在跟进兼容typescript方案的过程中,总会有各种各样的坑,这种时候去ts-node 的issue 看看,能发现一堆对于模块加载的问题。
一句话总结就是: ts-node 为了达到直接执行ts的效果,对模块解析和执行能力进行了黑盒封装,往往会导致出问题难以排查和解决。
Gundam 是如何做能力封装的
跟ts-node类似,Gundam作为一个脚手架框架,主打的一个目标就是业务无感知来做效率提升,很多功能我们都希望业务开箱即用,在某些特定的场景下,开箱我们就帮你做完了,都不需要你来用。比如:创建weblogger,CDN容灾能力, 注入雷达种子包, fmp自动上报的目等。
这些能力的实现方式, 并不是在初始化的时候在模板里集成这些代码片段,而是在构建时去做的动态注入。
前者像是一锤子买卖,把东西准备好了之后就甩给开发者,这部分代码在后续迭代过程中逐渐会跟业务逻辑耦合在一起。然而项目总是处于一个不断迭代和更新的过程,底层的工程化能力也一样,会在维护过程中也需要不停迭代工程化的能力,按照前者的方案,功能能力代码和业务代码耦合后,很难在业务改动一段时间后再去进行能力的升级。而后者Gundam采用的方案希望做到的是不侵入业务逻辑代码(src目录),每次都在构建时做动态生成和注入,只改变产物,不改变源码。在后续迭代过程中,也能做到不修改任何业务代码文件的情况下升级工程化能力, 在排查问题时,也能做到能力可插拔。
这一方案的整体实现思路如下:
左边的上方是 Gundam 插件的基座,下方是用户的工程源码。
根据插件的两种类型, 分为基于 vite hook 的构建能力和基于 Gundam hook 的通用能力。Vite hook 依赖 vite插件系统本身提供的hook ,包括 build start、load、tranform 等,在这些 hook 中可以实现我们对源码的分析和修改,然后与用户的vite config 合并为实际运行的config。所有关于源码的操作都是在这些hook中进行的,因此可以达到不侵入用户源码。
另一部分通用能力,则依赖于Gundam cli 自己定义的生命周期,例如 开始初始化、初始化完成,postinstall 等等时机,能够按需执行一系列的操作。然后通过在运行时对hook的实际调用, 执行与相关基建工具的通信 或者是对 构建产物的修改, 从而达到能力注入的目的。
能力封装了就会黑盒吗
有同学看到这就会觉得,你这不就黑盒了吗,等于你帮我写了一堆代码,我在源码里都看不到,产物又是打包后了,看起来也费劲, 我要查问题怎么办啊?我要要做业务场景定制化怎么办?
同学你好,你先别急,听我解释:
这个问题主要是解决 Gundam 封装的能力,如何在业务需要时能够快速的理解其原理,方便排障、定位问题或者是准备应急预案,也就是把封装的能力不再黑盒。 比如在这次新春除夕项目活动场景中,业务同学不会去编写容灾相关的逻辑,那如果上线后容灾功能出了问题,就只能找相关能力提供同学支持&着急了吗,这肯定不是我们所预期的。
对于黑盒透明化,我们从方案层面和工程层面两个方向来进行:
方案层面
方案层面主要分为三个步骤:评审、验收、演练。还是用上面提到的CDN容灾为例
评审:首先我们会提供详细的容灾方案,并且在各会场进行技术评审。即使不要业务同学参与开发,但是业务同学可以从方案中了解具体的原理,和一起评估案的可靠性和存在的风险。
验收:然后是接入相关能力插件提测,在测试环境跟随功能验收。这个过程能保证我们的方案不会影响到正常使用的业务逻辑。
演练:最后新春除夕项目的场景下,会有横向稳定性小组进行容灾演练,模拟一些故障注入来验证容灾逻辑的可靠性。
工程层面
动态可插拔:Gundam的能力都是由插件来提供,插件是对业务逻辑无侵入的,因此可以在排查问题的时候可以按需进行插拔,能够很容易得到一个较为“纯净”的项目,排除由插件“黑盒”产生的影响。
动态inspect:如果想进一步了解插件具体对运行时的改造和影响, Gundam对于运行时代码的注入,提供了inspect 的方式,可以看到Gundam 插件在构建时对源码具体做了哪些修改,当前是通过vite inspect 的方案来实现的。
以 Gundam 埋点插件 为例,埋点插件主要功能有三部分:
自动进行weblog实例创建
自动设置Radar+插件并设置RadarId
自动注册用于Weblogger埋点的Vue指令
下图通过 inspect 展示了 Gundam weblogger 插件构建时在 src/main.ts 中注入一段 weblogger 引入并执行初始化的代码(没错radar应用也是自动申请好了):
自定义配置接管:如果需要进一步的业务定制,可以在Gundam提供的配置文件,比如配置雷达的采样率、weblogger上报公参列表等。
如果上面的方式还不满足,插件还提供 autoCreateWeblog 参数设置为 false 来关闭自动实例化行为, 然后采用业务代码手动实例化的方式。
可以看出,在尽可能提供开箱即用的情况下,我们仍然保留了降级方案。封装和扩展本来也不一定是一个对立的概念,这里的中庸之道就在于,在特定场景下提供尽可能的封装来提升效率,但是需要保留对扩展和场景定制的开放能力。同时在需要问题排查的场景下能够足够便利的将“黑盒”变得足够透明。
但我们要注意,选择降级方案虽然增加了灵活性,但是不可避免的降低了效率和需要花费更多的成本,理想情况下,我们希望通过封装方案结合特定场景就能解决问题。这个问题我们留到下一个章节讨论。
总结
封装的好处是,无需使用者感知具体实现细节,是对效率有帮助的,代价是出了问题难以排查和解决。Gundam在能力的实践中采用构建时注入的方式进行能力提供,可以不侵入用户的业务代码,一方面方便功能迭代升级, 另一方面在排查问题时能方便支持热插拔。在更进一步的场景中,还能支持用户按需运行时 inspect、 通过自定义的方式来接管配置,达到能力透明化的目的。
接下来讲一讲, 未来 Gundam 的发展方向。
三、展望:工程化漏斗
3.1 什么是工程化漏斗
就像上面说的 “理想情况下,我们希望通过封装方案结合特定场景就能解决问题”, 但是我现在带大家认清一下现实。现实情况下,几乎不能将所有的工程化能力都在框架层解决,否则工程化的问题就没有那么复杂了。
下面会给大家介绍 “工程化漏斗” 的概念 ,这个词是我写这篇文章的时候编的,如有雷同,以我下面说的意思为准。
我把工程化漏斗分为三层,第一层是框架层,指可以提供插件、sdk 等方式在研发框架层面上进行集成,这部分能力是与工程代码紧密结合的,业务可以无感知(框架预先默认集成)或者是简单引入就能解决问题。例如基础的字体裁剪、图片裁剪、fmp自动上报等能力。
往下是研发工具层,无法在框架层解决的工程问题会进入到这一层,从这部分开始就已经脱离工程代码了,这部分的解决方案,需要借助外部的研发工具,按照工具文档或者某一个领域的具体SOP来操作。例如我们在性能优化的方向借助性能归因平台、破晓平台、EVA等工具,提供一些优秀实践的指导然后在回到工程里进行优化。
最下面的兜底层是领域专家,进入这里的问题,大多是凭借业务开发同学的经验和已有的工具不能解决的,例如我们在大型活动中,会有性能、动效方向的专家BP同学,解决上面框架和工具都不能解决的问题。
大致可以描述成下面这个样子。
3.2 举例:图片领域的漏斗
用工程化中图片处理来举一个实际的例子, 在新春除夕项目活动场景中,基础的图片压缩能力由 Gundam图片压缩插件提供,能在启动开发服务后自动对工程目录的图片进行压缩,根据场景调整好压缩参数后,不需要开发者更多关注,就能持续在整个研发流程中完整这个任务。这个是属于框架层的能力。如下图所示:
需要具体分析文件,或者是图片使用通用参数压缩后不能满足需求,这个时候就需要将这些素材使用提供的图片压缩工具手动调整参数,得到满足需求的结果。再或者,这个动效文件本身存在可优化的空间,但是跟具体的动效图层设计相关联。需要完成这些工作,就需要通过专门的图片压缩工具来完成这个事情。如下图,是一个通过工具对lottie资源检测的例子:
再往下可能就是现有工具不能很快解决的疑难杂症,比如 lottie的OOM问题排查,在各种兼容性环境下引起的异常行为问题解决,这部分问题大多是深水区的,并且可能是首次出现的。需要相关领域的专家借助合适的工具才能有可能解决这部分问题。
3.3 工程化漏斗的演进
工程化漏斗一定会存在,但不一定是上面的样子,根据发展阶段不同,呈现出来的样子也不一样。我理解的发展过程应该大致呈现下面的样子。
用动效这块领域来举例,这部分能力是主站前端去年重点建设,并且在新春除夕项目的场景中进行了落地和实践。
最初,在没有任何积累的时候,我们只有碰到的工程问题,这个时候只能通过领域专家来解决,在不断解决过程中,将经验以文档的方式进行沉淀:就有了动效开发经验小册,然后进一步将文档能力自动化,就有了AUG 动效效能平台。在新春除夕项目的场景中,这部分能力以sdk的方案结合了Gundam ,就有了 图片压缩插件这样开箱即用的能力。这就是一个实际的演化过程。但是不得不说的是,每次的自动化过程,并不能完全支持所有的工程化能力,这也是未来的一个建设方向,提高工程化的自动化和标准化程度。
漏斗的宽度决定了工程化的标准化的程度,即标准化程度越高,包含的工程领域就越多。漏斗的斜率决定了工程化的自动化程度,即自动化化程度越高,越能在最顶层解决问题,业务侵入小,效率高。
因此,扩大标准化、提高自动化也是Gundam 接下来的重点规划。24年 Gundam 启动标准工程化工作台(Standardized Efficiency Engineered Desk)专项,简称 SEED 专项 。SEED 的定位是向上连接各工程领域,将工程化的能力进行标准化收口,提供标准化的能力市场,能力以多层漏斗的方式来提供。向下连接用户实际工程,能帮助用户尽可能的减小使用、管理、配置的触达路径。也就是锚定着这两个方向:在横向上,进一步扩展工程化覆盖的领域,提高标准化和完备程度。在纵向上,提升工程能力的自动化程度,让更多的能力在框架层面上得到解决。
四、总结
最后, 这里给太长不看直接滑到底的同学们也总结一下:Gundam 作为工程能力的提供方,也是技术标准化落地的关键工具。在新春除夕项目活动中,我们通过工程能力的标准化和自动化提升了研发效率,保障稳定性底线,同时,在每一次活动落地都能积累宝贵的经验,看清当前存在的问题和未来发展的方向,Gundam未来也会沿着建设工程化漏斗的方向前进,进一步扩大工程能力标准化,提高工程能力自动化。