基于阿里云函数计算的简单邮件发送服务设计与体验
基本要求
- 理解掌握函数计算FC的设计部署过程
- 理解体验函数计算FC的弹性伸缩能力
实验原理
Serverless技术与无服务器模式
云计算是继传统CS架构之后的新一代互联网应用架构,是一种基于互联网的计算方式,用户不再需要了解硬件基础设施的细节,也不需直接进行控制。云计算通过互联网将算力以按需使用、按量付费的形式提供给用户,其两个最明显的优势是弹性和敏捷:弹性即按需使用各类服务,灵活扩缩容,应对业务流量的不确定性;敏捷即能快速部署应用而无需购买任何物理资源。
Serverless是一种云计算执行模型,使得开发者不用过多考虑服务器的问题,允许在服务部署级别而不是服务器部署级别来管理应用部署。与传统架构的不同之处在于,服务的提供完全由第三方管理。服务以函数的方式托管在Serverless框架下,当请求到达时会启动一个容器运行代码,无请求时不存在容器实例。
2017年国内三大云服务厂商均推出了自己的Serverless服务:阿里云FC、华为云FG、腾讯云SCF,现已发展成熟,服务稳定、社区活跃、教程丰富。
无服务器模式即将业务逻辑以Serverless执行模型部署在云上。无服务器指的是部署时根本不关注服务器,不需要用户提供服务器或者购买云服务器,而不是没有服务器。使用Serverless云计算执行模型时,用户一般只需要将代码上传到指定仓库,云厂商就会自动完成编译、部署等流水线工作,服务直接可用。
与传统架构的不同之处在于,服务器和集群都由云厂商提供、分配、维护,这些计算平台对用户透明。它的好处是稳定可靠、减少服务器运维工作量,且相比云服务器成本更低。
阿里云函数计算FC
函数计算是 Serverless 架构的一种形态,面向函数编程,基于事件驱动提供阿里云服务之间端到端的解决方案。借助函数计算,开发者可以快速构建任何类型的应用和函数,并且只需为任务实际消耗的资源付费。
阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,开发者无需管理服务器等基础设施,只需专注于业务代码。函数计算服务会准备好计算资源,以弹性、可靠的方式运行代码,并提供日志查询、性能监控、按需报警等功能。
架构
函数计算涉及函数、实例、运行环境、触发器、层、应用中心等功能组件,具体产品组件架构图如下图所示。

