作者:落泥
在运用阿里云函数核算渠道时,假如您从前遇到过以下问题,本文应该会对您有所帮助:
- 第三方依靠包太大,每次更新代码都非常耗时,甚至会出现超越代码包约束的情况,我该怎么办?
- 装置第三方依靠包后,可以在本地运转成功,上传到阿里云函数核算渠道上就会报错,这是什么情况?
- 有许多常用的依靠包,许多用户应该都会用到,阿里云函数核算官方不能直接内置到运转时环境中么?
- 我在多个函数中有相同的依靠包,我该怎么办理这些相同的依靠包?
层供给了会集办理,跨多个功用且可以同享代码和数据的办法。
2021 年 1 月,阿里云函数核算发布了 “自界说层”功用,让用户可以自界说层,并支撑跨函数同享。2022 年 8 月,阿里云函数核算发布“公共层”功用,供给了官方公共层,供用户直接运用,进一步提升了用户体验。
接下来先介绍“自界说层”的功用和作用。
自界说层
在层功用发布之前,必须将代码与代码的依靠项一同打包和布置,这些依靠项在不同函数中或许是相同的,许多情况下这些依靠项的巨细,远远大于代码的巨细。
在层功用发布后,咱们可以将代码的依靠项,或许多个函数中同享的部分打包成 Zip 压缩文件,并作为函数核算的自界说层发布,不同函数都可以运用该自界说层。阿里云函数核算会在调用时将层与函数代码一同加载。可参阅文末文档-创立自界说层 [ 1] 、在函数中装备自界说层 [ 2] :
为什么运用自界说层?
运用自界说层有以下优势:
- 跨函数复用代码
将多个函数中的通用代码或数据提取出来,打包成 Zip 包,做成自界说层,供不同函数引证,避免了在多个地方保护通用的代码或数据。
与此同时,也完结了依靠项和事务逻辑的别离,用户可以专心于中心的事务逻辑。
- 使代码包更小
函数的代码包越来越大时,布置速度也会越来越慢,导致函数的保护和测验愈加困难。
此外函数代码包巨细也有约束,比方阿里云函数核算的代码包约束为 500MB (2022 年 9 月),层是打破该约束的办法之一。层也有巨细约束,现在单个层的代码包巨细约束为 500MB,单个函数最多可装备5个层,总巨细不能超越 2GB。
- 加速代码布置,简化函数办理
函数代码包越小,代码包的布置就越快。尤其是一些大型依靠项时,中心功用代码或许只要几兆字节,但依靠项或许有几百兆。比方 Puppeteer 依靠包超越 100MB,阿里云的 DataX 依靠包超越 800MB。
一般来讲,这些依靠项很少修正,因而将他们打包成层后,可以避免在中心代码修正时频频修正这些大型依靠项。对这些依靠项也可以拆分成多个层,每次修正一个功用时,只需求更新其间一个层。比方咱们完结了自界说运转时 Python3.10 以及该运转时兼容的科学核算库 SciPy,可以将自界说运转时和依靠包拆成两个层,当需求更新依靠包时,只需求更新依靠包的层,而自界说运转时的层坚持不变。
自界说层的窘境
- 制作层有一定门槛
层的 Zip 包有一定的格式标准,用户需求按照该标准进行制作。以 Python 的 requests 库为例,依靠打包后的文件结构为:
my-layer-code.zip
└── python
└── requests
为什么有这种要求呢?这个涉及到不同运转时在查找第三方依靠包的完结逻辑,以 Python 为例,Python 运转时会在 sys.path 途径下查找依靠包,上面的 Zip 包会解压到函数实例的 /opt 目录下,解压后 requests 这个包就放到了 /opt/python 目录下。
然后,函数核算渠道会将一些特定的目录放到运转时言语的依靠查找途径上,比方 Python 运转时就会将 /opt/python 放到 sys.path 中,这样,代码中就可以直接引证 requests 库了。其他运转时的运用办法可参阅文末文档-创立自界说层。
当然,你也可以不按照这个格式标准来制作层,此刻就需求在代码中增加对应的查找途径了,具体办法可参阅文末文档-怎么在 Custom Runtime 中引证层中的依靠? [ 3]
需求在指定操作体系和处理器架构下制作层。有一些依靠是与操作体系和处理器架构有依靠关系的,比方 Python 的科学核算库 NumPy,假设你在 M1 芯片的 MacOS 下装置,其版别为:
numpy-1.23.3-cp39-cp39-macosx_11_0_arm64
可看到兼容的操作体系为 mac os, 处理器架构为 arm64。但在函数核算渠道的实例环境为 Linux x86_64,操作体系现在运用的发行版为 Debian 9,因而在 M1 Mac 下装置的 NumPy 库不能在阿里云函数核算渠道运用。
咱们引荐在 Debian 9 体系下进行装置,但用户本地或许没有该环境,您可以运用在线构建依靠库或许运用函数核算官方运转时镜像来构建,此处不再赘述。
层需求包括新增的同享动态库。有些依靠库需求装置额定的同享动态库,在构建层的 Zip 包时也需求包括这些同享动态库。例如 Nodejs 的依靠库 Puppeteer,需求额定装置二十多个同享动态库(如 libxss1,libnspr4 等),这些依靠库都要打包到层 Zip 包中。怎么成功的装置 Puppeteer 库并不是简略的工作。
同享动态库引荐放到 Zip 包的 lib 目录下,函数核算渠道会将/opt/lib 目录增加到 LD_LIBRARY_PATH(仅限于内置运转时)。
- 无法跨账号同享
自界说层默认只能在同账号同地域的不同函数之间同享,无法进行跨账号同享。因而,用户 A 创立的自界说层无法给用户 B 运用,这不仅给用户带来了重复的工作量,也不利于宿主机上相同层的复用。
公共层**
由于自界说层的这些痛点,阿里云函数核算在 2022 年 8 月发布了公共层功用。完结层跨账号同享,并供给了一些官方公共层 [ 6] 供用户直接运用,便利用户快速开发示例原型。阿里云函数核算渠道首要供给了三类官方公共层:
- 自界说运转时(如 Python 3.10、Nodejs17、PHP 8.1、Java17、.NET 6 等)
- 常用依靠库 (如 PyTorch、Scipy、Puppeteer 等)
- 阿里云 SDK (如 Aliyun DataX )
概况可参阅文末官方文档-在函数中装备官方公共层 [ 4] ,现在官方公共层仍在继续弥补,假如您有需求的运转时或许依靠库想经过官方公共层的方式运用,可经过钉钉答疑群(钉钉群号:11721331)与咱们联络,也可以直接在 Github [ 5] 提交 issue。
怎么揭露自界说层?
现在,层揭露功用在内测中,如有需求欢迎经过钉钉答疑群(钉钉群号:11721331)联络咱们。
同时,咱们也非常欢迎大家奉献公共层到库房,咱们很快会在该库房供给公共层奉献的办法和示例。
示例展现
官方公共层的最新版别和运用说明可参阅Github。下面咱们介绍一些运用官方公共层的典型示例。
示例一、根据 Nodejs16 + Puppeteer 完结网页截图示例程序
Puppeteer 是一个 Nodejs 库,它供给了高级的 API 并经过 DevTools 协议来操控 Chrome(或 Chromium)。浅显来说便是一个 headless chrome 浏览器,可以运用它完结许多主动化的工作,比方:
- 生成网页截图或许 PDF
- 做表单的主动提交、UI 的主动化测验、模拟键盘输入等
- more…
本示例运用 Puppeteer 完结一个网页截图示例程序。
首要,咱们运用内置运转时 Nodejs16 创立一个函数 start-puppeteer,其间恳求处理程序类型挑选“处理 HTTP 恳求”。
然后,在高级装备中将内存标准设置为 1GB,示例程序的内存运用大概在 550MB 左右。
创立成功后,在操控台上翻开 index.js 文件,将下面的代码复制并覆盖该文件,点击布置按钮。
const fs = require('fs');
const puppeteer = require('puppeteer');
function autoScroll(page) {
return page.evaluate(() => {
return new Promise((resolve, reject) => {
var totalHeight = 0;
var distance = 100;
var timer = setInterval(() => {
var scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= scrollHeight) {
clearInterval(timer);
resolve();
}
}, 100);
})
});
}
module.exports.handler = function (request, response, context) {
console.log('Node version is: ' + process.version);
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-zygote',
'--no-sandbox'
]
});
let url = request.queries['url'];
if (!url) {
url = 'https://www.serverless-devs.com';
}
if (!url.startsWith('https://') && !url.startsWith('http://')) {
url = 'http://' + url;
}
const page = await browser.newPage();
await page.emulateTimezone('Asia/Shanghai');
await page.goto(url, {
'waitUntil': 'networkidle2'
});
await page.setViewport({
width: 1200,
height: 800
});
await autoScroll(page)
let path = '/tmp/example';
let contentType = 'image/png';
await page.screenshot({ path: path, fullPage: true, type: 'png' });
await browser.close();
response.setStatusCode(200);
response.setHeader('content-type', contentType);
response.send(fs.readFileSync(path))
})().catch(err => {
response.setStatusCode(500);
response.setHeader('content-type', 'text/plain');
response.send(err.message);
});
};
扼要介绍一下上述代码的中心逻辑,首要代码会解析 query 参数获取需求截图的 url 地址(假如解析失败则默认运用 Serverless Devs 官网主页),然后运用 Puppeteer 对该网页进行截图,并保存到运转实例的 /tmp/example 文件中,然后将该文件作为 HTTP 恳求的回来体直接回来。
然后,咱们需求装备 Puppeteer 公共层,在函数装备中找到层,点击编辑,挑选增加官方公共层。
挑选官方公共层 Puppeteer17x,现在最新的层版别为 1。
参阅官方公共层Nodejs-Puppeteer17x README [ 6] 增加环境变量,关于版别 1,需求增加 LD_LIBRARY_PATH=/opt/lib/x86_64-linux-gnu:/opt/lib 环境变量。
最终,运用触发器办理中的测验地址进行测验验证。
测验成果如下所示,已成功将 Serverless Devs 官方进行截图。
示例二、根据公共层快速完结 .NET 6 自界说运转时
首要,经过操控台创立 .NET 6 自界说运转时。在最上层挑选 “运用自界说运转时创立”,挑选“处理 HTTP 恳求”,挑选 .NET 6 运转时,其他装备运用默认值。
创立成功后,可以经过 WebIDE 看到示例代码 Program.cs
示例代码中需求留意四个部分:
- 该示例监听了 0.0.0.0 的 9000 端口,Custom Runtime 发动的服务一定要监听 0.0.0.0:CAPort 或*:CAPort 端口,不能监听 127.0.0.1 或 localhost。概况参阅文档Custom Runtime>基本原理 [ 7] 。
- 增加路由 /,直接回来字符串 “Hello World!”
- 增加路由/invoke,该路由为运用事件恳求处理程序的途径,可参阅文档Custom Runtime >事件恳求处理程序(Event Handler) [ 8]
- 增加路由 /initialize,该路由为函数初始化回调程序对应的途径,该办法会在示例初始化时执行一次,可参阅文档Custom Runtime >函数实例生命周期回调 [ 9]
首要,咱们直接运用触发器办理页面中的测验地址进行测验,此刻不增加任何 PATH 信息,成果如下图所示:
然后,咱们测验增加 /invoke 途径进行测验,因为该路由办法为 POST,咱们直接运用 curl -XPOST 测验:
同样,咱们用这种办法测验一下/initialize
留意:此处只是做测验,初始化回调函数不需求主动调用,函数核算渠道会在实例发动后主动调用该回调办法(不要忘记在装备里启用 initializer 回调程序)
最终,咱们再做一个小测验,在触发器办理页面将 HTTP 触发器删去,删去后该函数类型会转换成事件恳求处理程序,在函数装备中,将 Initializer 回调程序启用
在操控台上测验该函数,成果如下图所示:
点击实时日志按钮,可以看到在该恳求执行前,现已执行了 Initialize 回调办法。
层的最佳实践是什么?
前文介绍了什么是自界说层,为什么运用自界说层,什么是公共层,并介绍了两个官方公共层的示例。但咱们对层的运用依然还有一些疑问,比方什么场景下引荐运用层?层与代码包有什么区别?有没有与层类似的功用?与这些类似功用相比,层的优缺点是什么?接下来测验答复一下这些问题。
什么场景下引荐运用层?
现在,运用层的场景首要有两类,一类是自界说运转时,另一类是各种言语的依靠库。强烈引荐经过层来构建并运用自界说运转时,但关于各类言语的依靠库,可以参阅下面这些建议:
- 引荐优先运用官方公共层
- 非编译型言语的依靠库引荐运用层来办理,对编译行言语需求根据实际情况进行判别(比方,对自界说运转时,假如运用 JAR 包的方式运转 Java 程序,则无法引入层中的依靠,可参阅文档-怎么在 Custom Runtime中引证层中的依靠? [ 3] )
- 假如依靠库较大,而且没有超越层的约束巨细,引荐运用层
- 假如依靠库需求额定装置同享动态库,引荐运用层(假如构建比较复杂,可联络函数核算团队制作)
- 假如在多个函数、多个账号之间有同享代码或数据的需求,引荐运用层
层与代码包有什么区别?
直观上看,层便是把本来代码包的一部分内容拆分出来,再重新建一个代码包罢了,那为什么又树立一个层的概念呢?这里的首要区别是层与代码包的规划理念不同。
- 层有更简练的版别办理方案
层的版别是从 1 开端主动递加的,现在一个层最大支撑 100 个可用版别(不包括已删去的版别);而对代码包来讲没有版别的概念,只要在服务层面上有版别概念,相对层的版别会更加复杂。
- 层版别是只读的,不可变的
一个层的版别在创立后内容是无法改变的(权限在外),假如想修正层的内容,只能发布一个新的版别。层版别的只读特性可以避免层的改动对函数的影响。
- 层的同享能力
层可以跨函数、跨账号进行同享,而代码包不支撑。
- 层版别的软删去战略
层版别删去后,不会影响现已装备改层版别的函数的正常运转。因为在层版别删去,阿里云函数核算渠道并不会直接将层版别的代码删掉,而是先进行一次软删去操作,避免新的函数运用已删去的层版别,当该层版别没有函数引证时,才会完全删去该层版别。
阿里云函数核算中有没有与层类似的功用?与类似功用相比,层的优缺点是什么?
在阿里云函数核算渠道中,与层类似的功用是服务装备中的“挂载 NAS 文件体系”和“挂载 OSS 目标存储”功用,层与挂载 NAS/OSS 在功用和运用场景上有一些显着的差异:
简略总结一下,假如代码或数据的巨细超越层的约束,则引荐运用挂载 NAS/OSS 的方式;假如代码或数据会常常改动,或许有运转中修正数据的需求,也引荐运用挂载 NAS/OSS 的方式。
结语
在阿里云函数核算中,层的定位是一种不可变的基础设施,经过层版别的只读特性确保层的一致性和可靠性。本文首要介绍了自界说层的特点和窘境,然后介绍了近期发布的公共层功用,详细陈说了根据官方公共层完结的两个示例程序,最终探讨了层的最佳实践是什么,期望经过本文能让读者更好的了解层的概念及其运用场景。
层的功用仍在继续完善中,接下来咱们会在一下几个方向进行重点优化:
- 完善官方公共层体验,弥补更多的常用依靠库或自界说运转时作为官方公共层,并供给完善的运用示例。
- 供给公共层奉献的办法和示例,促进公共层的开源共建。
假如对层的运用有任何的疑问或许建议,欢迎查找(群号:11721331)进入阿里云函数核算钉钉群联络咱们。
延申阅读
[1] 创立自界说层 :
help.aliyun.com/document_de…
[2] 在函数中装备自界说层:
help.aliyun.com/document_de…
[3] 怎么在Custom Runtime中引证层中的依靠? https://help.aliyun.com/document_detail/71142.html
[4] 在函数中装备官方公共层:
help.aliyun.com/document_de…
[5] 阿里云函数核算 公共层github:
github.com/awesome-fc/awesome-layers
[6] 官方公共层- Nodejs-Puppeteer17x README
github.com/awesome-fc/…
[7] Custom Runtime 基本原理
help.aliyun.com/document_de…
[8] Custom Runtime 事件恳求处理程序
help.aliyun.com/document_de…
[9] Custom Runtime 函数实例生命周期回调
help.aliyun.com/document_de…
点击此处,了解函数核算 FC!