云原生微信小程序开发实战读书笔记-9

云开发是云原生一体化的应用开发平台,主要为产品应用提供完备的后端服务。与传统的后端服务一样,云开发的云函数为后端业务处理提供了计算能力,开发者只需要提供业务函数有关的代码文件,当有请求时,云函数会自动将这些文件装载到一个计算资源容器中,按照开发者编写的规则进行处理,最终返回结果给请求方。下面是一个简单的云函数例子:

exports.main = async (event, context) => {
	return '这是来自云开发的消息'
}

例子中,云函数执行 exports.main 函数,接收的参数 event 是请求方发送过来的,与业务处理有关的必要信息(请求方自定义),函数主体直接 return 一串字符串,返回请求方一句话。

在整个过程中,开发者只需要关心业务相关的代码文件(也就是上述实例的相关业务代码),不用关心怎么接收请求,装载实例等。

而且,云函数使用的是事件触发模型,请求方每一次调用云函数,就触发了一次云函数调用事件,云函数平台就会新建或复用已有的云函数实例来处理这次调用。这与传统的后端服务有很大不同,因为云开发云函数是以实例来运行的,也就是说,一个云函数与其他云函数之间是独立运行的,云函数之间无法互相获取信息,甚至同一个云函数在不同时间、不同请求方调用执行时,也无法与其他存在的同一云函数实例通信,它们之间只能通过统一的存储空间进行配合,比如数据存入数据库、云存储中。

image (5).png

可是我们在开发一个完整的产品应用时,会让多个云函数同时存在并独立运作,以支撑应用运行时各种业务请求。比如,一个简单的应用登录页面,就需要登录、注册、忘记密码三个云函数进行支持。但云函数相对独立的特性,会让很多开发者在写业务逻辑时,无意将云函数写得特别臃肿,最终降低云函数的执行效率,从而增加产品操作的延迟、后期维护的负担。

所以为了提高云函数执行效率,让后期维护变得有效,我们推崇 KISS 原则,也就是 Keep It Simple & Stupid。

云函数的运行机制

在云函数被请求方调用而开始运行时,具体的顺序如下:

  • 用户发起请求,请求发送到云开发的后台;
  • 云开发后台的调度器将请求分发给下方的执行的 Worker 、容器;
  • 容器创建环境、下载代码;
  • 执行代码;
  • 返回执行结果。

其中 1~3 步都是由云开发团队负责优化的,我们只需要在第 4 步进行优化就可以了,这样一来,我们对整个云函数的优化就变成了对函数代码的优化。从流程上看的确如此,我们再横向看一下。

云函数会根据角色的不同,被请求的频次也不一样,比如页面刷新的云函数 A 和提交数据的云函数 B,从业务角度来讲,肯定是 A 请求频次远远高于 B 的。那么对于不同请求频次的云函数,云开发的调度系统会有什么具体的表现呢?我们来看一下更细致的运行原理。image (6).png

在云开发云函数中,我们可以按照路径把云函数分为三种类型。

  • 冷启动:这类云函数通常是很长时间没有请求,或者刚刚创建完毕,根本不存在运行实例,所以这类云函数在请求时就要先创建容器,再下载代码部署,最后才能执行。耗时最长。
  • 温启动:这类云函数可能有一段时间没有请求,或者是开发者更新了代码。在执行时直接下载部署代码就可以运行了。耗时中等。
  • 热启动:这类云函数一直处于活跃状态,请求来临时不需要创建容器和下载代码,就可以直接执行,耗时最短。

由此可见,当函数不需要创建容器、下载代码、直接请求函数就可以完成执行,显然比要创建容器或要下载代码的温启动和冷启动速度更快,所以你要想提升云函数的运作效率,就要想办法将云函数始终保持热启动状态。

让云函数保持热启动状态

让云函数保持热启动状态,就需要保持云函数的活跃性,也就是始终有频繁的调用请求。我们尝试让云函数的每一次调用都走热启动(设置每秒调用一次云函数),看看云函数执行耗时的变化。image (7).png

