点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群
2021 年上半年早已过去,回顾 Node.js 在国内的发展和生态建设,我们积累了一些经验,摸清了一些方向。在所谓的「Node.js 后框架时代」,其技术发展将随着语言演进和整体前后端技术架构的升级,将会搭上高速快车。
这篇文章,我们采用实录(AMA)的方式,就和 @天猪 关于「Node.js 框架设计及企业 Node.js 基础建设」相关话题的讨论内容进行总结。接近 90mins 的线上讨论,这篇文章相信对前端开发者,尤其是 Node.js 开发者会有所启示。
EggJS 官方站点
分享人介绍:阿里-天猪
相关周边:
如何评价阿里开源的企业级 Node.js 框架 EggJS?
EggJS 一个讨论
Egg & Node.js 从小工坊走向企业级开发
天猪,Egg 开源接口人,cnpm 接口人。隶属于蚂蚁体验技术部 - 广州分部,负责蚂蚁 Node.js 基础设施建设,包括 Chair、TNPM、BaaS 服务等。日常工作比较跨界,涉及到研发平台,PaaS,框架,包管理。
分享内容我们按照:
Node.js 框架设计相关
Node.js 方向相关
生态发展和开源社区相关
三大方向展开,每一个方向对应若干讨论话题/问题。下面展现部分交流内容。
话题一,关于 Node.js 框架设计
如何看待其他 Node.js 框架,如 ThinkJS, fastify 等?如何看待和对比 farrow-js,函数式风格的 Node.js 框架,相比较而言设计理念上差异还是很大的。
天猪认为,
定位不一样, Egg 的定位是框架的框架,面向的目标用户是团队的架构师或技术负责人,帮助他们来定制适合特定的业务场景的上层业务框架。解决的是大规模企业级开发时的生态共建 + 差异化定制问题。
像 Chair, Midway,beidou 这些才是面向一线应用开发者的上层框架,我们今年也会开源一个 TEgg 方案,提供官方的 TS 方案,以及业务逻辑复用能力。
从天猪的角度看,完全可以用 Egg 封装出一个 ThinkJS 类似风格的框架。
编者按,
不管是 koa 还是 express,它们都属于轻量级的 Node.js 框架,它们使用起来优点明显:使用简单,中间件机制灵活,社区生态完善。
但是在实际使用中,随着业务复杂度上升,以及不同场景下对于 Node.js 框架的不同诉求,在直接依赖 koa 或 express 的实践中我们发现了一些痛点,包括但不限于:
需要业务手动配置各种中间件
在上一条条背景下,一些基础通用的中间件缺少统一抽象和维护
容易出现重复代码以及各造轮子(重复中间件逻辑)现象
koa 和 express 并不约束项目规范(比如目录结构等),不同水平程序员搭建的 Node.js 项目风格不统一,质量参差不齐
koa 和 express 并不直接解决 Node.js 项目的基建部分,不解决调试、开发配置等关键环节,使得生成效率难以最大化提升
为了解决上述问题,社区上出现不少基于 koa 和 express 的上层 Node.js 框架,比如:
阿里体系基于 EggJS 之上的 Chair、Midway 等框架。
Spring 风格的 NestJS
360 奇舞团的 ThinkJS
这些框架在不同程度上封装了 koa 和 express,并给出了各自风格的上层方案,但都也不完全是一个开箱即用的企业级 Node.js 框架,不能直接满足 XX 企业内场景,难以直接融入已有 Node.js 项目,理想情况下,仍需一道封装。
此外,社区上还活跃着着:fastify,farrow-js,restify,hapi,sails 等 Node.js library/framework,各自侧重点(性能优先/易用性优先等)和分层领域(low level/high level 等)各不相同。
对于一套企业级 Node.js 框架来说,设计一套 Node.js MVC 开发框架,或在 Egg 之上再封装,其目的是保障易用性、稳定性的基础上,提升 Node.js 项目开发的效率及服务性能。
话题二,一个关于 Node.js 框架性能的讨论
EggJS 文档中关于特性描述,提到了「基于 KoaJS 开发,性能优异」。其中,前半句「基于 KoaJS 开发」和后半句「性能优异」构成因果关系吗?如果构成,KoaJS 带来的性能收益体现在哪里?如果不构成,那么 EggJS 关于性能方面做了哪些事情?(除了 co 升级为 async/await 之外)
天猪认为,
没有因果关系,这里当时只是陈述 Egg 的 2 个特点,在那个时间点来说,Koa 在社区的认知里面还是属于先进的(那时候还是 Express 的中期)。
其实对于大部分的应用来说,框架远远不会是性能的影响因素,性能更多可能出现在和后端通讯的序列化/反序列化,不合理的调用链,耗时的 CPU 操作等等。
Egg 宣称自己有优异的性能的底气在于:在过去 9 年来在线上双十一量级的压力场景下的稳定表现,绝大部分性能问题都能被发现并逐一解决(AliNode 利器),举个例子,最近语雀居然发现 Router 成为了性能瓶颈之一(1000+ 路由,20ms)。
同时丰富的实践也让我们很清楚一个事实,就是大部分的性能问题,都轮不到框架来背锅,很多都是业务不合理使用。因此我们在性能上优化主要不体现在 Egg 框架本身。比如,fasitfy 基于 Schema 来优化序列化的性能,这些好的社区实践我们也一直在吸收,如 sofa-hessian-node 针对复杂对象场景的性能有明显的提升。
编者按,
Egg 方面通过相关 benchmark,发现去除 co 后堆栈信息更清晰,能带来 30% 左右的性能提升(不含 Node 带来的性能提升),但对于 Egg 本身来说,框架并没有着重发力性能细节,没有像 fasitfy 等框架内置性能机制优化操作,对 perf friendly 锱铢必较。
事实上,对于一个 Node.js 服务来说,服务的响应性能瓶颈往往在业务工程当中,在框架设计方面,使用便利性和性能的平衡是一个永恒的话题。当然,社区上也鼓励和欢迎任何一性能为「卖点」的框架。
这里希望大家注意的是,脱离业务的性能优化——只会是框架本身的「自嗨」。像 sofa-hessian-node 案例一样,由业务中发现性能痛点(后端交互的序列化性能瓶颈),再去借鉴业界的相关实践,最终业务和技术相互促进,框架在各个层面也会得到更多发展,是一个非常可贵的相互促进结果。
话题三,仍然是一个关于 Node.js 框架性能的讨论
EggJS 加载器的设计,看上去在启动时并不很性能友好(比如 Egg 会遍历项目中的 loadUnit,再比如插件的加载过程)?这方面有相关的设计考虑吗?
天猪认为,
在 Egg 发起时那个时间点,甚至包括现在,启动的时间其实没那么重要,因为都是集群部署的方式来分批滚动升级的。
一般应用插件也不会太多,我们最复杂的场景 Chair 内置的插件近百个,读取文件的耗时其实占比很小,更多的启动耗时在于某些插件需要和后端中间件服务建联,拉取配置等等初始化上。所以上面提到的『遍历目录』这个其实不会对性能有影响,而要看插件本身的自有逻辑复杂度。
Serverless 时代,做极速启动时可能就会有一定的影响,这块可以通过构建期的预分析,来帮助框架减少文件系统的遍历,但收益不一定大。
编者按,
Node.js 框架启动性能往往是一个被忽略的话题,但在容器化和 Severless 大势发展下,启动速度逐渐被开发者所关心。这一方面,我们将会持续关注相关话题。
话题四,一个关于框架渐进式设计/发展的讨论
EggJS 内置了 static 服务插件,在 EggJS 内置哪些插件/能力的设计上,考虑有哪些?(比如为什么 static, i18n, logger 作为内置插件出现,这些能力像其他插件一样做成可插拔的不更好么?
天猪认为,
由于历史原因,当时 Egg 开源的时候没有拆的很干净。其实可以看下 egg-core,它其实才是 egg 的最核心部分:
Koa 在 Node HTTP 之上提供了一层很薄的语法糖封装,并提供了洋葱模型。
egg-core 在 Koa 之上,提供了一套 Loader 机制,并基于它延伸出了插件和上层框架。
特定场景框架:chair-serverless | midway-faas | ↑ ↑ 团队业务框架:chair | midway | nut | ... ↑ ↑ ↑ ↑ 阿里统一框架:@ali/egg ↑ ↑ ↑ ↑ 开源社区框架:egg
事实上,Egg 的应用、插件、框架的目录结构几乎一模一样。
实际开发过程中,我们也有一套 渐进式的演进方式,分享给大家:
实验性的功能,可以先在应用里面实现,作为 inline plugin 通过 path 方式来挂载。
功能稳定后,就抽出来变为独立的插件,应用再通过 npm 依赖方式引入,只需改两行代码即可。
当该功能成熟后,成为团队的统一规范时,直接把这个插件集成到 Framework 中,所有应用只需重新安装下依赖,即可立刻享受到。
这个过程是闭环的,是渐进式,而且升级过程几乎无痛。
编者按,
对于任何一个框架的设计来说,渐进式是一个重要的课题。
比如 Vue.js,比如 React.js,渐进式的设计能够保证业务开发者更敏捷地迭代,更方便快捷地使用框架,同时依然保留有对架构进行优化、基础能力进行下沉的能力。对框架设计者来说,渐进式的设计,更是保障框架本身进步发展的关键。
话题五,一个关于框架的上层封装讨论
关于基于 EggJS 的上层框架方案,有没有 Best pratice 建议?比如,Chair 基于 EggJS 的封装,做了哪些「有趣、不一样」的事情吗,有哪些值得借鉴的吗?
天猪认为,
上层框架是针对特定的业务场景 或 特定的基础设施 的封装,而随着一波又一波的需求过来,各种阶段性的妥协,基础设施的演进,它注定是维护和治理的重灾区。
一个新的插件,该不该内置,如何渐进式的推进,又如何在它被另一个插件替代时,推动下线。业务场景变化的时候,框架该不该多套娃一层。锁不锁版本,如何推动更新,出现问题如何快速止血,等等对于这些问题,框架的应用规模是 10 和 1000 时的思考点是不一样的。
话题六,关于设计遗憾和未来计划的讨论
关于 EggJS 的进程管理问题,以及相关部署、重启等环节,在容器化 docker 化的背景下,EggJS 的相关设计是否需要跟进和调整?
天猪认为,
Egg 的 agent 提出的背景,是后端中间件服务曾经被我们 ddos 了,当时好像是 diamond 这个远程配置服务,我们每一个进程,都会在启动的时候向它拉取配置,所以需要有个 agent 来统一做这事,拉取完后再共享给 workers。
在 Serverless 的大背景下,类似的配置拉取其实有云原生的内置方案,不需要框架层过多考虑。我们也确实有考虑单进程模型,并在内部也有一些实践,但目前没有看到特别的收益。
在 Egg 3.0 里面,cluster 不会内置,单进程模式也能对 mock 和单测这块带来更清晰的逻辑。
EggJS 有不适用的场景吗?是否有在设计上的一些遗憾?未来有哪些计划?
天猪认为,
受限于 Koa,目前更适合于 Web 框架。在 3.0 Context 模型后,这块应该会更灵活,不再受限。据我所知,有不少开发者用来当队列处理器,或者任务执行器。前者不是不可以用,只是总会觉得有一点别扭。后者其实 FC 这类函数计算会更合适。
Egg 3.0(egg-core) :
会更纯粹,一套 Loader 机制 + 洋葱模型,面向未来。
Loader 的生命周期支持异步(从而可以支持异步配置)
不依赖于 Koa,可以脱离 Web 框架这个局限,我们需要的只是洋葱模型,可以自行实现,核心是一个 Context,根据不同的流量入口,实例化为 HttpContext、RPCContext、WSContext 等。(我们对 socket.io 等的支持就不会看起来太别扭。)
以上是 Egg 的后续规划
编者按,
很少有一种技术设计,能够完全开启「我在 XX 年后等你」的视角,关于 Node.js 的单进程/多进程模型等诸多话题的背后,其实是基础服务设施的变迁,编程模型趋势的演进浪潮。这一方面,前端开发者将会越来越多的「跨界」,也只有迈出更多步,才能发挥更大的价值。 一个 Node.js 框架设计实在是整个技术链条上的微观的小环节,但管中窥豹,我们的边界在更远方。
下面,我们进入整个 Node.js 方向上的讨论。
话题一,关于下一代 Node.js 框架和 Node.js 趋势
未来(下一代) Node.js 框架会有什么样的发展趋势?你们团队目前探索的方向是?(比如 BFF -> SFF?)
天猪认为,
框架主要体现在研发模式这块,涉及到用户编程界面层。框架是一个重要的点,但不是全部,藏在冰山之下的还有整个研发生命周期(研发平台、迭代模型、部署、前后端研发模式、监控体系等等)。
Node SDK 这块未来会 ServiceMesh 化掉,它们应该回归后端中间件团队的工作范畴,毕竟由后端来维护自己的服务对应的 SDK 才是最合理的分工。
一个团队的业务,可能一下子不需要 Node.js,可以不用,但团队必须具备随时可以有的准备,没有这个储备,你们的技术选型视角和话语权会完全不一样。Node.js 是前端的一个可选能力,也是一个必要的储备。
如上图,Node 的一部分场景是聚合逻辑层,会往云端一体化方向发展,另一部场景是会走微服务方向,跟后端的框架没啥区别,当今的业界认知是跨语言方向。
话题二,关于 APM
关于 APM,我们知道 AliNode,除此之外还有其他建议或者经验吗?关于 AliNode 目前发展情况,比如 runtime 替换掉 Node.js runtime,这种方式是否会成为趋势,阿里之外有比较成熟的应用吗?
天猪认为,
可以考虑用一君的 easy-monitor,优点是:
比 AliNode 更低的维护成本,通过 Addon 方式,而不是 Runtime 方式,从而不绑定 Node Runtime 升级,更快的适配。
支持私有化部署。(虽然 AliNode 的 agent 上报是开源的,但人与人的信任还是很难的)
开源。
说到 APM 这里,其实我们内部曾经有过一个有趣的讨论:Serverless 后, APM 还重要么?反方观点:内存泄露就内存泄露呗,小的泄露无所谓,到了阀值,直接动态腾挪换机器。我个人认为 APM 还是很重要的,工具本身很重要,它是查问题的利器和底气。直接腾挪是实际运维的 ROI,只是代表了某个时刻,暂时可以不用费精力查问题,但不代表问题不应该被解决。
编者按,
重点再聊聊 APM,上面的辩论(「Serverless 后, APM 还重要么?」),反方其实是在靠中台能力来降维规避问题,这背后其实是一个技术态度问题。
Node.js 服务自己的问题,还需要自己来解决。那么出现了 Node.js 方面的问题,如何借助 APM 来进行调试,发现并解决问题呢?我们又积累了哪些实战场景经验呢?请关注我们,后续会带来更多线上真实案例。
话题三,关于 Node.js 原生 http 模块的一个讨论
EggJS 基于 urllib 实现了一个 httpclient,这个考虑是什么,性能吗?接上问,EggJS 既然实现 httpClient,那么如何看待 Node.js undici,似乎是一个更贴近官方的方案?fastify 一开始就在使用 undici, 如何看待主打性能的 fastify,Node.js 实践经验上,性能是否是比较棘手的一环?
天猪认为,
因为 urllib 是我们实现的,在这么多年的实践中,踩了 http 的无数坑,跨过之后沉淀到 urllib 里了,多年后它的稳定性我们无比相信。所以目前重度使用了 urllib。undici 最近也有关注到,也稍微用了下。从现在这个时间点来看, urllib 的代码确实有点乱,我也两度想重构它,但 ROI 不是很高,就没下手。未来也许有机会把它重构掉。
编者按,
Node.js 及其相关的基础建设决定着 Node.js 落地情况,也决定了 Node.js 最终价值。这主要可以分为两个方向:
Node.js 本身语言演进
Node.js 对接中台基础
其一 Node.js 本身语言的演进,
比如,undici 提供了 Node.js core http 模块的代替方案,带来了性能和稳定性的提升,解决了 Node.js core http client 历史的重大技术债务。
另外 Node.js 对接「更后的后端体系」,是基础建设的重要一环,比如 ServiceMesh 生态。
举个例子,通过 Sidecar 模式我们可以在 Kubernetes Pod 中,在原有应用容器旁边运行一个伴生容器,由 Sidecar 接管进出应用容器的所有流量,实现 just work 的应用容器监控。
但应用容器本身的运行细节,如 CPU Profile,Memory usuage 等依然需要 Node.js 开发者给出答案,因此有了 AliNode,也有了不侵入 Node.js runtime 的 Easy-monitor。
对于企业级 Node.js 解决方案来说,不妨经常反问自己:「我们还缺什么,我们还能做些什么」。毕竟 Node.js 基础建设的发展始终在探索中前进,在这方面,希望每一名开发者贡献力量。
话题一,锁/不锁版本,ESM 和前端生态协作链
2021 年,对于版本管理和 Node.js ESM 有哪些新的看法?(比如锁版本,Node.js package 对 ESM 的支持程度)
天猪认为,
锁版本 vs 不锁版本,一个永恒的话题。天猪的观点是,
不同团队基础能力下的权衡,没有银弹。
锁版本是一个保守的方案,击鼓传花,把炸弹留给后人。
前端依赖和后端依赖是 2 个话题。
事实上:等有空了再统一升级,往往是一个美丽的谎言,这个在工程上的可执行性不是很看好。
这里附上旧文一篇:为什么我不使用 shrinkwrap(lock) ,后面天猪会写一篇新的。
继续这个话题,在职责分离维度,
框架层面可以考虑锁自己的子依赖,框架维护者来负责推动升级,应用开发者锁自己引入的依赖,职责分离。但这一点上,需要 npm 工具和研发平台上的紧密配合。
给框架维护者提供一种锁内部版本的机制,且能由框架维护者来主导应用的框架版本升级
当然提供 CITGM 机制来帮助框架维护者回归,也是很有必要的。
这里附一个「彩蛋图」——「研发平台」中关于版本和生态的「仪表盘」:
我们不妨继续探索「研发平台」,即基于研发平台的依赖生态解决方案:我们给出设计如下,
本地、开发环境,不锁版本。
信息公开,每次迭代构建后,依赖的更新情况要让开发者清晰。
配套的止血机制,未来需要更智能化。
紧急迭代,测试 → 预发阶段,允许开发者在平台锁版本。
编者按,
在生态发展和开源社区相关部分,我们和天猪重点聊了「锁/不锁版本」的问题。
这个问题虽然老生常谈,但他绝不是一个包管理方案选型的问题,「锁/不锁版本」的背后,何尝不是整个前端社区生态。这方面,不同团队给出的不同答案,都在诠释着对前端社区生态的不同理解。
同时,我们也期待 关于现代包管理器的深度思考——为什么现在我更推荐 pnpm 而不是 npm/yarn 之后的 TNPM,以及不断进化的前端技术潮流。
话题二,关于项目开源和普及度
EggJS 在阿里内部是如何推行的?推行当中遇到的最大困难是什么?(可以再透露下目前内部哪些系统使用了 EggJS 吗)
天猪,
好像没怎么推行,就普及的,天时地利人和吧(编者按,好凡尔赛!手动狗头)
2015.10.13 内部拉通 Node 工作组,闭关一周,产出一份 RFC,基于 RFC 产出 @ali/egg 1.0
2016 年 JSConf 开源,年底的时候差不多几个大的 BU 的上层框架都重构为基于 egg 了。
目前你能看到的阿里系的页面,绝大部分都是基于 Egg 的,像蚂蚁大部分流量入口都是基于 render 这个高性能的渲染服务(蚂蚁森林),天猫那边的斑马,还有财富的好几个大的 BFF 有数千个 POD
Node 是阿里的第二大语言,而 Egg 是唯一的官方框架,不是基于 Egg 的框架,无法跟内部的各大中间件交互,也无法很好地被研发平台和监控平台等支持。阿里有数千个 Node 应用。
语雀 - 可能是西湖区最大的 Node 全栈应用。
目前这个时间点会遇到一些问题,整个大环境变了,所以 BFF → SFF
不管是 Node.js 还是其他前端技术方案,我们都期待着一个更友好开阔、更互动交流的氛围。我们和 @天猪 的交流,名义上是「 Node.js 框架」,其实更是对 Node.js 甚至前端未来发展的讨论。
个人力量太过有限,但我们对技术抱有理想,也寻求更多的交流、共创。????
关注我们,后面将会有更多前端技术方面的想法和积淀产出!
Happy coding!
如果觉得这篇文章还不错
点击下面卡片关注我