前端新思路:组件即函数和Serverless SSR实践

前端新思路:组件即函数和Serverless SSR实践_第1张图片

导读:在今天,对于 Node.js 运维和高并发依然是很有挑战的,为了提效,将架构演进为页面即服务,可是粒度还不够,借着云原生和 Serverless 大潮,无运维,轻松扩展,对前端是极大的诱惑。那么,基于 FaaS 之上,前端有哪些可能性呢?

本次分享主要围绕 Serverless SSR 和它的演进过程、背后思考为主。(长文预警,内含超多干货,请耐心阅读)

文末福利:《2020 前端工程师必读手册》电子书已上线,可免费下载。

2019年上半年,我在阿里经济体前端委员会推进的Serverless研发体系共建项目中负责Serverless SSR的研究,将CSR,SSR,边缘渲染进行整合和尝试,提出组件即服务的概念(Component as Service),试图结合FaaS,做出更简单的开发方式。

本文是狼叔在D2大会的分享《前端新思路:组件即函数和Serverless SSR实践》的内容,阅读需要10分钟,你将了解如下内容:

1.可以了解Serverless时代端侧渲染面临的具体问题 

2.可以了解Serverless SSR规范以及渲染体系的完整工作链路和原理 

3.为业内提供解决Serverless SSR渲染问题的新思路

前端新思路:组件即函数和Serverless SSR实践_第2张图片

狼叔(网名i5ting)现为阿里巴巴前端技术专家,Node.js 技术布道者,Node全栈公众号运营者,曾就职于去哪儿、新浪、网秦,做过前端、后端、数据分析,是一名全栈技术的实践者。目前负责BU的Node.js和基础框架开发,已出版《狼书(卷1) 更了不起的Node.js》。

技术趋势分析

前端新思路:组件即函数和Serverless SSR实践_第3张图片

今年的技术趋势,我的判断是技术混乱期已过,提效才是今日的挑战。

前端新思路:组件即函数和Serverless SSR实践_第4张图片

在Node.js领域,今年新东西也不多,最新已经发布到13,lts是12,Egg.js的生态持续完善,进度也不如前2年,成熟之后创新就少了。在很多框架上加入ts似乎已经政治正确了。比如自身是基于ts的nest框架,比如阿里也开源了基于Egg生态的midway框架,整体加入ts,类型系统和oop,对大规模编程来说是非常好的。另外GraphQL也有很强的应用落地场景,尤其是Apollo项目带来的改变最大,极大的降低了落地成本。已经用rust重写的deno稳步进展中,没有火起来,但也有很高的关注度,它不会替代Node.js,而是基于Node之上更好的尝试。

今日的Node.js存在的问题是会用很容易,做到高可用不容易,毕竟高可用对架构运维要求更好一些,这点对前端同学要求会更难一些。做到高可用之后,做性能调优更难。其实,所有这些难点的背后是基于前端工程师的角度来考虑的,这也是非常现实的问题。

对于很多团队,上Node是找死,不上Node是等死。在今天web框架已经相当成熟,所以如何破局,是当下最破解需要解决的问题。

你可能会感觉Node.js热度不够,但事实很多做Node.js的人已经投身到研发模式升级上了。对于今天的Node.js来说,会用很容易,但用好很难,比如高可用,性能调优,还是非常有挑战的。我们可以假想一下,流量打网关,网关根据流量来实例化容器,加载FaaS运行时环境,然后执行对应函数提供服务。在整个过程中,不许关心服务器和运维工作,不用担心高可用问题,是不是前端可以更加轻松的接入Node.js。这其实就是当前大厂前端在做的前端基于Serverless的实践,比如基于FaaS如何做服务编排、页面渲染、网关等。接入Serverless不是目的,目的是让前端能够借助Serverless创造更多业务价值。

为何如此钟爱 SSR?

