- 如何提升你的云函数性能
在使用云开发一段时间后,你一定会遇见一个问题:虽然云函数非常的方便,但我的云函数似乎性能不够好,为什么我的云函数每次加载都需 2 ~ 3 秒种,时间太长了!。 这篇文章,就来告诉你,应该如何提升你的云函数性能。 如何了解云函数运行情况? 在了解如何优化云函数的运行情况之前, 我们需要先了解,如何查看当前的云函数运行情况,这样才能有个对比。 [图片] 打开小程序开发者工具,并打开你的项目 进入到你要调试的页面,打开调试器 调用云函数,并在调试器中切换到 Network 页面,找到你的请求。 点击你的请求,然后切换到 Timing 页面,查看具体的情况。 在这个页面中,你可以理解其中的 Waiting(TTFB) 是你发起请求到你接收到返回结果的第一个字节的时间,简单的来说,就是服务器计算结果需要花费的时间。而下方的 Content Download 则是下载内容所需的时间,你可以理解是表现出网络速度快慢的数据。 总结来说,就是如果 Waiting TTFB 的值比较大,你就去优化云函数性能。如果 Content Download 的数值毕竟大,你就需要优化网络情况 优化 Waiting TTFB 云函数的运行机制 Waiting TTFB 的优化是云函数性能端的优化,那么在优化之前,我们就需要先来了解一下云函数的运行机制,以便帮助我们了解应该如何去进行性能优化。 [图片] 在蕴含运行时,具体的顺序是这样的 用户发起请求,请求发送到云开发的后台 云开发后台的调度器将请求分发给下方的执行的 worker 、容器 容器创建环境、下载代码 执行代码 在这个过程中,发起请求到云开发、调度器调度速度、调度器传递信息到容器、函数调用等,都是可以优化的,但是我们在具体的使用过程中。这些大都需要由云开发的工作人员来完成,对于我们自己来说,只能去尽可能的优化容器内部到代码层面的东西。 接下来,我们可以看看更细致的调用逻辑。 [图片] 在云开发中,我们可以将调用分为三种类型: 冷启动:图中的红色阶段,需要重新创建容器、下载代码,耗时最长 温启动:图中的黄色阶段,需要下载代码,耗时较长 热启动:图中的蓝色阶段,不需要下载代码,耗时最短 我们可以看到,最快的,是热启动,函数不需要创建容器,不需要启动函数就可以完成执行,显然比要创建容器或要下载代码的温启动和冷启动速度更快。这样,我们就得到了优化云函数性能的第一个方法 1. 让你的云函数每次调用都走热启动 当我们可以让我们的云函数的每一次调用都走热启动,少了容器的创建和函数的部署,请求的速度理所当然的要比冷启动和温启动更快。 我们可以测试一下,我设置每秒调用一次云函数,看看 TTFB 的变化。 [代码]setInterval(()=>{wx.cloud.callFunction({name:'profile'})},1000) [代码] 函数内代码是默认创建的云函数代码。 则对应的执行效果如下 [图片] 可以看到,函数的执行时间从第一次的 1.2s 降低到了 200ms左右,性能提升了 80%,我们仅仅是简单的提升了函数的调用频次,就可以实现提升函数的调用性能,这就是热启动带给我们的价值。 实施方案 如果你需要足够高的性能,不妨借助云开发的定时器,定期唤起你的容器,从而为你的容器保活,确保你的函数时刻被热启动。 2. 缩小你的函数大小 在前面我们曾介绍过,云函数在启动过程中,会创建容器和下载代码。创建容器的过程对于开发者来说不可控,不过我们可以使用一些方法,缩小我们的代码,提升代码的下载速度,比如说,缩小我们的函数代码。 这里我们可以做个测试,这里我创建了两个函数,两个函数的代码完全一致,不同的是,在实验组的函数中,我加入了一个 temp 变量的声明,这个变量的值是一个非常长的字符串,从而使得两个函数的大小分别是 68K 和 4K。 接下来,我们看看二者的执行时间。 [图片] 我们会发现,几乎没有差距的代码,因为加入了变量声明的因素,在性能上会略慢几毫秒,后续随着容器的不断复用,函数的之间的差距也越来越小,几乎可以忽视。 实施方案 对于你的代码,要尽可能的精炼,减少无用的代码,减少代码下载所需时间。 3. 削减不需要的 Package 除了下载代码以外,还需要下载 Node 环境运行所需的依赖包,虽然云开发可能针对 Node Modules 已经做了缓存,但依然存在下载的时间差区别,这里我也做了一个实验。 空包:什么都没装,把 wx-server-sdk 都卸载掉了。 复杂包:装了 Mongoose、sequelize、sails 等依赖的包。 函数逻辑上也相差无几,都是返回 Event ,则结果如下 [图片] 我们发现,前三次可能是因为涉及到依赖包的下载问题,所以前三次的时长大小对比特别的明显,而从第四次开始,二者的区别就不大了,可能是因为依赖已经完成了缓存,所以可以直接使用缓存来完成函数的执行。 实施方案 你可以选择看看你的 package.json ,看看其中是否有你不需要的依赖,将其删除,仅保留有需要的依赖,可以有效提升你的代码执行速度。 优化 Content Download 如果你想要优化 Content Download ,核心需要优化的是两个点: 手机到服务端的节点的距离和速度 内容的大小 前者一般来说,你可以通过切换不同的网络环境来实现优化,比如从 3G 切换到 4G ,从 4G 升级到 5G,这些都可以提升你的手机到服务端节点之间的速度。 此外,还可以借助内容分发网络 CDN 能力来完成缩小你到服务端节点之间的距离,不过对于云函数来说,因为你不可控,无法控制,所以这一点不再谈。 这里补充一句,云开发的文件存储都是有 CDN 的,因此,你通过云存储下载的文件才会比别人更快。 后者则一般通过调整代码来完成,比如只返回必须的资源,对于不需要的内容,不再返回,或压缩返回。 总结 最后,我们回顾一下这篇文章中介绍的优化云函数的方法: 函数下载性能优化 保持函数容器的热启动,提升函数启动性能 缩小函数大小,提升代码下载速度 削减不必要的包,减少依赖大小 网络优化 使用更好的网络,比如 Wi-Fi 云函数中仅返回所需要的内容,减少下载时间。 以上这些方法,你都在你的函数中试过么?有没有其他的优化方法?欢迎你与我分享。
2019-12-08 - 云开发云函数定时触发器讲解
任何可以产生事件,触发云函数执行的均可以被称为触发器,而定时触发器则是可以处理周期性的事情,比如时报、日报、周报等通知提醒,也可以处理倒计时任务,比如节假日、纪念日以及你可以指定一个具体时间的倒计时任务,除此之外,定时触发器还可以用来周期性处理一些定时任务。比如定期清理一些不必要的数据,定期更新集合内的数据。 13.5.1 定时触发器使用说明1、定时触发器的配置与部署配置了定时触发器的云函数,会在相应时间点被自动触发,云函数的返回结果不会返回给调用方。在对某个云函数使用定时触发器前,首先要保证该云函数在小程序端可以调用成功,更准确的说是能够在不传入参数的情况下在云开发控制台的云端测试能调试成功(小程序端调用有登录态)。 云函数目录里的 config.json 文件可以用来配置权限和定时触发器,如果你的云函数目录下面没有这个配置文件,可以自己创建一个,创建的结构目录如下: test //云函数目录 ├── config.json //权限和定时触发器等的配置文件 ├── index.js //云函数 ├── package.json //云函数的依赖管理 然后再来在配置文件 config.json 里进行类似如何格式的配置,config.json 严格遵循配置文件所要求的格式,比如数组最后一项不能有逗号[代码],[代码];配置文件里不能有注释等 triggers 字段是触发器数组,但是目前云函数只支持一个触发器,即数组只能填写一个,不可添加多个;name 是触发器的名字,最大支持 60 个字符,支持 a-z, A-Z, 0-9, - 和 _,必须以字母开头;type 为触发器类型,timer 是定时触发器config 是触发器的定时配置,里面为 cron 表达式(后面有介绍),cron 有七个必需字段,不能多也不能少(以下为每天早上 9 点到 12 点每隔 5 秒触发一次);{ "triggers": [ { "name": "tomylove", "type": "timer", "config": "*/5 * 9-12 * * * *" } ] } 当我们在修改触发器配置文件 config.json 后,首先鼠标右键 config.json 选择“云函数增量上传:更新文件”,然后再右键 config.json 选择“上传触发器”。这里的“云函数增量上传:更新文件”是让云函数端的触发器文件更新;而“上传触发器”则是让触发器开始生效执行。如果在云函数端的触发器没有更新的情况下就“上传触发器”来执行定时触发,文件可能没有更新,执行的还是旧的触发器内容。当我们想暂停或删除触发器时,可以右键选择“删除触发器”。 2、Cron 表达式语法Cron 表达式有七个必填字段,按空格分隔,既不能多写也不能少写,每一个字段都有它的含义对应着不同的时间点,表达式的取值都为整数且为时间制的范围(注意月在星期的前面): 第一位第二位第三位第四位第五位第六位第七位秒(0-59 )分钟(0-59)小时(0-23)日(1-31)月(1-12或三个字母的英文缩写)星期(0-6或三个字母的英文缩写)年(1970~2099 ) 下面是 cron 表达式的案例,以及我们需要了解一下 cron 表达式里的通配符以及直接写数字的含义: [代码],[代码],表示并集,在时间的表述里是“和”的意思,比如在“小时”字段中, [代码]1,2,3[代码]表示 1 点、2 点和 3 点;[代码]-[代码],指定范围的所有值,在时间的表述里是“到”的意思,比如在“日”字段中,[代码]1-15[代码]包含指定月份的 1 号到 15 号;[代码]*[代码],表示所有值,在时间的表述里是“每”的意思,比如在“小时”字段中,[代码]*[代码]表示每小时;[代码]/[代码],指定步长,在时间的表述里是“隔”的意思,比如在“秒”字段中,[代码]*/5[代码]表示每隔 5 秒;直接写数字,在时间的表述里是“第”(时间点)的意思,比如在“月”字段中,[代码]5[代码]表示每月的第 5 日;//表示每隔5秒触发一次, */5 * * * * * * //表示在每月的1日的凌晨2点触发 0 0 2 1 * * * //表示在周一到周五每天上午10:15触发 0 15 10 * * MON-FRI * //表示在每天上午10点,下午2点,4点触发 0 0 10,14,16 * * * * //表示在每天上午9点到下午5点内每半小时触发 0 */30 9-17 * * * * //表示在每个星期三中午12点触发 0 0 12 * * WED * 定时触发器的 Cron 语法没法实现每隔 90 秒钟或 90 分钟发送一次这样的效果,因为 90 秒超过了秒的时间制上限 60,而 cron 在跨位组合(比如 90 秒需要结合秒和分)上无法覆盖所有的时间;除此之外,云开发的触发器暂时不支持多个定时触发器的叠加;在 Cron 表达式中的“日”和“星期”字段同时指定值时,两者为“或”的关系,即两者的条件均生效;值得一提的是,尽管云函数的时区为 UTC+0 时区,但是定时触发器的时间还是北京时间。 13.5.2 用定时触发器调用云函数定时触发器的使用非常简单,使用开发者工具新建一个云函数比如 trigger,然后在 index.js 里输入以下代码: const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); exports.main = async (event, context) => { console.log(event); return event; }; 再在 trigger 云函数目录下的 config.json(如果没有这个文件,就创建一个),然后输入以下触发器,为了调试方便,我们可以每隔 5 秒触发一次: { "permissions": { "openapi": [ ] }, "triggers": [ { "name": "tomylove", "type": "timer", "config": "*/5 * * * * * *" } ] } 然后分别右键 index.js 和 config.json,选择“云函数增量上传:更新文件”,然后再来右键 config.json 选择“上传触发器”。云函数就会每隔 5 秒自动触发,相关的日志我们可以在开发者工具的云开发控制台以及腾讯云云开发网页控制台的云函数的日志里查看。 注意小程序端调用 trigger 云函数返回的 event 对象,和使用定时触发器返回的 event 对象的不同,用定时触发器触发云函数是获取不到 openId 的,同时这里有一个 Time 时间是时区为 UTC+0 的时间,比北京时间晚 8 个小时: //在小程序端调用trigger云函数之后返回的event对象 { "userInfo":{ "appId":"wxda99******7046", "openId":"oUL-m5F******buEDsn8" } } //使用定时触发器触发云函数之后返回的event对象 { "Message":"", "Time":"2020-06-11T11:43:35Z", "TriggerName":"tomylove", "Type":"timer", "userInfo":{ "appId":"wxda99********46" } } 13.5.3、定时触发器的应用定时触发器的应用非常广泛,以下仅举一些常用案例,并加以说明: 1、结合消息推送这里的消息推送不仅仅只是指订阅消息,还可以是统一服务消息、公众号的消息(可以用云函数开发微信公众号)、小程序内自己开发的通知(只是用户只有在打开小程序时才能看到)、Email 邮件等等。 比如用户订阅了日报、周报、月报等周期性的通知提醒或者我们需要给用户发送一些汇总信息,就可以固定写一个定时触发器,比如我们需要给指定用户发送工作周报,每周五晚上 17 点 30 分就定时从数据库获取数据发送消息,cron 表达式写法如下: * 30 17 * * FRI * 还可以用来处理一些倒计时(指定时间点)的任务,比如节假日、纪念日以及一些活动时间节点(定时触发器目前只能一个云函数配一个触发器,但是可以提前管理),比如我们希望在六一儿童节的早上 9 点调用云函数给指定用户群体发送消息: 0 0 9 1 6 * * 当然这样的具体时间点显得过于的不灵活,但是如果把时间与云开发数据库结合起来,灵活性就会大很多,比如在运营上每天早上 11 点是你们用户访问最多的时间点,你只需要写一个云函数,把所有的活动都在这个时间点来推送,让定时触发器每天这个时间点都触发,有活动(数据库里有数据)就会发消息,如果没有就不发(云函数调用一次的成本极低)。 如果是实时数据,我们还可以把定时触发器的频率调高,每 5 秒就触发一次,比如我们的数据库只要有最新的数据,就会发消息给指定用户。尽管不是完全的实时,但是 5 秒的频率和实时的差别也就不大了。你也可以根据情况,来调整触发器的频率,毕竟 5 秒和 1 分钟的频率给用户的体验差异并没有太大,但是成本却是 12 倍的关系。 可能你还希望在指定的时间段才触发云函数,比如你只希望在工作日、或者在早上 9 点到晚上 18 点才触发,在指定的时间段才触发既可以让触发更精准不扰民,也可以节约成本,比如下面的触发器就是工作日早上 9 点到 12 点和下午 14 点到 18 点这个时间段,每 5 秒触发一次。 */5 * 9-12,14-18 * MON,TUE,WED,THU,FRI * 从以上案例我们可以了解到,云函数的定时触发可以来自于 cron 表达式的配置,我们可以指定时间点时间段和频率来达到我们想要的效果,同时这个时间“也可以来自于数据库的配置”(伪装),意思是我们可以设置触发器的时间段或频率,如果数据库里有数据就发送,没有数据就不发送,这样就可以达到触发器在时间上的灵活性了。 2、实时获取数据有的时候我们的数据并不是来自于数据库,而是来自于第三方服务,比如前面介绍过的历史上的今天的 API,天气的 API,知乎日报的 API 等等,以及一些 webhook,这些 API 和第三方服务提供的是 json 格式的文件,API 的数据也会随时更新,但是它们更新了却并不会主动通知我们,这时我们可以使用定时触发器向这些 API 发起请求,如果数据出现更新,我们就可以将更新的数据存储到我们的数据库或者进行其他处理,比如企业微信的机器人等机器人通知服务就是如此。 当然定期获取的数据还可以是爬虫,比如我们可以定期抓取指定关键词的新闻或者指定网站的动态,当爬虫获取到了不同的数据的时候,就将最新的动态以机器人消息或者其他方式进行及时的处理。 也就是说,我们无法实时监听到第三方 API 或者网站数据的变动,但是可以用定时触发器来发起请求或者爬虫抓取数据,通过数据的变化来达到“实时”获取数据的目的。 3、自动化处理在数据库的设计里,我们就提到有时候需要对数据库里的数据进行定期的备份与删除等清理维护工作,比如超过一定时间的日志,具有很强时效性的活动数据,以及为了性能考虑而做的虚假删除(数据库性能与优化有介绍)等,毕竟数据库有一定的存储成本而且过多无用数据也会影响数据库的性能,我们可以写一个云函数用定时触发器来执行此类任务。 我们还可以在用户并发比较少的时间段(比如凌晨几点)来处理一些比较耗云函数、数据库性能的任务,比如图片的审核与裁剪、缩略等处理,用户评论是否包含敏感词汇(尽管经过安全处理,但是有时候我们还会设置特别的敏感词),数据的汇总,云存储里废弃文件的删除,用户信息是否完整等等。 也就是说,结合定时触发器,我们可以实现一些任务的自动化处理。 4、密集型任务分流我们知道云函数在处理一些复杂性的任务时是有一些限制的,一是执行时间的限制,建议在设置时执行时间一般不要超过 20s,最长不要超过 60s;二是并发的限制,云函数最大的并发为 1000;三是云函数在查询数据库时一次可以获取最多 1000 条的数据,面对这三个限制,我们应该如何处理密集型的任务呢,比如发送 100 万封邮件,导出几百万条数据到 Excel,发送十万级的订阅消息或消息等等,这个时候就可以使用到定时触发器来处理了。 借助于定时触发器,我们可以将需要耗时较长、对并发要求较高以及数据库请求等的任务进行分批处理,比如我们要给 100 万人发邮件:云函数发起数据库请求,一次只请求 1000 条未发送过邮件的用户(用 where 条件查询某个字段,比如[代码]status:false[代码]),然后将邮件发给 1000 个人(可以参考前面的邮件发送),发完邮件并对这 1000 条数据进行标记(比如使用更新指令将 status 改为 true),这样下次查询未发送过邮件的用户时,就不会重复发送了。通过定时触发器,每 2 秒执行一次发送任务,几十分钟就可以处理完任务。
2021-09-10