函数计算基于实例生命周期增加多种回调操作,有效解决传统应用迁移至Serverless架构时遇到的指标数据延迟或丢失等痛点。本文介绍函数计算的运行时扩展功能原理、如何配置PreFreeze和PreStop回调函数及回调函数日志查询。
背景信息
传统应用迁移Serverless架构的痛点
传统常驻的虚拟机或者托管容器类服务通常从实例启动到结束作为计费区间,即使该时间段没有业务请求仍纳入计费。函数计算提供1 ms计费粒度,且只在有实际请求的区间内计费,在请求以外的时间段内实例会被冷冻。这样基本消除了完全事件驱动的计费模型的闲置成本。然而冷冻机制也会打破一些传统架构下long-running进程的假设,加大应用迁移的难度。由于函数计算拥有特殊的运行环境,面对没有冷启动的场景,例如常用的开源分布式链路追踪Tracing Analysis库和第三方APM解决方案等,将无法追踪并正确上报其数据。
阻碍传统应用平滑迁移至Serverless架构的痛点如下。
异步背景指标数据延迟或丢失:如果在请求期间没有发送成功,则可能被延迟至下一次请求,或者数据点被丢弃。
同步发送指标增加延迟:如果在每个请求结束后都调用类似Flush接口,不仅增加了每个请求的延迟,对于后端服务也产生了不必要的压力。
函数优雅下线受阻:实例关闭时应用有清理连接、关闭进程、上报状态等需求。在函数计算中实例的下线时机开发者无法掌握,也缺少Webhook通知函数实例下线事件。
函数计算编程模型扩展
函数计算针对上述痛点发布了运行时扩展(Runtime Extensions)功能。该功能在现有的HTTP服务编程模型上扩展,在已有的HTTP服务器的模型中增加了PreFreeze和PreStop Webhooks,扩展开发者实现HTTP handler,监听函数实例生命周期事件。
PreFreeze
在每次函数计算服务决定冷冻当前函数实例前,函数计算服务会调用HTTP GET /pre-freeze路径,扩展开发者负责实现相应逻辑以确保完成实例冷冻前的必要操作,例如等待指标发送成功等。函数调用InvokeFunction的时间不包含PreFreeze hook的执行时间。
PreStop
在每次函数计算决定停止当前函数实例前,函数计算服务会调用HTTP GET /pre-stop路径,扩展开发者负责实现相应逻辑以确保完成实例释放前的必要操作,如关闭数据库链接,以及上报、更新状态等。
前提条件
已完成函数的创建,具体请参见创建函数。
使用限制
PreFreeze和PreStop回调函数输入参数没有event参数。
PreFreeze和PreStop回调函数无返回值,在函数末尾增加返回逻辑无效。
所有Runtime均支持配置PreStop回调函数;Python、PHP及C# Runtime不支持配置PreFreeze回调函数。
如果使用Java Runtime,您需要将fc-java-core更新至1.4.0及以上版本,否则无法使用PreFreeze和PreStop扩展回调函数。具体操作,请参见HTTP请求处理程序(HTTP Handler)。
如果您使用的是非Web Server模式的Custom Container Runtime,则配置的PreFreeze和PreStop回调无效。
当函数执行返回时,函数计算将冻结函数实例,用户不可假设调用返回时所有异步进程、线程、协程等执行完成,也不可假设本次异步写入的日志被刷新。
配置PreFreeze和PreStop回调
唤起PreFreeze或PreStop中产生的费用计费方式与InvokeFunction计费方式一致。具体信息,请参见计费说明。
通过控制台配置回调
当您使用控制台创建函数时,函数计算不支持您配置PreFreeze及PreStop回调,您需要在更新函数时配置该回调函数。
- 登录函数计算控制台,在左侧导航栏,单击服务及函数。
- 在顶部菜单栏,选择地域,然后在服务列表页面,单击目标服务。
- 在函数管理页面,单击目标函数操作列的配置。
在编辑函数配置页面的实例生命周期回调区域,设置回调程序与超时时间,然后单击保存。
说明每一个扩展函数都需要配置单独的回调程序和超时时间,其中回调程序格式为[文件名].[扩展函数名]。例如在Python Runtime中,创建函数时指定的PreStop回调为index.preStopHandler,那么文件名为index.py,PreStop函数名为preStopHandler。
配置扩展函数后,您需要在代码执行中实现对应的函数。
单击函数代码页签,在代码编辑区域,输入扩展函数代码。
例如,您配置的PreStop回调程序为
index.preStopHandler
,则需要实现preStopHandler函数。不同语言运行时实现函数实例生命周期回调的方法请参见函数实例生命周期回调方法。说明在线IDE支持PHP、Python、Node.js和Custom Runtime;但不支持Java、Go和.NET这类编译性语言,以及Custom Container。
单击代码编辑器上方的部署代码,然后单击测试函数。
通过Serverless Devs配置回调
使用Serverless Devs配置PreFreeze、PreStop扩展函数时,s.yaml
配置文件示例代码片段如下所示:
codeUri: './code.zip'
......
instanceLifecycleConfig:
preFreeze:
handler: index.PreFreeze
timeout: 60
preStop:
handler: index.PreStop
timeout: 60
如果您需要关闭某个扩展函数,需要将扩展函数的handler
参数显示置空,否则后端默认不更新。例如关闭PreFreeze函数,您需要按照以下配置进行部署更新,此时PreFreeze函数的timeout
参数已无效。
codeUri: './code.zip'
......
instanceLifecycleConfig:
preFreeze:
handler: ""
timeout: 60
preStop:
handler: index.PreStop
timeout: 60
不同语言运行时实现函数实例生命周期回调的方法请参见函数实例生命周期回调方法。
通过SDK配置回调
您可以通过SDK部署和更新扩展函数。本文介绍如何获取在创建函数时配置PreStop和PreFreeze函数的SDK示例代码。
进入CreateFunction - 创建函数页面,单击调试,进入OpenAPI门户。
在参数配置页签,根据需要创建函数的基本信息填写输入参数。
其中您可以在实例生命周期函数配置
instanceLifecycleConfig
字段配置PreStop和PreFreeze回调。参数配置完成后,单击SDK示例页签,从而获取对应语言的SDK示例代码。
不同语言运行时实现函数实例生命周期回调的方法请参见函数实例生命周期回调方法。
函数实例生命周期回调方法
不同语言运行时实现函数实例生命周期回调的方法请参考以下内容。
运行时 | 描述 | 参考文档 |
Node.js | 通过Node.js实现并应用函数实例生命周期回调方法。 | |
Python | 通过Python实现并应用函数实例生命周期回调方法。 | |
PHP | 通过PHP实现并应用函数实例生命周期回调方法。 | |
Java | 通过Java运行时实现函数实例生命周期回调的方法。 | |
C# | 通过C#运行时实现函数实例生命周期回调的方法。 | |
Go | 通过Go实现函数实例生命周期回调的方法。 | |
Custom Runtime | 通过Custom Runtime实现函数实例生命周期回调的方法。 | |
Custom Container | 通过Web Server模式的Custom Container Runtime实现函数实例生命周期回调的方法。 |
查询回调函数相关日志
配置函数实例生命周期回调并执行代码实现对应的回调函数后,您可以查询实例生命周期回调函数的相关日志。
- 登录函数计算控制台,在左侧导航栏,单击服务及函数。
- 在顶部菜单栏,选择地域,然后在服务列表页面,单击目标服务。
在函数管理页面,单击目标函数,然后单击调用日志页签。
在调用请求列表页签,找到想要查询的请求行,复制实例 ID,然后单击高级日志。
您可以使用复制的实例ID,查询所有生命周期回调函数的Start/End日志;还可以使用
实例ID+函数实例生命周期回调关键字
查询指定回调函数的Start/End日志,例如,c-62833f38-20f1629801fa4bd***** and PreStop
。此外,您还可以根据Start/End日志中的RequestId查询请求的日志信息。如果用户日志中没有RequestId,可以单击该日志中的图标获取上下文日志。
计费说明
扩展HTTP hooks请求数不计费。扩展在单实例多并发的场景下依然适用,假设同时有多个Invoke请求在相同实例执行,在所有请求都结束后,即将冷冻实例之前,会调用一次PreFreeze hook。
以下图为例,函数规格为1 GB,从t1 PreFreeze开始到t6请求2结束的时间段(假设为1秒),则实例执行时间为t6-t1,消耗1s * 1 GB=1 CU。关于计费方式的具体信息,请参见计费方式。