最佳实践

基本概念

最近更新时间:2020-06-29 19:58:49

用户在使用云函数(Serverless Cloud Function,SCF)平台支持的语言编写代码时,需要采用一个通用的范式,包含以下核心概念:

执行方法

SCF 平台在调用云函数时,首先会寻找执行方法作为入口,执行用户的代码。此时,用户需以文件名.执行方法名的形式进行设置。
例如,用户设置的执行方法为 index.handler,则 SCF 平台会首先寻找代码程序包中的 index 文件,并找到该文件中的 handler 方法开始执行。
在编写执行方法时,用户需遵循平台特定的编程模型。如下所示:

def method_name(event,context): 
    ...
    return some_value

该模型中指定固定的 event 事件数据和 context 环境数据作为入参。在执行方法中,用户需对参数进行处理,并且可任意调用代码中的任何其他方法。

命名规范

开发语言 规范及示例 通用规范
Node.js
Python
PHP
两段式格式为 [文件名].[函数名],例如 index.main_handler 只能包含字母、数字、下划线、连字符,支持以字母开头且需以数字或字母结尾,长度限制为2 - 60个字符。
Java 三段式格式为 [package].[class]::[method],例如 example.Hello::mainHandler
Go 一段式格式为 [文件名],例如 main

函数入参

函数入参,是指函数在被触发调用时所传递给函数的内容。通常情况下,函数入参包括 event 入参context 入参两部分,但根据开发语言和环境的不同,入参个数可能有所不同,详情请参见 开发语言说明

入参类型 作用 使用说明
event 入参 参数类型:dict
将 event 入参传递给执行方法,实现代码与触发函数的事件(event)交互。
例如,由于文件上传触发了函数运行,代码可从 event 参数中获取该文件所有信息,包括文件名、下载路径、文件类型、大小等。
针对不同的函数情况,event 参数的值有以下区别:
  • 云服务触发函数时,云服务会将事件以一种平台预定义的、不可更改的格式作为 event 参数传给 SCF 函数,您可以根据此格式编写代码并从 event 参数中获取信息。
    例如,COS 触发函数时会将 Bucket 及文件的具体信息以 JSON 格式 传递给 event 参数。
  • 云函数被其他应用程序调用时,您可以在调用方和函数代码之间自定义一个 dict 类型的参数。调用方按照定义好的格式传入数据,函数代码按格式获取数据。
    例如,定义一个 dict 类型的数据结构 {"key":"XXX"},当调用方传入数据 {"key":"abctest"} 时,函数代码可以通过 event[key] 来获得值 abctest
context 入参 将 context 入参传递给执行方法,代码将通过 context 入参对象,了解到运行环境及当前请求的相关内容。 请参考以下 context 入参,了解具体信息:
{
    "time_limit_in_ms": 3000, 
    "request_id": "627466b4-8049-11e8-8758-5254005d5fdb",
    "memory_limit_in_mb": 512
}
其中包括了当前调用的执行超时时间,内存限制,以及当次请求 ID。
注意:context 结构内容将会随着 SCF 平台的开发迭代而增加更多内容。

了解 event 入参和 context 入参的基本用法后,在编写函数代码时您还需注意以下几点:

  • 为保证针对各开发语言和环境的统一性,event 入参和 context 入参均使用 JSON 数据格式统一封装。
  • 不同触发器在触发函数时,所传递的数据结构均有所不同。详情请参见 函数触发器说明
  • 在使用云 API 方法触发云函数时,您可以自行定义传递给云函数的入参。
  • 当云函数不需要任何输入时,您可以在代码中忽略 event 和 context 参数。

函数返回

SCF 平台会获取到云函数执行完成后的返回值,并根据下表中不同的触发方式进行处理。

触发方式 处理方式
同步触发
  • 通过 API 网关、云 API 中 RequestResponse 触发函数的方式为同步触发。
  • 使用同步方式触发的函数在执行期间,SCF 平台不会返回触发请求。
  • 在函数执行完成后,SCF 平台会将函数返回值封装为 JSON 格式并返回给调用方。
异步触发
  • 使用异步方式触发的云函数,SCF 平台接收触发事件后,会返回触发请求。
  • 在函数执行完成后,函数的返回值会封装为 JSON 格式并存储在日志中。
  • 用户可在函数执行完成后,通过记录请求返回中的 requestId 查询日志,即可获取该异步触发函数的返回值。

当函数中的代码返回具体值时,通常返回特定的数据结构。例如 :

运行环境 返回数据结构类型
Python 简单数据结构或 dict 数据结构
Node.js JSON Object
PHP Array 结构
GO 简单的数据结构或带有 JSON 描述的 struct

为保证针对各开发语言和环境的统一性,函数返回会使用 JSON 数据格式统一封装。SCF 平台在获取到例如以上运行环境函数的返回值后,将会对返回的数据结构进行 JSON 化,并返回 JSON 内容到调用方。

注意:

  • 需确保函数的返回值均可被 JSON 化,若直接返回对象且不具备 JSON 化方法,将会导致 SCF 平台在 JSON 化操作时失败并报错。
  • 例如以上运行环境的返回值,无需在 return 前自行 JSON 化,否则会导致输出的字符串会进行再次 JSON 化。

异常处理

若函数在调试和运行过程中出现异常,SCF 平台会尽最大可能捕获异常并将异常信息写入日志中。函数运行产生的异常包括捕获的异常(Handled error)和未捕获的异常(Unhandled Error)。