在2017年底,优酷只有passport和土豆的部分页面用Node.js,QPS不高,大多是一些尝试性业务,优酷PC 和H5核心页面还都是PHP模板渲染。最近2年基于阿里强大的技术体系,我们也对PC、H5多端进行了技术改造。今年双十一是React SSR第一次扛双十一,具有一定意义,背景就不赘述了,参见 看优酷 Node 重构之路,Serverless SSR 未来可期 。

前端新思路:组件即函数和Serverless SSR实践_第5张图片

  • 将优酷C端核心页面全部用Node重写,完成了PHP到Node.js的迁移。在没有PHP同学的情况下,前端可以支撑业务。

  • 性能提升明显,从v1(Bigpipe+jQuery)到v2(React SSR),性能逐步提升。PC页面首屏渲染降到150ms、播放器起播时间从4.6秒优化到2秒。H5站上了React SSR后,性能提升3倍,H5唤端率提升也极其明显,头条短视频唤端率由5.68%提升到9.4%,环比提升65%。单机性能qps从80提升150+(压测最高可以到300左右)。

  • QPS过万,2年没有p4以上故障,相对来说是比较稳定的。扛过双十一、世界杯,最高三倍以上的流量。

  • 在集团前端委员会承担Serverless SSR专项。

裕波曾经问我,为何如此钟爱SSR?

从前端的角度看,它是一个相对小的领域。PC已经非主流,H5想争王者,却不想被rn、weex中间截胡。怎么看,SSR能做的都有限。但是,用户体验提升是永远的追求,另外web标准化是正统,在二者之间,和Node做结合,除了SSR,目前想不到更好的解法。

贴着C端业务,从后端手里接过来PC、H5,通过Node构建自己的生存之地是必然的选择。

活下来之后就开始有演进,沉淀,通过C端业务和egg-react-ssr开源项目的沉淀,我们成功的打通2点:

  1. 写法上的统一:CSR和SSR可以共存,继而实现二种模式的无缝切换

  2. 容灾降级方案:从Node SSR无缝切换到Node的CSR,做到第一层降级,从Node CSR还可以继续降到CDN的CSR

2019年,另外一个风口是Serverless,前端把Serverless看成是生死之地,下一代研发模式的前端价值证明。那么,在这个背景下,SSR能做什么呢?基于FaaS的SSR如何呢?继续推演,支持SSR,也可以支持CSR,也就是说基于FaaS的渲染都可以支持的。于是和风驰商量,做了Serverless端侧渲染方向的规划。

本来SSR是Server-side render,演进为Serverless-side render。元彦给了一个非常好概念命名,Caaf,即Component as a fuction。渲染层围绕在以组件为核心,最终统统简化到函数层面。

在今天看,SSR是成功的,一个曾经比较偏冷的点已经慢慢变得主流。集团中,基于React/Rax的一体化开发,可以满足前端所有开发场景。优酷侧的活动搭建已经升级到Rax1.0,对外提供SSR服务。在uc里,已经开始要将egg-react-ssr迁移到FaaS上,代码已经完成迁移。

  • PC/中后台,React的CSR和SSR

  • 移动端/H5,Rax的CSR和SSR。尤其是Rax SSR给站外H5提供了非常好的首屏渲染时间优化,对C端或活动支持是尤其有用的。

在2020年,基于FaaS之上的渲染已经获得大家的认可。另外大量的Node.js的BFF应用已经到了需要治理的时候,BFF感觉和当年的微服务一样,太多了就会牵扯到管理成本,这种情况下Serverless是个中台内敛的极好解决方案。对前端来说,SSR让开发变得简单,基于FaaS又能很好的收敛和治理BFF应用,结合WebIDE,一种极其轻量级基于Serverless的前端研发时代已经来临了。

Serverless-side render 概念升级

从BFF到SFF

了解SSR之前,我们先看一下架构升级,从BFF到SFF的演进过程。

BFF即 Backend For Frontend(服务于前端的后端),也就是服务器设计 API 时会考虑前端的使用,并在服务端直接进行业务逻辑的处理,又称为用户体验适配器。BFF 只是一种逻辑分层,而非一种技术,虽然 BFF 是一个新名词,但它的理念由来已久。

