写在前面
上篇SSR 的利与弊列举了 SSR 渲染模式的 6 大难题:
- 难题 1:如何利用存量 CSR 代码实现同构
- 难题 2:服务的稳定性和性能要求
- 难题 3:配套设施的建设
- 难题 4:钱的问题
- 难题 5:hydration 的性能损耗
- 难题 6:数据请求
这些问题是 SSR 一直以来远不如 CSR 应用广泛的主要原因,但时至今日,Serverless、low-code、4G/5G 网络环境三大机遇让 SSR 出现了新的转机,落地开花正当时
第一大机遇:Serverless
无服务器计算(serverless computing)将服务器相关的配置管理工作统统交给云供应商去做,以减轻用户管理云资源的负担
对云计算用户而言,Serverless 服务能够(自动)弹性伸缩而无需显式预配资源,不仅免去了云资源的管理负担,还能够按使用情况计费,这一特点在很大程度上解决了“难题 4:钱的问题”:
引入 SSR 渲染服务,实际上是在网络结构上加了一层节点,而大流量所过之处,每一层都是钱
将组件渲染逻辑从客户端改到服务器执行,势必会增加成本,但有望通过 Serverless 将个中成本降到最低
另一方面,Serverless Computing的关键是 FaaS(Function as a Service),由云函数提供常规计算能力:
直接运行后端代码,而无需考虑服务器等计算资源以及服务的扩展性、稳定性等问题,甚至连日志、监控、报警等配套设施也都开箱即用
也就是说,喂给 FaaS 一个 JavaScript 函数,就能上线一个高可用的服务,无需操心如何承载大流量(几万 QPS)、如何保障服务稳定可靠……听起来有些跨时代是么,实际上,AWS Lambda、阿里云 FC、腾讯云 SCF 都已经是成熟的商业产品了,甚至能够免费试用
无状态的模板渲染工作尤其适合用云函数(输入 React/Vue 组件,输出 HTML)来完成,“难题 2:服务的稳定性和性能要求”最关键的后端专业性问题迎刃而解,SSR 面临的技术难题从一个高可用的组件渲染服务缩小到了一个 JavaScript 函数中:
与客户端程序相比,服务端程序对稳定性和性能的要求严苛得多,例如:
稳定性:异常崩溃、死循环(由前端人员自行解决)
性能:
内存/CPU 资源占用(由 FaaS 基础设施解决)、响应速度(网络传输距离等都要考虑在内)
如何应对大流量/高并发,如何识别故障,如何降级/快速恢复(由 FaaS 基础设施解决),哪些环节需要加缓存,缓存如何更新……
FaaS 基础设施解决了大部分的性能问题和可用性问题,函数内的稳定性问题可通过纯前端手段解决,至于剩下的响应速度、缓存/缓存更新问题,则需要引入另一个云计算概念——边缘计算
边缘计算
所谓的边缘计算,就是将计算和数据存储分布到离用户更近的(CDN)节点(或者叫边缘服务器,Edge server)上,节省带宽的同时更快响应用户请求:
Edge computing is a distributed computing paradigm that brings computation and data storage closer to the location where it is needed, to improve response times and save bandwidth.
(摘自Edge computing)
像传统 CDN 通过缩短静态内容与最终用户之间的物理距离来加速资源访问,同时减少了应用服务器的负载一样,支持边缘计算的 CDN 允许将云函数部署到边缘节点中,加速服务响应,同时依托 CDN 轻松控制缓存策略,甚至能够实现动静分离的边缘流式渲染(ESR):
P.S.基于边缘计算的 SSR 的更多信息,见前端性能优化:当页面渲染遇上边缘计算
第二大机遇:low-code
如果说 FaaS 解决了 SSR 落地最核心的服务可用性问题,给 SSR 插上了双翼,那么low-code则是让 SSR 得以冲向天际的助飞跑道
因为low-code 几乎解决了其余的所有难题:
- 难题 1:如何利用存量 CSR 代码实现同构
难题 2:服务的稳定性和性能要求- 难题 3:配套设施的建设
难题 4:钱的问题- 难题 5:hydration 的性能损耗
- 难题 6:数据请求
源码开发模式下难以解决的问题,在 low-code 模式下有了不同维度的解法,就像是通过几何方法来解决代数问题
难题 1:如何利用存量 CSR 代码实现同构
要让现有的 CSR 代码在服务端跑起来,先要解决诸多问题,例如:
客户端依赖:分为 API 依赖和数据依赖两种,比如
window/document
之类的 JS API、设备相关数据信息(屏幕宽高、字体大小等)生命周期差异:例如 React 中,
componentDidMount
在服务端不执行异步操作不执行:服务端组件渲染过程是同步的,
setTimeout
、Promise
之类的都等不了依赖库的适配:React、Redux、Dva 等等,甚至还有第三方库等不确定能否跑在 universal 环境,是否需要跨环境共享状态,以状态管理层为例,SSR 要求其 store 必须是可序列化的
两边共享状态:每一份需要共享的状态都要考虑(服务端)如何传递、(客户端)如何接收
首先,low-code 模式不同于源码开发,现有的 CSR 代码无法直接迁移到 low-code 平台上来,其次,low-code 配置化的开发模式提供了天然的细粒度逻辑拆分和完整的精细控制力,体现在:
细粒度逻辑拆分:各个生命周期函数独立配置
完整的精细控制力:依赖库、生命周期、异步操作、共享状态严格受控,low-code 平台全权控制所填代码的编译时、运行时环境
客户端依赖虽无法消除,但能够像函数式编程中的副作用一样管控起来,比如将其约束到特定的生命周期函数(componentDidMount
)中,使之仅在客户端执行,避免影响服务端。生命周期的差异可通过 low-code 平台让用户产生强感知,比如在编辑、预览等环节强化差异。对于不支持的异步操作,可在编辑阶段进行校验并提示。至于依赖库和状态共享方式,low-code 平台能够全权控制,将其约束到支持范围内
总之,low-code 轻松解决了源码开发模式下棘手的如何约束写法、如何管控不确定性的问题
难题 3:配套设施的建设
SSR 最核心的部分是渲染服务,但除此之外还要考虑:
本地开发套件(校验 + 构建 + 预览/HMR + 调试)
发布流程(版本管理)
一整套的工程设施,在 SSR 模式下都需要重新考虑
这些配套设施是 SSR 要解决的问题,low-code 也面临同样的问题,因此,SSR 能够在一定程度上复用 low-code 提供的在线研发链路支持,只对其部分环节进行扩展,降低配套设施建设的成本
难题 5:hydration 的性能损耗
组件作为一层抽象,在提供模块化开发、组件复用等工程价值的同时,也带来了一些问题。典型的,交互逻辑与组件渲染机制绑定在了一起,这是 SSR 需要 hydration 的根本原因:
客户端接到 SSR 响应之后,为了支持(基于 JavaScript 的)交互功能,仍然需要创建出组件树,与 SSR 渲染的 HTML 关联起来,并绑定相关的 DOM 事件,让页面变得可交互,这个过程称为 hydration
也就是说,只要仍然依赖组件这层抽象,hydration 的性能损耗就无可避免。在源码开发模式下,组件无可替代,因为没有与之等价的抽象描述形式。然而,在 low-code 模式下,其输出产物(配置数据)也是一种抽象描述形式,如果能够具有与组件同等的表达力,就完全有可能去掉组件这层抽象,不必再背负 hydration 的性能损耗
另一方面,对于无交互(纯静态展示)、弱交互(静态展示带埋点/跳转)的偏静态场景,low-code 平台也能准确地识别出来,避免不必要的 hydration
难题 6:数据请求
服务端同步渲染要求先发请求,拿到数据后才开始渲染组件,那么面临 3 个问题:
数据依赖要从业务组件中剥离出来
缺失客户端公参(包括 cookie 等客户端会默认带上的 header 信息)
两边数据协议不同:服务端可能有更高效的通信方式,比如 RPC
low-code 开发模式下,数据依赖以配置化的形式录入,天然剥离,客户端公参、数据协议等均可通过 low-code 平台来配置,比如配 HTTP、RPC 两套协议,按环境自动选用
第三大机遇:4G/5G 网络环境
移动时代早期,离线 H5 是业界最佳实践,因为在线页面意味着秒级的加载时间,离线页面有着巨大的加载速度优势
但随着网络环境的发展,离线页面的加载速度优势已经不再是决定性因素(小程序的大爆发足以说明问题),在线页面的动态化特性备受关注,(SSR 无能为力的)离线场景越来越少,SSR 的用武之地越来越多
FaaS/Serverless比直接使用EC2类的要更贵. https://einaregilsson.com/serverless-15-percent-slower-and-eight-times-more-expensive/ 这篇博文有提供一些价格参考.