从图中可以看到,函数的执行时间从第一次(冷启动)的 1.2s 降低到了 200ms左右,性能提升了 80%,我们仅仅提升了函数的调用频次(由十几分钟 1 次提升到 1 秒 1 次),就可以提升函数的调用性能,这就是热启动带给我们的价值。

所以,如果你希望业务应用中,后端服务的云函数需要保持较高的响应速度,以支撑一些特殊的业务场景(抽奖、抢购),则可以使用此方法(预热云函数使其热启动)来提升云函数响应速度。具体操作是:借助云函数提供的定时触发器,定期唤起你的容器,从而为你的云函数容器保活,确保函数时刻被热启动。

缩小云函数文件的大小

在下载部署速度相同的情况下,代码的体积越大,所耗费的时间就越长。所以你可以通过缩小代码来提升代码的下载部署速度,进而整体提升云函数的执行速度。来做个测试: 创建两个函数,两个函数的代码完全一致,不同的是,在实验组的函数中,加入了一个 temp 变量的声明,这个变量的值是一个非常长的字符串,会让两个函数的大小分别是 68K 和 4K。

接下来,我们看看二者的执行时间。

image (8).png

我们发现,在前几次执行中,大体积函数运行时间要大于小体积函数,也就是说在性能上会略慢几毫秒,而后续不断重复调用,在多次执行之后,云函数变成热启动状态,所以并没有下载部署代码这个步骤了,函数之间的差距也越来越小,几乎可以忽视。

由此我们可以得出结论:函数文件的大小,从一定程度上决定了云函数的执行速度。

所以,在编写云函数过程中,你要尽量避免出现无意义的冗余代码,有代码注释也尽量将注释去掉,压缩函数体积。这个原理和浏览器加载 js 等资源文件的情况特别相似,都是通过减少文件体积来降低加载的时间从而提升性能。

削减不需要的 Package

除了缩减业务代码之外,通常为了业务代码编写而引入的各种 npm 包也是需要重点改进的对象。很多开发者都会使用 npm 包提升开发速度和开发便捷性。云开发云函数虽然也会针对 Node Modules 做一些缓存机制的优化,但依然存在下载时间差。我们来做一个实验:一个云函数中没有安装任何依赖,另外一个云函数安装了很多依赖,两者逻辑都一样,都是直接返回同一个字符串。

实验结果如下:

image (9).png

我们发现,前三次是因为涉及依赖包的下载问题,所以时长大小对比特别明显,从第四次开始,二者的区别就不大了,云函数中因为依赖已经完成了缓存,所以可以直接使用缓存来完成函数的执行。

由此可见,在云函数中只保留必要的依赖,去掉无用的依赖,在使用依赖时,尽可能选择够用且体积小的依赖,以简化云函数的整体体积。

如果你想实质性地降低云函数的执行时间,必须改造云函数代码逻辑,因为代码逻辑本身决定了云函数的执行路径,也决定了云函数的执行时间长短。

KISS

在设计云函数时,要遵循简单的原则,也就是一个函数尽量只解决一个关键的问题。比如商品的添加、删除功能,尽可能用两个独立的云函数实现相应的功能,而不要用一个云函数直接构建两个功能。这样的好处是:

  • 便于定位和调试问题;
  • 在一定程度上减少了中间的判断逻辑,节约了执行时间。

总的来说,你要根据自身的业务设计云函数,你可以遵循以下 3 个原则。

  • 云函数的执行流程尽量减少分支数量:过多的判断导致大量的分支流程,会使云函数代码冗余,因为每次执行只使用了其中一个分支,其他的分支被浪费了。
  • 云函数的代码尽可能采用最简单的设计:可以通过合理使用一些设计原则(比如开闭原则、接口隔离原则等),将复杂的代码逻辑变得简单,高效处理才可以有效降低时间成本。
  • 对于可以同步的流程,尽量同步来触发启动:如果云函数中的一些流程无前后因果关系,比如请求操作和数据库操作并没有前后关系,就可以使用 Promise.all 来并发请求。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!