导语

在万物皆可云的时代,你的应用甚至不需要服务器。云函数功能在各大云服务中均有提供,那么,如何用“无所不能”的 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 世界里,执行一个字符串类型的函数体,有以下这么一些途径:

  • eval 函数
  • new Function
  • vm 模块

那么要选哪一种呢?让我们回顾云函数的特点:各自独立,互不影响,运行在云端。

关键是将每个云函数放在一个独立的作用域执行,并且没有访问执行环境的权限,因此,最优选择是 nodejs 的 vm 模块。关于该模块的使用,可参考官方文档。

至此,云函数的执行可以分为三步:

  • 从数据库获取函数体
  • 构造 context
// ctx 为 koa 的上下文对象 
const sandbox = {
  ctx: {
   params: ctx.params,
   query: ctx.query,
   body: ctx.request.body,
   userid: ctx.userid,
  },
  promise: null,
  console: console
 }
 vm.createContext(sandbox);

执行函数得到结果

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 code
const script = new vm.Script(code);

执行的时候可以直接传入编译好的 Script 对象:

// ...get sandbox
vm.createContext(sandbox);
script.runInContext(sandbox);
const data = await sandbox.promise;

函数体缓存

简单的缓存,不需要很复杂的更新机制,定一个时间阈值,超过后拉取新的函数体并编译得到 Script 对象,然后缓存起来即可:

const cacheFuncs = {};
// ...get script
cacheFuncs[funcName] = {
  updateTime: Date.now(),
  script,
};

// cache time: 60 sec
const 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
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持Devmax。

详解基于 Node.js 的轻量级云函数功能实现的更多相关文章

  1. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  3. Node.js调试技术总结分享

    Node.js是一个可以快速构建网络服务及应用的平台。该平台的构建是基于Chrome's JavaScript runtime,也就是说,实际上它是对Google V8引擎(应用于Google Chrome浏览器)进行了封装。 今天介绍Node.js调式目前有几种技术,需要的朋友可以参考下。

  4. node.js实现http服务器与浏览器之间的内容缓存操作示例

    这篇文章主要介绍了node.js实现http服务器与浏览器之间的内容缓存操作,结合实例形式分析了node.js http服务器与浏览器之间的内容缓存原理与具体实现技巧,需要的朋友可以参考下

  5. 教你如何使用node.js制作代理服务器

    本文介绍了如何使用node.js制作代理服务器,图文并茂,十分的详细,代码很简洁易懂,这里推荐给大家。

  6. node.js中的fs.openSync方法使用说明

    这篇文章主要介绍了node.js中的fs.openSync方法使用说明,本文介绍了fs.openSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

  7. Node.js+ELK日志规范的实现

    这篇文章主要介绍了Node.js+ELK日志规范的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  8. node.js爬虫框架node-crawler初体验

    这篇文章主要介绍了node.js爬虫框架node-crawler的相关资料,帮助大家利用node.js进行爬虫,感兴趣的朋友可以了解下

  9. node.js中的fs.existsSync方法使用说明

    这篇文章主要介绍了node.js中的fs.existsSync方法使用说明,本文介绍了fs.existsSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

  10. 说说如何利用 Node.js 代理解决跨域问题

    这篇文章主要介绍了Node.js代理解决跨域问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部