当前位置: 首页 > 后端技术 > Node.js

基于Node.js的轻量级云函数实现

时间:2023-04-03 14:40:17 Node.js

简介在云时代,你的应用甚至不需要服务器。各大云服务都提供了云函数功能,那么用“万能”的node.js如何实现呢?1、什么是云函数?云功能是云服务中诞生的一个新名词。顾名思义,云函数就是在云端(即服务器端)执行的函数。每个云函数相互独立,简单单一,执行环境相互隔离。在使用云函数时,开发者只需要关注业务代码本身,其他的如环境变量、计算资源等都由云服务提供。2、为什么需要云函数?程序员说不想买服务器,就有了云服务;程序员说他连服务器都不想写了,于是就有了云功能。Serverless架构通常我们的应用都会有一个后台程序,负责处理各种请求和业务逻辑,一般需要处理网络、数据库等I/O。所谓Serverless架构,就是把业务代码以外的一切都交给执行环境。开发者不需要知道服务器是如何运行的,如何调用数据库的API——一切都交给了外部,在“温室”里写代码就行了。FaaS和云函数是Serverless架构的实现方式。我们的应用程序将由独立的功能组成,每个功能都是一个小粒度的业务逻辑单元。没有服务器,没有服务器程序,“功能即服务”。3、如何实现?由于这个实现是在CLI工具中应用的,函数声明在开发者的工程文件中,所以大致流程如下:1.函数声明和存储声明我们的目标是让云函数的声明和普通js一样functions:module.exports=asyncfunction(ctx){return'hahha'}};由于云函数的执行通常伴随着接口的调用,所以应该可以支持http方法的声明:module.exports={method:'POST',handler:asyncfunction(ctx){return'哈哈'}};storage因为有method等配置,编译时需要需要上面的声明文件。此时的handler字段是一个Function类型的对象。可以调用它的toString方法获取字符串类型的函数体:constf=require('./func.js');constmethod=f.method;constbody=f.handler.toString();//asyncfunction(ctx){//return'hahha'//}有了字符串的函数体,存储就很简单了,直接存储在数据库中字符串类型的字段即可。2.如果函数执行url用于前端调用,每个云函数都需要有对应的url。如果上述声明文件的文件名是云函数的唯一名称,则url可以简单设计为:/f/:funcnamestructure独立作用域(强调)在js世界中,执行一个函数有以下几种方式string-typefunctionbody:evalfunctionnewFunctionvmmodule那么你应该选择哪一个呢?再回顾一下云函数的特点:相互独立,互不影响,运行在云端。关键是在不访问执行环境的情况下在独立范围内执行每个云功能。所以最好的选择是nodejs的vm模块。该模块的使用请参考官方文档。至此,云函数的执行可以分为三步:从数据库中获取函数体构造上下文//ctx为koa的上下文对象constsandbox={ctx:{params:ctx.params,query:ctx.query,正文:ctx。request.body,userid:ctx.userid,},promise:null,console:console}vm.createContext(sandbox);执行函数得到结果constcode=`func=${funcBody};promise=func(ctx);`;vm.runInContext(code,sandbox);constdata=awaitsandbox.promise;NPM社区的vm2模块改进了vm模块的一些安全漏洞。这个模块也可以用,思路大致一样。3.引用虽然原则上云函数之间应该相互独立,互不欠账,但为了提高灵活性,我们还是决定支持函数之间的相互引用,即一个云函数可以调用另一个云函数。声明很简单,只需要添加一个函数名的数组字段:}};注入也很简单,根据依赖链找出所有的函数,全部挂载到ctx下,深度优先或者广度优先都可以。如果(func.use){constfuncs={};constfnames=func.use;for(leti=0;i`${fname}:${funcs[fname]}`).join('\n')}}`;code=`ctx.methods=${funcCode};${code}`;}else{code=`ctx.methods={};${code}`;}//获取所有依赖函数constgetUsedFuncs=async(ctx,funcName,methods)=>{constfunc=getFunc(funcName);方法[funcName]=func.body;如果(func.use){constuses=func.use.split(',');for(leti=0;i{funcMap[f.name]=f;});constchain=[];flist.forEach((f)=>{getUseChain(f,chain);});functiongetUseChain(f,chain){if(chain.includes(f.name)){thrownewError(`函数有循环依赖:${[...chain,f.name].join('→')}`);}else{f.use.forEach((fname)=>{getUseChain(funcMap[fname],[...chain,f.name]);});}}4.性能上面的解决方案中,每次执行云函数,都需要执行几个步骤:获取函数体编译代码构建的作用域,在独立环境下执行第3步,因为每次执行的参数都是不同的,不同的请求会并发执行在同一个函数的情况下,作用域ctx不能重复使用;第4步是必要的,因此只有1和2可以优化。代码缓存vm模块提供了代码编译和执行分开处理的接口,所以每次获取函数体字符串时,先编译成Script对象://...getcodeconstscript=newvm.Script(代码);execute可以直接传入编译好的Script对象://...getsandboxvm.createContext(sandbox);script.runInContext(sandbox);constdata=awaitsandbox.promise;functionbodycache简单缓存,不需要复杂的更新机制,设置一个时间阈值,超过后拉取新的函数体并编译Script对象,然后缓存:constcacheFuncs={};//...getscriptcacheFuncs[funcName]={updateTime:Date.now(),script,};//缓存时间:60秒constcacheFunc=cacheFuncs[cacheKey];if(cacheFunc&&(Date.now()-cacheFunc.updateTime)<=60000){constsandbox={/*...*/}vm.createContext(sandbox);cacheFunc.script.runInContext(沙箱);constdata=awaitsaandbox.promise;returndata;}else{//更新缓存}4.参考资料相关文章什么是无服务器(serverless)架构?业界无服务器腾讯云-ServerlessCloudFunction阿里云-函数计算AWS-LambdaAzure-AzureFunctions