全部产品

Node.js 运行环境

更新时间:2019-06-19 20:52:18

函数计算目前支持 Nodejs 6.10(runtime = nodejs6)和 Nodejs 8.9.0(runtime = nodejs8)运行环境。本文介绍了 Nodejs 运行环境的以下内容:

使用 logger

您的函数通过 console.log 打印的内容会被收集到创建 service 时指定的 Logstore 中,有关函数日志的更多信息,请参考 函数日志

  1. exports.handler = function (event, context, callback) {
  2. console.info(null, 'hello world');
  3. callback(null, 'hello world');
  4. };

上面的代码输出的日志内容是:

  1. message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [INFO] hello world

使用 console.warnconsole.error 分别可以打包 WARN / ERROR 级别的日志。

可以通过接口 setLogLevel 来改变日志级别,其中日志级别从高到低有如下几种

  • error:对应的接口是 console.error
  • warn:对应的接口是 console.warn
  • info:对应的接口是 console.info
  • verbose:对应的接口是 console.log
  • debug:对应的接口是 console.debug
  1. 'use strict';
  2. exports.handler = function(evt, ctx, cb) {
  3. console.setLogLevel("error");
  4. console.error("console error 1");
  5. console.info("console info 1");
  6. console.warn("console warn 1");
  7. console.log("console log 1");
  8. console.setLogLevel("warn");
  9. console.error("console error 2");
  10. console.info("console info 2");
  11. console.warn("console warn 2");
  12. console.log("console log 2");
  13. console.setLogLevel("info");
  14. cb(null, evt);
  15. };

上面的代码输出的日志内容是:

  1. message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [ERROR] console error 1
  2. message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [ERROR] console error 2
  3. message:2017-07-05T05:13:35.920Z a72df088-f738-cee3-e0fe-323ad89118e5 [WARN] console warn 2

使用内置的模块

除了 Nodejs 的标准模块,函数计算的 Nodejs 运行环境中还包含了一些常用模块,您可以直接引用,目前包含的模块有:

模块名称 版本 模块介绍 相关链接
co 4.6.0 控制流 https://github.com/tj/co
gm 1.23.0 图片处理库 https://github.com/aheckmann/gm
ali-oss 4.10.1 OSS SDK https://github.com/ali-sdk/ali-oss
ali-mns 2.6.5 MNS SDK https://github.com/InCar/ali-mns
tablestore 4.2.0 OTS SDK https://github.com/aliyun/aliyun-tablestore-nodejs-sdk
aliyun-sdk 1.10.12 Aliyun SDK https://github.com/aliyun-UED/aliyun-sdk-js
@alicloud/fc2 2.1.0 函数计算SDK https://github.com/aliyun/fc-nodejs-sdk
opencv 6.0.0 视觉算法库 https://github.com/peterbraden/node-opencv
body 5.1.0 http body 解析库 https://github.com/Raynos/body
raw-body 2.3.2 http body 解析库 https://github.com/stream-utils/raw-body

例如使用 gm 进行图片翻转的函数如下:

  1. var gm = require('gm').subClass({ imageMagick: true });
  2. exports.handler = function (event, context, callback) {
  3. gm(event)
  4. .flip()
  5. .toBuffer('PNG', function (err, buffer) {
  6. if (err) return callback(err);
  7. callback(null, buffer);
  8. });
  9. };

注意:上面的函数直接使用 event 作为图片的二进制数据并且直接把生成的图片作为二进制数据返回。

使用自定义模块