在Node.js世界里,BFF是最合适的应用场景。常见的API、API proxy、渲染、SSR+API聚合,当然也有人用来做网关。

从Backend For Frontend升级Serverless For Frontend,本质上就是利用Serverless基建,完成之前BFF的工作。那么,差异在哪里呢?

前端新思路:组件即函数和Serverless SSR实践_第6张图片

核心是从Node到FaaS,本质上还是Serverless,省的其实只是运维和自动扩缩容的工作,一切看起来都是基建的功劳,但对于前端来说,却是极为重大的痛点解决方案,能够满足所有应用场景,基于函数粒度可以简化开发,乃生死必争之地。

SFF 前后端分工

Serverless简单理解是FaaS+BaaS。FaaS是函数即服务,应用层面的对外接口,而BaaS则是后端即服务,更多的是业务系统相关的服务。当下的FaaS还是围绕API相关的工作为主,那么,前端如何和Serverless绑定呢?

Serverless For Frontend(简称SFF)便是这样的概念,基于Serverless架构提供对前端开发提效的方案。

下面看一下SFF分工,这张图我自认为还是非常经典的。首先将Serverless劈成2半,前端和后端,后端的FaaS大家都比较熟悉了,但前端页面和FaaS如何集成还是一片待开发的新领域。

前端新思路:组件即函数和Serverless SSR实践_第7张图片

举个例子,常见BFF的例子,hsf调用获得服务端数据,前端通过ctx完成对前端的输出。这时有2种常见应用场景

  1. API,同后端FaaS(RPC居多)

  2. 页面渲染(http居多)

基于FaaS的页面渲染对前端来说是必须的。从beidou、Next.js、egg-react-ssr到Umi SSR,可以看出服务端渲染是很重要的端侧渲染组成部分。无论如何,React SSR都是依赖Node.js Web应用的。那么,在Serverless时代,基于函数即服务(Functions as a Service,简写为FaaS)做API开发相关是非常简单的:

  • 1)无服务,不需要管运维工作

  • 2)代码只关系函数粒度,面向API变成,降低构建复杂度

  • 3)可扩展

前端新思路:组件即函数和Serverless SSR实践_第8张图片

目前还是Serverless初期,大家还是围绕API来做,那么,在FaaS下,如何做好渲染层呢?直出HTML,做CSR很明显是太简单了,对于React这种高级玩法如何集成呢?

其实我们可以做的更多,笔者目前能想到的Serverless时代的渲染层具有如下特点。

  • 采用Next.js/egg-react-ssr写法,实现客户端渲染和服务端渲染统一

  • 采用Umi SSR构建,生成独立umi.server.js做法,做到渲染

  • 采用Umi做法,内置Webpack和React,简化开发,只有在构建时区分客户端渲染和服务端渲染,做好和CDN如何搭档,做好优雅降级,保证稳定性

  • 结合FaaS API,做好渲染集成。为了演示Serverless下渲染层实现原理,下面会进行简要说明。在Serverless云函数里,一般会有server.yml作为配置文件,这里以lamda为例子。

前端新思路:组件即函数和Serverless SSR实践_第9张图片

SSR 概念升级

现状:

  1. 现有FaaS主要是针对API层做扩展,视图渲染是一个新的命题。

  2. 竞品Serverless.com提供了Components类似的视图渲染层方案

  3. 如何打造一个基于阿里技术栈又有业界领先的端侧渲染解决方案

业界还缺少最佳实践,这是极好的机会。因此,我们对SSR做了概念上的升级(感谢justjavac大佬的提示)

前端新思路:组件即函数和Serverless SSR实践_第10张图片

Serverless端渲染层,是针对 SSR 做概念和能力升级:

  • SSR 从 Server side render 升级为 Serverless side render,基于FaaS环境,提供端侧页面渲染能力。

  • Serverless渲染层涵盖的范围扩展,从服务器端渲染升级到同时支持 CSR 和 SSR 2种渲染模式。

