用华为云 FunctionGraph 与 OBS 搭建图片自动缩略图服务:从开通到上线的完整实战
在内容型应用里,图片几乎是最容易被低估的成本项。一张手机直出的照片动辄几兆,列表页一次加载几十张原图,流量、首屏时间和存储费用都会跟着上涨。更麻烦的是,靠前端临时压缩并不可靠:不同机型的浏览器表现不一,重复上传还会产生重复处理。把压缩这件事交给云端、让它在图片上传完成后自动发生,是更稳妥的做法。本文以华为云对象存储服务 OBS 与函数工作流 FunctionGraph 为例,介绍如何搭建一个图片自动缩略图服务:用户上传原图,系统自动生成缩略图并存入另一个桶,应用直接使用缩略图地址,全程不需要维护服务器。
一、方案与架构
整体链路只有四步。第一步,应用把原图上传到 OBS 的源桶。第二步,源桶配置事件通知,对象创建事件触发 FunctionGraph 函数。第三步,函数下载原图,用图像处理库生成指定宽度的缩略图,再把结果上传到目标桶。第四步,应用读取目标桶里的缩略图地址展示,必要时再叠加 CDN 加速。这个架构的优点很明确:上传与压缩解耦,应用不需要等待压缩完成;函数按调用次数计费,没有图片上传时不产生计算费用;原图与缩略图分桶存放,权限可以分别控制,清理策略也可以分开制定。需要注意的是,触发器只负责通知,真正的处理逻辑都在函数里,所以函数要做好幂等:同一张图重复触发时,重复生成的结果应当覆盖同一目标对象,而不是产生重复文件。
二、准备工作
开始之前,先准备两个 OBS 桶:一个存放原图,一个存放缩略图。两个桶建议放在同一区域,这样函数访问存储的延迟更低,也不容易产生跨区域流量。接着在 FunctionGraph 里创建一个 Python 函数,运行时建议选择较新的 Python 版本。函数需要访问 OBS 的权限,最规范的做法是给函数配置委托,让函数以委托身份访问 OBS,而不是把长期密钥写进代码或环境变量里。委托只需要授予源桶的读权限与目标桶的写权限,遵循最小权限原则。最后确认函数的内存与超时设置:处理图片是内存密集型操作,内存太小容易失败,超时太短则大图还没处理完就被中断,初始值可以设为 512MB 内存与 30 秒超时,再根据实测调整。
三、核心代码
函数入口接收 OBS 事件,从事件中取出对象键,再按原图路径生成对应的缩略图路径。下面的示例把宽度固定为 320 像素,高度按比例缩放,输出为 JPEG 格式,并把缩略图统一放到 thumbs/ 目录下,便于在目标桶里集中管理。桶名、宽度与服务端地址都从环境变量读取,换一个桶只需改配置,不用改代码。临时凭证由函数委托提供,不在代码中存放任何密钥。以下为简化后的核心代码,实际使用时按所选 SDK 的版本调整客户端初始化方式。
import os
from io import BytesIO
from PIL import Image
from obs import ObsClient
SRC_BUCKET = os.environ.get("SRC_BUCKET", "")
DST_BUCKET = os.environ.get("DST_BUCKET", "")
ENDPOINT = os.environ.get("OBS_ENDPOINT", "")
WIDTH = int(os.environ.get("THUMB_WIDTH", "320"))
def handler(event, context):
key = event["Records"][0]["s3"]["object"]["key"]
if key.startswith("thumbs/"):
return "skip"
client = ObsClient(server=ENDPOINT)
resp = client.getObject(SRC_BUCKET, key)
image = Image.open(BytesIO(resp["body"].read()))
image.thumbnail((WIDTH, 100000))
buf = BytesIO()
image.convert("RGB").save(buf, format="JPEG", quality=82)
buf.seek(0)
client.putContent(DST_BUCKET, "thumbs/" + key, buf)
return "ok"
代码里有两处细节值得注意。第一,先判断对象键是否已经以 thumbs/ 开头,避免函数处理自己生成的缩略图造成循环触发。第二,处理失败时直接抛错,让 FunctionGraph 的日志里留下完整堆栈,而不是悄悄返回成功,否则问题会被掩盖,事后很难定位。
四、配置 OBS 触发器
代码写好之后,在函数上配置 OBS 触发器。选择源桶,事件类型选择对象创建事件,并把前缀限制在原图目录,例如 uploads/,这样管理后台上传的临时文件就不会触发函数。触发器保存后,函数与 OBS 之间就建立了委托关系,第一次配置时需要确认授权。配置完成后,可以直接在控制台上传一张测试图片,观察函数是否被触发。触发器有一个容易忽略的点:对象覆盖上传也会产生创建事件,更新同一张图会重新生成缩略图,这通常是期望的行为,但如果业务上不希望覆盖就重新生成,需要在函数里比对原图与缩略图的时间戳再决定是否处理。
五、依赖打包与部署
Pillow 是最常用的 Python 图像处理库,但它不是标准库的一部分,需要随函数一起部署。常见做法有两种:把 Pillow 与代码打进同一个部署包,或者做成函数依赖层供多个函数复用。部署包里的依赖要与函数运行时的系统架构一致,本地打包时建议使用与线上一致的 Linux 环境,否则可能出现导入失败。部署之后,先在控制台用测试事件调用一次函数,确认依赖加载正常、日志没有导入错误,再打开触发器接收真实事件。测试事件的格式可以参照 OBS 事件通知的实际结构,只保留必要的字段,方便反复验证。
六、验证与常见问题排查
验证分为三步。第一步,在源桶上传一张图片,等待几秒后检查目标桶是否出现对应的缩略图对象。第二步,直接访问缩略图的地址,确认图片可以正常显示、尺寸符合预期。第三步,查看函数的运行日志,确认每次调用的耗时与内存占用。如果目标桶没有生成缩略图,先看触发器是否生效,再看函数日志:权限不足通常会报明确的拒绝访问错误,超时则需要调大内存或超时时间。还有一种常见情况是图片格式不受支持,例如某些特殊格式的源文件,这时可以在函数入口先校验文件后缀,不支持的格式直接跳过并记录日志,避免因为一张坏图阻塞整个链路。
七、成本与优化建议
这套方案的成本主要由三部分组成:OBS 存储费用、FunctionGraph 调用与执行费用,以及数据流出流量。日常优化可以从几个方面入手:按访问热度选择存储类别,历史原图可以转入低频访问存储;缩略图尺寸按实际展示宽度设置,不要生成超过需要的超大缩略图;函数内存设置要实测而非一味调大,内存越大单价越高,过量配置反而浪费;热点图片可以再叠加 CDN,减少回源次数。冷启动也值得关注:函数长时间没有调用后首次调用会有额外延迟,如果业务对延迟敏感,可以评估预留实例或保持低频调用来维持热度,但这会增加成本,需要根据业务实际权衡。
八、总结与扩展
回顾整个方案,它的核心思路是把图片处理从业务服务器剥离,变成由存储事件驱动的自动化环节:上传触发函数,函数生成缩略图,应用只关心最终地址。代码不到一百行,部署后基本不需要日常维护。这个思路还可以继续扩展:同一条链路可以再生成多档尺寸,适配列表与详情两种场景;可以在生成缩略图时叠加水印;也可以把输出格式改成体积更小的现代格式,进一步节省流量。先把最小链路跑通,再按需加功能,是这类云端自动化任务最稳妥的推进方式。
- 点赞
- 收藏
- 关注作者
评论(0)