基本术语
- FC函数
函数计算的资源调度与运行是以函数为单位。FC函数由函数代码和函数配置构成。
- 版本
版本相当于函数的快照,包括函数的配置和函数代码,不包括触发器。版本类似于 Git 里的一次 commit,该 commit 包含了一个或者多个代码文件及其配置变更,是当前仓库的一次快照。
- 层
层可以为您提供自定义的公共依赖库、运行时环境及函数扩展等发布与部署能力。您可以将函数依赖的公共库提炼到层,以减少部署、更新时的代码包体积,也可以将自定义的运行时,以层部署在多个函数间共享。
- 触发器
触发器是触发函数执行的方式。在事件驱动的计算模型中,事件源是事件的生产者,函数是事件的处理者,而触发器提供了一种集中、统一的方式来管理不同的事件源。在事件源中,当事件发生时,如果满足触发器定义的规则,事件源会自动调用触发器所对应的函数。
- 运行时
函数运行环境,函数计算提供多种语言的运行环境。可以构建自己的运行时,或者自行构建容器运行环境。
- 冷启动
冷启动是指在函数调用链路中的代码下载、启动函数实例、进程初始化及代码初始化等环节。当冷启动完成后,函数实例就绪,后续请求就能直接被函数执行。
- 按量模式
按量模式下,函数计算系统自动为函数分配和释放实例。
- 预留模式
预留模式是将函数实例的分配和释放交由您管理。当您预留了函数实例,函数计算系统收到函数调用请求时,会优先将请求转发给您预留的函数实例。当函数请求的峰值超过预留的函数实例处理能力时,剩余的部分请求将会转发给您的按量模式的实例。
预留模式下实例的执行环境是长驻的,可以彻底消除冷启动对业务的影响。
为了解决预留模式配置的固定预留实例利用不充分问题,您可以设置预留模式实例的弹性伸缩功能,支持定时弹性伸缩和指标追踪弹性伸缩两种方案。
函数类型
| 对比项 | 事件函数 | Web函数 | 容器镜像 | 任务函数 |
|---|---|---|---|---|
| 适用场景 | 按照函数计算定义的接口编写程序处理事件 | 基于各个语言的流行框架(Java、SpringBoot、Node.js、Express、Python Flask、Golang Golang等)编写程序,或者迁移已有的框架应用 | 完全控制程序运行的环境,或者迁移已有的容器应用。使用GPU实例。 | 对函数发起异步调用,且需要追踪并保存异步调用各个阶段的状态,可以选择创建任务函数。任务函数默认开启任务模式,您可以使用任务模式提交、查看、停止和重试异步任务。 |
| 冷启动 | 最快,代码包中不包含运行时,所以冷启动最快 | 较快。Web函数使用公共镜像,没有镜像拉取时间,所以冷启动会较快 | 较慢。需要拉取镜像,所以冷启动较慢 | 最快。代码包中不包含运行时,所以冷启动最快。 |
| 代码包限制 | 500 MB未解压代码包 | 500 MB未解压代码包 | 10 GB未解压镜像 | 500 MB未解压代码包 |
| 代码包格式 | ZIP、JAR(Java)、文件夹 | ZIP、JAR(Java)、文件夹 | 参见什么是容器镜像服务ACR-阿里云帮助中心 | ZIP、JAR(Java)、文件夹 |
| 是否支持GPU实例 | 不支持 | 不支持 | 支持 | 不支持 |
| 运行时环境 | Node.js、Python、PHP、Java、.NET Core、Go | 无限制 | 无限制 | Node.js、Python、PHP、Java、.NET Core、Go |
弹性伸缩
在现实世界的业务场景中,服务可能会遭遇突如其来的流量高峰,这种高峰可能是由于促销活动、新闻事件或任何其他不可预测的因素引起的。这种高峰期的并发请求对服务构成了巨大的冲击,可能导致服务响应缓慢甚至崩溃。为了应对这种冲击,需要一种解决方案能够快速响应并解决资源不足的问题。为了解决这一问题,可以利用阿里云函数计算(FC)的弹性伸缩能力。
FC的弹性伸缩是一种自动化的资源管理机制,它可以根据实际的负载情况动态地调整资源分配,以确保服务的稳定性和响应速度。
实验过程
实验环境准备
获取函数计算免费额度
https://fcnext.console.aliyun.com/,阿里云自2024年8月27日起为新用户提供了12个月免费使用额度。
创建自定义公共层,提供Python Sanic依赖
在阿里云的函数计算(FC)环境中开发与本地环境开发有着显著的不同。在本地环境中,通常可以通过 pip install xxx 命令来安装所需的依赖。然而,在FC环境中,这种做法并不适用。FC环境下,应该将这些依赖项提炼成一个公共层,以便在多个函数之间共享,同时可以有效地减少部署和更新时的代码包大小。
在构建公共依赖层时,首先应该检查阿里云官方提供的公共层列表(https://github.com/awesome-fc/awesome-layers/blob/main/README.md)。如果所需的依赖已经存在于官方列表中,那么就没有必要创建自定义的公共层。
在本实验中,将使用Sanic框架来编写接口。由于Sanic框架并未包含在阿里云的官方公共层列表中,因此需要创建一个自定义的公共层来满足需求。
自定义公共层创建步骤如下:
- 浏览器访问函数计算 FC 3.0控制台(https://fcnext.console.aliyun.com/),进入`高级功能 - 层管理
页面,点击创建层`按钮进行创建。

- 参考下图所示及界面提示填写相关信息,点击创建按钮,会显示依赖层构建日志,构建完成后页面将自动跳转回层管理界面。

构建并部署告警邮件发送接口
创建Web函数
- 浏览器访问函数计算 FC 3.0控制台(https://fcnext.console.aliyun.com/),进入函数页面,点击`创建函数`按钮进行创建。

- 勾选
Web函数,在基本设置中填写函数名称,函数代码配置部分保持默认,暂时使用FC提供的示例代码(在后续步骤中会对其修改),并点击创建按钮。

基于Sanic框架编写接口代码
上一步操作中创建好函数后,页面将自动跳转至函数详情页面,并使用在线IDE打开代码。将下面提供的范例代码替换原示例代码app.py文件的内容。
# -*- coding: utf-8 -*-
from sanic import Sanic
from sanic.response import json
from smtplib import SMTP
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
app = Sanic("EmailSender")
# 邮件配置 需要自行修改。邮箱授权码(password)的获取,自行前往对应的邮件服务器官网获取。
EMAIL_CONFIG = {
"host": "smtp.example.com",
"port": 587,
"username": "your-email@example.com",
"password": "your-password",
"sender": "your-email@example.com"
}
def send_email(recipient, subject, body):
# 创建邮件对象
msg = MIMEMultipart()
msg['from'] = EMAIL_CONFIG['sender']
msg['to'] = recipient
msg['subject'] = subject
# 添加邮件正文
msg.attach(MIMEText(body, 'plain')
# 连接SMTP服务器
server = SMTP(EMAIL_CONFIG["host"], EMAIL_CONFIG["port"])
server.starttls() # 启动TLS加密
server.login(EMAIL_CONFIG["username"], EMAIL_CONFIG["password"])
# 发送邮件
server.send_message(msg)
# 关闭连接
server.quit()
@app.route("/send", methods=["POST"])
def send_email_route(request):
data = request.json
recipient = data.get("recipient")
subject = data.get("subject")
body = data.get("body")
if not all([recipient, subject, body]):
return json({"error": "Missing required fields"}, status=400)
try:
send_email(recipient, subject, body)
return json({"message": "Email sent successfully"})
except Exception as e:
return json({"error": str(e)}, status=500)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=9000)

代码修改完成后,点击IDE右上角上面的编辑层按钮,修改默认的公共层配置。
先删除现有的官方公共层Flask,再点击添加层-添加自定义层按钮选择之前创建的sanic-c-custom-layer层,最后点击部署按钮。

依赖层部署完成后,点击页面上的部署代码按钮,进行代码部署。

获取函数外网访问地址
在函数详情页面,将光标放在触发器矩形框上,将显示公网访问的地址,该域名由 CNCF SandBox项目 Serverless Devs 社区所提供,仅供学习和测试使用,社区会对该域名进行不定期地拨测,并在域名下发 30 天后进行回收。
有条件可以参考 函数计算 FC控制台-域名管理 页面提示进行自定义域名配置。

开启函数日志功能
开启日志功能后,可以实时查看该函数中打印到控制台日志,从而便于进行代码测试、故障分析等操作。点击IDE左上角偏上部分的实时日志按钮,在弹出的页面中点击一键启用按钮开启日志服务。
[!NOTE]
注意:使用阿里云的日志服务SLS会产生一定的费用,详情参见日志服务计费方式(https://help.aliyun.com/zh/sls/product-overview/billable-items)。
测试告警邮件发送接口功能
[!NOTE]
本小节采用Apifox工具进行接口测试,工具介绍参见其官方文档(https://apifox.com/help/),此处不做赘述。
利用Apifox工具编写对邮件发送接口发起HTTP接口。
URL: https://<先前获取的外网访问地址>/send
请求方式: POST
请求参数:
{
"recipient": "synx@example.com", // 实际场景中应替换为相应告警管理员的邮箱
"subject": "告警邮件", // 根据实际情况填写,此处仅做模拟
"body": "告警邮件正文部分" // 根据实际情况填写,此处仅做模拟
}

稍等片刻,对应的收件邮箱将接收到对应告警邮件。

体验函数计算FC的弹性伸缩能力
进入之前创建的 fun-alarm-email-send 函数详情页面,点击实例按钮查看当前函数的实例(阿里云函数计算FC用来运行函数的最小单元,请求最终是由函数实例来进行处理的),将发现当前不存在任何函数实例。因为按量实例(默认创建的Web函数就是按量实例)在处理完请求后会被冻结,如果一段时间内(一般为3~5分钟)不再处理请求,会自动销毁。

为了更直观观察到自动扩缩容的过程,可以设置函数的单实例并发度为较低的值,并借助Apifox工具对邮件发送接口/send进行压力测试。其中,单实例并发度指每个应用实例能够同时处理的请求次数上限,当单个实例的并发请求数达到上限,才会创建新的实例。
点击函数详情页面的配置-运行时按钮,在运行时配置展示页面点击编辑按钮,进入运行时的编辑页面,修改单实例并发度为2,最后点击部署按钮。

在Apifox工具上点击自动化测试按钮,并在配置页面新增测试场景。

在压力测试的配置界面点击添加步骤-添加自定义请求按钮,参考先前步骤填写请求信息,填写完成后点击保存按钮。

返回压力测试场景配置页面,将线程数设置为10,即同时并发执行的线程数一共10个。

点击运行按钮(模拟请求流量波峰),监控fun-alarm-email-send函数详情页面的实例列表,将发现实例从0个快速增长至5个实例(10 / 2 = 5),等待3-5分钟后(模拟请求流量波谷),5个函数实例都将销毁。