在 Serverless 背景下,页面渲染层包含2种情况:

  • 基于 FaaS 的客户端渲染

  • 基于 FaaS 的服务器端渲染

目标是提供基于 FaaS 的页面渲染描述规范,提供标准化组件描述,统一组件写法,用法简单,易实现,可扩展。因此,我们制定了SSR-spec规范,下面会详细讲解。

Serverless-side render 规范和实现原理

在讲规范之前,我们先简单了解3个术语:

前端新思路:组件即函数和Serverless SSR实践_第11张图片

CSR 和 SSR

先科普一下CSR和SSR的概念。

客户端渲染(简称CSR),简单理解就是html是没有被动态数据灌入的,即所谓的静态页面。比如通过React、Rax编写的组件,打包构建后,以html文件形式分发到CDN上,不需要Node.js支持,就是非常典型的CSR。

前端新思路:组件即函数和Serverless SSR实践_第12张图片

资源加载完成后(注意:只有一个bundle,一次性吐出),通过React中的render API进行页面渲染。优点是不需要服务端接入,简单,对于性能要求不高的页面是非常合适的。中后台应用大多是CSR,优化也都是打包环节玩。

服务器端渲染(SSR),简单理解就是html是由服务端写出,可以动态改变页面内容,即所谓的动态页面。早年的php、asp、jsp这些Server page都是SSR的。但基于React技术栈,又有些许不同,server bundle构建的 时候,要吐多少模块,是server端决定的。client bundle和之前一样,差别在于这次是hydrate,而非render。

前端新思路:组件即函数和Serverless SSR实践_第13张图片

hydrate是 React 中提供在初次渲染的时候,去复用原本已经存在的 DOM 节点,减少重新生成节点以及删除原本 DOM 节点的开销,来加速初次渲染的功能。主要使用场景是服务端渲染或者像prerender等情况,所以在图中hydrate之后才是tti时间。

如果想全局了解CSR和SSR,共分5个阶段,参考下图。

前端新思路:组件即函数和Serverless SSR实践_第14张图片

纯服务端渲染和纯客户端渲染是2个极端,React SSR是属于中间的,这种服务端吐出的粒度是可以根据业务来控制的,可以服务端多一点,性能会差,也可以服务端吐出刚好够首屏的数据,其他由客户端来处理,这种性能会好很多。Static SSR和预渲染的CSR也是特定场景优化的神器。

最佳写法

了解了CSR和SSR的区别,下面我们看一下最佳写法是如何演进的。业内最好的实现大概是next.js了,抛开负责度不谈,单就写法来说,它确实是最合理的。

既然是基于React做法,核心肯定以Component为主,在Component上扩展静态方法用于接口请求是很好的实践。

写法如下:

前端新思路:组件即函数和Serverless SSR实践_第15张图片

早年写过bigpipe相关事项,其中模块成为biglet,它的作用是获取接口,结合tpl生成html。

前端新思路:组件即函数和Serverless SSR实践_第16张图片

biglet的生命周期如下。

before
.then(self.fetch.bind(self))
.then(self.parse.bind(self))
.then(self.render.bind(self))
end

fetch是获取接口数据,parse解析数据,最终赋值给data。render是模板引擎编译的函数。每个函数的返回值都约定是promise,便于做流程控制。

很明显,biglet和Component是异曲同工的,而fetch是对象上的方法,必须实例化biglet才能调用,而next的做法getInitialProps是静态方法,不必实例化,内存和复用性上都是非常好的。

这点在egg-react-ssr项目技术选型调研期,我们就已经达成一致了。问题是,next只支持SSR,如何能够更好的支持CSR?做到真正的组件级别的同构。于是,基于这种写法,通过高阶组件进行包装,轻松实现了CSR,核心代码如下。

前端新思路:组件即函数和Serverless SSR实践_第17张图片

既然写法上统一了,那么,我们还能进一步进行优化么?核心点在网络获取部分。我们能看到的gRPC-web或isomorphic-fetch,分别实现了:1)RPC和http的约定,2)CSR和SSR中fetch统一。给我们带来的启示是在getInitialProps里,我们还可以做更多同构的玩法。