如果您需要使用自定义的模块,则需要将它们与代码一起打包。下面通过 Fun 来介绍添加 mysql 模块 mysql 的步骤(Fun 的安装文档请参考):

  1. 建立一个目录,用于存放代码和依赖模块:

    1. mkdir /tmp/code
  2. 新建代码文件,例如 /tmp/code/index.js,在代码中使用 mysql:

    1. var mysql = require('mysql');
    2. var connection;
    3. // exports.initializer: initializer function
    4. exports.initializer = function (context, callback) {
    5. connection = mysql.createConnection({
    6. host: 'localhost',
    7. user: 'me',
    8. password: 'secret',
    9. database: 'my_db'
    10. });
    11. connection.connect();
    12. }
    13. exports.handler = function (event, context, callback) {
    14. connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
    15. if (error) return callback(error);
    16. console.log('The solution is: ', results[0].solution);
    17. callback(null, results[0].solution);
    18. });
    19. connection.end();
    20. };
    • 为什么将连接数据库的操作放入 initializer 中?

      Initializer 保证在函数实例的生命周期内成功执行且只执行一次。建立数据库连接的操作属于应用层面的冷启动开销,为维持此连接在函数实例的生命周期内有效,且不因后续多次请求而重复建立连接带来的冷启动开销,故将此操作放在 initializer 中,避免了应用层冷启动开销影响函数性能。

    • 为什么使用 initializer 比使用 global variable 方式来初始化更好?

      Initializer 可以保证在 invoke 请求到来之前执行成功且在函数实例生命周期内仅执行一次,使用 global variable 方式只保证了后一点,在请求触发新的函数实例/代码升级/FC系统周期性回收或更新容器时仍避免不了冷启动开销的影响。

  3. 在 /tmp/code 目录下安装依赖:

    1. cd /tmp/code
    2. npm install mysql
  4. 新建 template.yml 文件,例如 /tmp/code/template.yml,内容为:

    1. ROSTemplateFormatVersion: '2015-09-01'
    2. Transform: 'Aliyun::Serverless-2018-04-03'
    3. Resources:
    4. FunDemo:
    5. Type: 'Aliyun::Serverless::Service'
    6. nodejsdemo:
    7. Type: 'Aliyun::Serverless::Function'
    8. Properties:
    9. Handler: index.handler
    10. Runtime: nodejs8
    11. Initializer: index.initializer
    12. CodeUri: './'

    这个 template.yml 的含义如下:声明一个名为 FunDemo 的 服务,并在这个服务下,再声明一个名为 nodejsdemo 的 函数,配置函数入口为 index.handler,Initializer 为 index.initializer 以及函数的 runtime 为 nodejs8。并且,我们指定了 CodeUri 为当前目录。在部署时,Fun 会将 CodeUri 指定的目录打包上传。更多的配置规则 请参考

    文件保存后,/tmp/code 目录的内容应该是这样:

    1. ls -l /tmp/code
    2. -rw-r--r-- 1 tan wheel 522B Jun 18 14:50 index.js
    3. drwxr-xr-x 13 tan wheel 416B Jun 18 14:49 node_modules
    4. -rw-r--r-- 1 tan wheel 297B Jun 18 14:58 template.yml
  5. 使用 Fun 部署:

    1. fun deploy

    执行成功时,会看到相关日志:

    1. using region: cn-hangzhou
    2. using accountId: ***********3557
    3. using accessKeyId: ***********r3Ra
    4. using timeout: 300
    5. Waiting for service FunDemo to be deployed...
    6. Waiting for function nodejsdemo to be deployed...
    7. Waiting for packaging function nodejs code...
    8. package function nodejs code done
    9. function nodejsdemo deploy success
    10. service FunDemo deploy success

    登陆控制台,即可看到相关的服务、函数被创建成功,且触发执行可以可以返回正确的结果。

    这里简单介绍下 fun deploy 所做的事情:使用 Fun 部署时,Fun 会根据这个 template.yml 的配置,为我们创建一个名为 FunDemo 的服务,然后再在该服务下创建一个运行环境为 nodejs8、名称为 nodejsdemo 的函数,并且会将当前目录(CodeUri 指定的目录)打包,作为函数的代码上传到函数计算。因为我们打包的代码包含了 node_modules 目录,而我们安装的库都在这个目录中,所以函数计算就可以直接使用这个库了。

调用外部命令

用户的函数可能会用到一些工具,而这些工具并不是用 Nodejs 写的(例如 shell 脚本 / C++ 或者 go 编译出来的可执行文件)。用户仍然可以将它们与代码一起打包,然后在函数中通过运行外部命令的方法来使用它们。下面的例子中演示了如何运行一个 shell 脚本:

  1. var exec = require('child_process');
  2. exports.handler = function(event, context, callback) {
  3. var scriptPath = process.env['FC_FUNC_CODE_PATH'] + '/script.sh';
  4. exec.exec('bash '+scriptPath, {}, function(err, stdout, stderr) {
  5. if (err) return callback(err);
  6. console.log(stdout, stderr);
  7. callback(null, stdout);
  8. });
  9. };

需要注意的是,使用 C / C++ / go 编译出来的可执行文件,需要与函数计算的运行环境兼容。函数计算的 Nodejs 运行环境是:

  • Linux 内核版本:Linux 4.4.24-2.al7.x86_64
  • docker 基础镜像:docker pull node:6.10

理解 callback

Nodejs 采用异步编程的模型,用户函数必须通过调用 callback 来返回数据或者错误。如果函数中没有使用 callback,则会提示函数运行超时。

1. 确保callback被调用

如果在函数中没有调用 callback,则系统将认为函数没有结束,会等待函数结果直到超时。例如下面的函数调用时将收到超时错误:

  1. exports.handler = function(event, context, callback) {
  2. console.log('hello world');
  3. };

调用结果:

  1. {"errorMessage":"Function timed out after 3 seconds"}

2. callback 被调用后则函数结束

callback 被调用后函数就会停止运行,重复调用 callback 只接受第一次调用的结果;需确保所有任务在 callback 调用之前完成,否则它们将不会被运行。例如下面的函数,将返回 “hello world” 并且 “message” 不会 被打印:

  1. exports.handler = function(event, context, callback) {
  2. callback(null, 'hello world');
  3. callback(null, 'done');
  4. setTimeout(function() {
  5. console.log('message');
  6. }, 1000);
  7. };

错误处理

对于 Nodejs 运行环境的函数,用户可能收到两种错误,错误类型记录在返回的 HTTP Header 字段中 (X-Fc-Error-Type):

  1. HandledInvocationError:通过 callback 的第一个参数返回的错误。
  2. UnhandledInvocationError:其他错误,包括未捕获异常、超时错误和OOM(Out of memory)错误等。

    示例1 返回 HandledInvocationError

  1. exports.handler = function(event, context, callback) {
  2. callback(new Error('oops'));
  3. };

调用时收到的响应为:

  1. {
  2. "errorMessage": "oops",
  3. "errorType": "Error",
  4. "stackTrace": [
  5. "at exports.handler (/code/index.js:2:12)"
  6. ]
  7. }

示例2 返回 UnhandledInvocationError

  1. exports.handler = function(event, context, callback) {
  2. throw new Error('oops');
  3. };

调用时收到的响应为:

  1. {"errorMessage":"Process exited unexpectedly before completing request"}

有关函数计算的错误类型,请参考文章 错误类型