LOADING

加载过慢请开启缓存 浏览器默认开启

基于阿里云函数计算的简单邮件发送服务设计与体验

基于阿里云函数计算的简单邮件发送服务设计与体验


基本要求

  • 理解掌握函数计算FC的设计部署过程
  • 理解体验函数计算FC的弹性伸缩能力

实验原理

Serverless技术与无服务器模式

云计算是继传统CS架构之后的新一代互联网应用架构,是一种基于互联网的计算方式,用户不再需要了解硬件基础设施的细节,也不需直接进行控制。云计算通过互联网将算力以按需使用、按量付费的形式提供给用户,其两个最明显的优势是弹性和敏捷:弹性即按需使用各类服务,灵活扩缩容,应对业务流量的不确定性;敏捷即能快速部署应用而无需购买任何物理资源。

Serverless是一种云计算执行模型,使得开发者不用过多考虑服务器的问题,允许在服务部署级别而不是服务器部署级别来管理应用部署。与传统架构的不同之处在于,服务的提供完全由第三方管理。服务以函数的方式托管在Serverless框架下,当请求到达时会启动一个容器运行代码,无请求时不存在容器实例。

2017年国内三大云服务厂商均推出了自己的Serverless服务:阿里云FC、华为云FG、腾讯云SCF,现已发展成熟,服务稳定、社区活跃、教程丰富。

无服务器模式即将业务逻辑以Serverless执行模型部署在云上。无服务器指的是部署时根本不关注服务器,不需要用户提供服务器或者购买云服务器,而不是没有服务器。使用Serverless云计算执行模型时,用户一般只需要将代码上传到指定仓库,云厂商就会自动完成编译、部署等流水线工作,服务直接可用。

与传统架构的不同之处在于,服务器和集群都由云厂商提供、分配、维护,这些计算平台对用户透明。它的好处是稳定可靠、减少服务器运维工作量,且相比云服务器成本更低。

阿里云函数计算FC

函数计算是 Serverless 架构的一种形态,面向函数编程,基于事件驱动提供阿里云服务之间端到端的解决方案。借助函数计算,开发者可以快速构建任何类型的应用和函数,并且只需为任务实际消耗的资源付费。

阿里云函数计算是事件驱动的全托管计算服务。通过函数计算,开发者无需管理服务器等基础设施,只需专注于业务代码。函数计算服务会准备好计算资源,以弹性、可靠的方式运行代码,并提供日志查询、性能监控、按需报警等功能。

架构

函数计算涉及函数、实例、运行环境、触发器、层、应用中心等功能组件,具体产品组件架构图如下图所示。

屏幕截图 2024-11-11 153344

基本术语
  • 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框架并未包含在阿里云的官方公共层列表中,因此需要创建一个自定义的公共层来满足需求。

自定义公共层创建步骤如下:

屏幕截图 2024-11-19 171937

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

屏幕截图 2024-11-19 172054

构建并部署告警邮件发送接口

创建Web函数

屏幕截图 2024-11-19 172136

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

屏幕截图 2024-11-19 172246

基于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)               

屏幕截图 2024-11-19 172318

代码修改完成后,点击IDE右上角上面的编辑层按钮,修改默认的公共层配置。

先删除现有的官方公共层Flask,再点击添加层-添加自定义层按钮选择之前创建的sanic-c-custom-layer层,最后点击部署按钮。

屏幕截图 2024-11-19 172448

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

屏幕截图 2024-11-19 172522

获取函数外网访问地址

在函数详情页面,将光标放在触发器矩形框上,将显示公网访问的地址,该域名由 CNCF SandBox项目 Serverless Devs 社区所提供,仅供学习和测试使用,社区会对该域名进行不定期地拨测,并在域名下发 30 天后进行回收。

有条件可以参考 函数计算 FC控制台-域名管理 页面提示进行自定义域名配置。

屏幕截图 2024-11-19 172630

开启函数日志功能

开启日志功能后,可以实时查看该函数中打印到控制台日志,从而便于进行代码测试、故障分析等操作。点击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": "告警邮件正文部分" // 根据实际情况填写,此处仅做模拟
}

屏幕截图 2024-10-12 153803

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

屏幕截图 2024-10-12 153813

体验函数计算FC的弹性伸缩能力

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

屏幕截图 2024-11-19 172915

为了更直观观察到自动扩缩容的过程,可以设置函数的单实例并发度为较低的值,并借助Apifox工具对邮件发送接口/send进行压力测试。其中,单实例并发度指每个应用实例能够同时处理的请求次数上限,当单个实例的并发请求数达到上限,才会创建新的实例。

点击函数详情页面的配置-运行时按钮,在运行时配置展示页面点击编辑按钮,进入运行时的编辑页面,修改单实例并发度为2,最后点击部署按钮。

屏幕截图 2024-11-19 173153

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

屏幕截图 2024-11-19 173330

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

屏幕截图 2024-11-19 173519

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

屏幕截图 2024-11-19 173603

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

屏幕截图 2024-11-19 173719