处理方式

本文提供以下三种抛出异常方式,您可根据实际需求选择在代码中进行异常处理。

说明:

您可以前往 SCF 控制台 按照以下步骤进行异常处理测试:

  1. 新建函数并复制以下函数代码,不添加任何触发器。
  2. 单击控制台【测试】,选择 “Hello World” 测试示例进行测试。
处理方式 示例 说明
显式抛出异常
def always_failed_handler(event,context):
    raise Exception('I failed!')
此函数在运行过程中将引发异常,返回以下错误信息,SCF 平台会将此错误信息记录到函数日志中。
File "/var/user/index.py", line 2, in always_failed_handler
raise Exception('I failed!')
Exception: I failed!
继承 Exception 类
class UserNameAlreadyExistsException(Exception): pass
def create_user(event):
    raise UserNameAlreadyExistsException('
        The username already exists,
        please change a name!')
您可以在代码中自行定义错误的处理方式,保障应用程序的健壮性和可扩展型。
使用 Try 语句捕获错误
def create_user(event):
    try:
        createUser(event[username],event[pwd])
    except UserNameAlreadyExistsException,e:
        //catch error and do something

返回错误信息

当用户的代码逻辑中未进行异常处理及错误捕获时,SCF 平台会尽可能的捕获错误。例如,用户函数在运行过程中突然崩溃退出,当出现此类平台也无法捕获错误的情况时,系统将会返回一个通用的错误信息。
下表展示了代码运行中常见的一些错误:

错误场景 返回消息
使用 raise 抛出异常 {File "/var/user/index.py", line 2, in always_failed_handler raise Exception('xxx') Exception: xxx}
处理方法不存在 {'module' object has no attribute 'xxx'}
依赖模块并不存在 {global name 'xxx' is not defined}
超时 {"time out"}

日志

SCF 平台会将函数调用的所有记录及函数代码中的全部输出存储在日志中,请使用编程语言中的打印输出语句或日志语句生成输出日志,方便调试及故障排除时使用。

日志组成

函数运行日志包含:函数名、启动时间、执行时间、计费时间、内存实际用量、返回码、返回值、代码日志、执行状态。

函数的运行日志通常数据结构如下所示:

{
    "functionName": "testnode",
    "retMsg": "\"ok\"",
    "requestId": "b33b9d0b-9c51-11e7-b38f-525400c7c826",
    "startTime": "2017-09-18 17:13:57",
    "retCode": 0,
    "invokeFinished": 1,
    "duration": 7.437,
    "billDuration": 100,
    "memUsage": 131072,
    "log": "2017-09-18T09:13:57.155Z\tundefined\tHello World\n2017-09-18T09:13:57.156Z\tundefined\t{ Records: [ { CMQ: [Object] } ] }\n2017-09-18T09:13:57.158Z\tundefined\t{ msgBody: '3',\n  msgId: '3096224743817223',\n  msgTag: '',\n  publishTime: '2017-09-18T17:13:57Z',\n  requestId: '5761047512720426853',\n  subscriptionName: 'test',\n  topicName: 'test',\n  topicOwner: 1251762227,\n  type: 'topic' }\n2017-09-18T09:13:57.159Z\tundefined\t{ callbackWaitsForEmptyEventLoop: [Getter/Setter],\n  done: [Function: done],\n  succeed: [Function: succeed],\n  fail: [Function: fail],\n  memory_limit_in_mb: 128,\n  time_limit_in_ms: 30000 }\n"
}

日志写入

Log 语句为函数提供必要的执行过程中的信息,是开发者对代码进行排障的必要手段。SCF 平台会将用户在代码中使用 log 语句生成的日志全部写入日志系统中,如果您使用控制台调用函数,控制台将显示相同的日志。

您可以通过 print 语句或 logging 模块中的 Logger 函数生成日志条目。 如下表所示:

写入方式 示例
使用 logging 语句 下例代码使用 logging 模块将信息写入日志中:
import logging
logger = logging.getLogger()
def my_logging_handler(event):
    logger.info('got event{}'.format(event))
    logger.error('something went wrong')
    return 'Hello World!'
您可以前往控制台日志模块或通过 获取函数运行日志 API 来查看代码中的日志信息。
注意:日志级别标识日志的类型,例如 INFOERRORDEBUG
使用 print 语句 在代码中使用 print 语句:
def print_handler(event):
    print('this will show up in logging')
    return 'Hello World!'
注意:当使用控制台【测试】同步调用此函数时,控制台将显示 print 语句和 return 的值。

获取日志

您可以通过以下途径获取函数运行日志:

获取途径 获取方法
SCF 控制台 在函数代码页面,单击【测试】同步调用函数,执行完成后会直接在控制台展示本次调用的日志。
触发器调用 该函数的日志选项卡中会展示函数每一次被调用产生的日志。
SCF API 通过 GetFunctionLogs 接口获取函数日志。
Invoke API 同步调用 可在返回值的 logMsg 字段中获取本次调用的日志。

注意事项

由于 SCF 的特点,您须以无状态的风格编写您的函数代码。本地文件存储等函数生命周期内的状态特征,在函数调用结束后将随之销毁。
因此,持久状态建议存储在关系型数据库 TencentDB、对象存储 COS、云数据库 Memcached 或其他云存储服务中。

开发流程

了解更多云函数开发流程,请参见 使用流程

目录