前端新思路:组件即函数和Serverless SSR实践_第18张图片

在webpack打包构建server bundle了的时候会注入isBrowser变量。

const plugins = [
  new webpack.DefinePlugin({
    '__isBrowser__': false //eslint-disable-line
  })
]

以此来区分CSR和SSR做同构兼容就更简单了。

Page.getInitialProps = async (ctx) => {
    if (__isBrowser__) {
        // for CSR
    } else {
        // for SSR
    }
}

以上做法,都是我们基于 https://github.com/ykfe/egg-react-ssr 提炼出来的实践,这些都是SSR-spec的基础。

标FaaS和 SSR 如何结合

有了egg-react-ssr,确立了最佳写法,接下来就是结合FaaS做好集成。

前端新思路:组件即函数和Serverless SSR实践_第19张图片

对比一下,getInitialProps的参数和FaaS函数的参数都有context,这个是非常好的:

  • egg-react-ssr中getInitialProps参数ctx是egg/koa的ctx

  • FaaS函数的参数context挂了各种函数、内存、日志、client相关的信息,把response信息挂上理论上也是可行的,只是处理位置问题,参见 https://docs.aws.amazon.com/lambda/latest/dg/Nodejs-context.html

解法:给 context 扩展SSRRender方法。

为了演示Serverless下渲染层实现原理,下面会进行简要说明。在Serverless云函数里,一般会有server.yml作为配置文件,这里以lamda为例子。

前端新思路:组件即函数和Serverless SSR实践_第20张图片

通过这个配置,我们可以看出函数app.server对应的http请求路径是'/',这个配置其实描述的就是路由信息。对应的app.server函数实现如下图:

通过提供ctx.SSRRender方法,读取dist目录下的Page.server.js完成服务端渲染。

核心要点:

  • SSRRender方法比较容易实现

  • 采用类似Umi SSR的方式,将源码打包到Page.server.js文件中

  • 在发布的时候,将配置,app.server函数和Page.server.js等文件上传到Serverless运行环境即可

架构升级四阶段

纵观SSR相关技术栈的演进过程,我们大致可以推出架构升级的4阶段:

  • CSR,很多中后台都是这样的开发的,最常见

  • 其次是阿里开源的beidou,基于egg做的React SSR,这是一个集成度很高的项目,很好用,难度也很大

  • Umi SSR是基于egg-react-ssr上演进出来的,在umi之上,用户不需要关心webpack,但打包后的代码返回的是stream,这点抽象,云谦做的非常到位,对于开发者来说,还是需要自建Node web server的。

  • 在Serverless里,具体怎么玩是需要我们来创造的。

前端新思路:组件即函数和Serverless SSR实践_第21张图片

对照上图,说明如下:

  1. 在CSR中,开发者需要关心React和Webpack

  2. 在SSR中,开发者需要关心React、Webpack和Egg.js

  3. 在Umi SSR同构中,开发者需要关心React和Egg.js,由于Umi内置了Webpack,开发者基本不需要关注Webpack

  4. 在Serverless时代,基于FaaS的渲染层,开发者需要关心React,不需要关心Webpack和Egg.js

在这4个阶段中,依次出现了CSR和SSR,之后在同构实践中,对开发者要求更高,甚至是全栈。所有这些经验和最佳实践的积累,沉淀出了更简单的开发方式,在Serverless环境下,可以让前端更加简单、高效。

组件即函数

对于组件写法,我们继续抽象,将布局也拉出来。

function Page(props) {
  return 
{props.name}
} Page.fetch = async (ctx) => { return Promise.resolve({ name: 'Serverless side render' }) } Page.layout = (props) => { const { serverData } = props.ctx const { injectCss, injectScript } = props.ctx.app.config return ( React App { injectCss && injectCss.map(item => ) }
{ commonNode(props) }
{ serverData &&

你可能感兴趣的:(前端新思路:组件即函数和Serverless SSR实践)