时间:2021-05-25
导语
在万物皆可云的时代,你的应用甚至不需要服务器。云函数功能在各大云服务中均有提供,那么,如何用“无所不能”的 node.js 实现呢?
一、什么是云函数?
云函数是诞生于云服务的一个新名词,顾名思义,云函数就是在云端(即服务端)执行的函数。各个云函数相互独立,简单且目的单一,执行环境相互隔离。使用云函数时,开发者只需要关注业务代码本身,其它的诸如环境变量、计算资源等,均由云服务提供。
二、为什么需要云函数?
程序员说不想买服务器,于是便有了云服务;
程序员又说连 server 都不想写了,于是便有了云函数。
Serverless 架构
通常我们的应用,都会有一个后台程序,它负责处理各种请求和业务逻辑,一般都需要跟网络、数据库等 I/O 打交道。而所谓的无服务器架构,就是把除了业务代码外的所有事情,都交给执行环境处理,开发者不需要知道 server 怎么跑起来,数据库的 api 怎么调用——一切交给外部,在“温室”里写代码即可。
FaaS
而云函数,正是 serverless 架构得以实现的途径。我们的应用,将是一个个独立的函数组成,每一个函数里,是一个小粒度的业务逻辑单元。没有服务器,没有 server 程序,“函数即服务”(Functions as a Service)。
三、如何实现?
由于本实现是应用在一个 CLI 工具里面的,函数声明在开发者的项目文件里,因而大致过程如下:
1、函数声明与存储声明
我们的目标是让云函数的声明和一般的 js 函数没什么两样:
module.exports = async function (ctx) { return 'hahha' }};由于云函数的执行通常伴随着接口的调用,所以应该要能支持声明 http 方法:
module.exports = { method: 'POST', handler: async function (ctx) { return 'hahha' }};存储
由于有 method 等配置,因此编译的时候,需要把上述声明文件 require 进来,此时,handler 字段是一个 Function 类型的对象。可以调用其 toString 方法,得到字符串类型的函数体:
const f = require('./func.js');const method = f.method;const body = f.handler.toString();// async function (ctx) {// return 'hahha'// }有了字符串的函数体,存储就很简单了,直接存在数据库 string 类型的字段里即可。
2、函数执行
url
如果用于前端调用,每个云函数需要有一个对应的 url,以上述声明文件的文件名为云函数的唯一名称的话,可以简单将 url 设计为:
/f/:funcname构造独立作用域(重点)
在 js 世界里,执行一个字符串类型的函数体,有以下这么一些途径:
那么要选哪一种呢?让我们回顾云函数的特点:各自独立,互不影响,运行在云端。
关键是将每个云函数放在一个独立的作用域执行,并且没有访问执行环境的权限,因此,最优选择是 nodejs 的 vm 模块。关于该模块的使用,可参考官方文档。
至此,云函数的执行可以分为三步:
执行函数得到结果
const code = `func = ${funcBody}; promise = func(ctx);`;vm.runInContext(code, sandbox);const data = await sandbox.promise;NPM社区的 vm2 模块针对 vm 模块的一些安全缺陷做了改进,也可用此模块,思路大抵相同。
3、引用
虽然说原则上云函数应当互相独立,各不相欠,但是为了提高灵活性,我们还是决定支持函数间的相互引用,即可以在某云函数中调用另外一个云函数。
声明
很简单,加个函数名称的数组字段就好:
module.exports = { method: 'POST', use: ['func1', 'func2'], handler: async function (ctx) { return 'hahha' }};注入
也很简单,根据依赖链把函数都找出来,全部挂载在 ctx 下就好,深度优先或者广度优先都可以。
if (func.use) { const funcs = {}; const fnames = func.use; for (let i = 0; i < fnames.length; i++) { const fname = fnames[i]; await getUsedFuncs(ctx, fname, funcs); } const funcCode = `{ ${Object.keys(funcs).map(fname => `${fname}:${funcs[fname]}`).join('\n')} }`; code = `ctx.methods=${funcCode};$[code]`;} else { code = `ctx.methods={};$[code]`;}// 获取所有依赖的函数const getUsedFuncs = async (ctx, funcName, methods) => { const func = getFunc(funcName); methods[funcName] = func.body; if (func.use) { const uses = func.use.split(','); for (let i = 0; i < uses.length; i++) { await getUsedFuncs(ctx,uses[i], methods); } }}依赖循环
既然可以相互依赖,那必然会可能出现 a→b→c→a 这种循环的依赖情况,所以需要在开发者提交云函数的时候,检测依赖循环。
检测的思路也很简单,在遍历依赖链的过程中,每一个单独的链条都记录下来,如果发现当前遍历到的函数在链条里出现过,则发生循环。
const funcMap = {};flist.forEach((f) => { funcMap[f.name] = f;});const chain = [];flist.forEach((f) => { getUseChain(f, chain);});function getUseChain(f, chain) { if (chain.includes(f.name)) { throw new Error(`函数发生循环依赖:${[...chain, f.name].join('→')}`); } else { f.use.forEach((fname) => { getUseChain(funcMap[fname], [...chain, f.name]); }); }}4、性能
上述方案中,每次云函数执行的时候,都需要进行一下几步:
步骤3,因为每次执行的参数都不一样,也会有不同请求并发执行同一个函数的情况,所以作用域 ctx 无法复用;步骤4是必须的,那么可优化点就剩下了1和2。
代码缓存
vm 模块提供了代码编译和执行分开处理的接口,因此每次获取到函数体字符串之后,先编译成 Script 对象:
// ...get codeconst script = new vm.Script(code);执行的时候可以直接传入编译好的 Script 对象:
// ...get sandboxvm.createContext(sandbox);script.runInContext(sandbox);const data = await sandbox.promise;函数体缓存
简单的缓存,不需要很复杂的更新机制,定一个时间阈值,超过后拉取新的函数体并编译得到 Script 对象,然后缓存起来即可:
const cacheFuncs = {};// ...get scriptcacheFuncs[funcName] = { updateTime: Date.now(), script,};// cache time: 60 secconst cacheFunc = cacheFuncs[cacheKey];if (cacheFunc && (Date.now() - cacheFunc.updateTime) <= 60000) { const sandbox = { } vm.createContext(sandbox); cacheFunc.script.runInContext(sandbox); const data = await saandbox.promise; return data;} else { // renew cache}以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
最近在做小程序开发时,有用到云函数,而云函数就是用node.js写的,所以其中难免会用到一些node类库。用node类库就必选在电脑上安装node.js环境,并
Linux使用Node.js建立访问静态网页的服务实例详解一、安装node.js运行所需要的环境,:二、创建node目录(/node/www),并在目录下创建n
本文实例讲述了node读写Excel操作。分享给大家供大家参考,具体如下:目支持写Excel的node.js模块:node-xlsx:基于Node.js解析ex
安装express$npminstallexpress--save在node.js中,我们最常用的框架就是expressExpress是一个基于Node.js平
一、简介这是一款基于JS实现的超轻量级桌面版聊天软件。主要适用于私有云项目内部聊天,企业内部管理通讯等功能,主要通讯协议websocket。也支持web网页聊天