Serverless 实战:用华为云 FunctionGraph + OBS 构建弹性图片处理流水线

举报
yd_275936632 发表于 2026/11/03 10:09:53 2026/11/03
【摘要】 图片处理是很多业务的"隐形基础设施":商品图要生成多规格缩略图,用户头像要裁剪压缩,内容平台要统一转码。传统做法是部署一台常驻的图片处理服务,但它的资源曲线很尴尬——白天峰值时 CPU 打满,深夜又长期闲置。本文以华为云对象存储 OBS 与函数工作流 FunctionGraph 为例,搭建一条"上传即处理"的事件驱动流水线,把服务器的运维成本换成按需计费。一、为什么选择事件驱动图片处理有三个...

图片处理是很多业务的"隐形基础设施":商品图要生成多规格缩略图,用户头像要裁剪压缩,内容平台要统一转码。传统做法是部署一台常驻的图片处理服务,但它的资源曲线很尴尬——白天峰值时 CPU 打满,深夜又长期闲置。本文以华为云对象存储 OBS 与函数工作流 FunctionGraph 为例,搭建一条"上传即处理"的事件驱动流水线,把服务器的运维成本换成按需计费。

一、为什么选择事件驱动

图片处理有三个典型特征:触发时机随机、单次任务短、峰值差异大。这正好落在 Serverless 的能力区间里。把 OBS 的 ObjectCreated 事件作为触发器,函数只在有新对象写入时启动,处理完立即释放资源。业务侧不需要预估容量,也不需要维护常驻进程;流量低谷时成本趋近于零,突然放量时由平台自动横向扩展。

整条链路可以概括为四步:

  1. 客户端把原图上传到 OBS 的 uploads/ 前缀;
  2. OBS 事件通知投递到 FunctionGraph;
  3. 函数下载原图、生成缩略图并写回 thumbs/;
  4. 业务系统直接读取缩略图地址。

二、准备资源与权限

先创建两个 OBS 桶:一个接收原图,一个存放处理结果。分开建桶有两个好处:便于设置不同的生命周期策略,也能避免输出对象再次触发函数形成回环。

权限方面,推荐使用 IAM 委托(agency)而不是在代码里硬编码永久 AK/SK。给函数绑定一个只允许访问指定桶的委托,权限收敛到 obs:object:GetObject 与 obs:object:PutObject 即可。临时凭证由平台自动轮换,泄露风险远低于写死在环境变量里的密钥。

三、函数代码实现

函数选择 Python 运行时,依赖 esdk-obs-python 与 Pillow。把依赖打包进代码包,或放到 OBS 后通过层(Layer)挂载。

import io
import os
from obs import ObsClient
from PIL import Image

ENDPOINT = os.environ["OBS_ENDPOINT"]
OUTPUT_BUCKET = os.environ["OUTPUT_BUCKET"]
client = ObsClient(
    access_key_id=os.environ["AK"],
    secret_access_key=os.environ["SK"],
    server=ENDPOINT,
)

def handler(event, context):
    for record in event.get("Records", []):
        bucket = record["obs"]["bucket"]["name"]
        key = record["obs"]["object"]["key"]

        # 只处理上传前缀,防止输出桶回环触发
        if not key.startswith("uploads/"):
            continue

        resp = client.getObject(bucket, key)
        if resp.status >= 300:
            raise RuntimeError("download failed: %s" % resp.status)

        img = Image.open(io.BytesIO(resp.body.read()))
        img.thumbnail((1280, 1280))

        buf = io.BytesIO()
        img.convert("RGB").save(buf, format="JPEG", quality=85, optimize=True)

        name = os.path.splitext(key.split("/", 1)[1])[0]
        client.putContent(OUTPUT_BUCKET, "thumbs/%s.jpg" % name, buf.getvalue())

    return {"ok": True}

四、配置 OBS 事件触发器

在函数详情页创建触发器,类型选择 OBS,绑定原图桶,事件类型勾选 ObjectCreated,并设置前缀过滤 uploads/。建议只订阅需要处理的后缀(如 .jpg、.png),减少无效调用。

触发器创建完成后,上传一张测试图片验证:函数日志中应出现本次对象的 bucket 与 key,输出桶的 thumbs/ 目录下会生成同名 JPEG。

五、生产环境必须处理的六个细节

1. 防回环。 如果输入输出使用同一个桶且不做前缀区分,缩略图写入会再次触发函数,形成无限循环。最稳妥的做法是输入输出分桶,并在代码里做前缀白名单。

2. 幂等。 OBS 事件是"至少一次"投递,网络抖动时同一对象可能被处理多次。可以用 对象 key + ETag 作为幂等键,处理前先检查输出对象是否存在且大小符合预期。

3. 超时与内存。 单张图片处理建议配置 512MB 内存、30 秒超时。内存过小会导致 Pillow 解码大图时被 OOM kill;超时过长则故障时占用并发配额。超大图应拆分或转为异步批处理。

4. 冷启动。 对延迟敏感的场景,为函数配置预留实例,避免首批请求等待运行时初始化。也可以通过定时触发器低频保活,但要注意保活本身也产生费用。

5. 失败重试与死信。 异步调用配置 1~2 次重试;仍失败的事件写入死信桶并触发告警,避免"图片悄悄没处理"却无人知晓。

6. 可观测性。 函数日志接入 LTS,结构化打印 requestId、bucket、key、耗时;在 CES 上为错误率、超时次数、并发使用率配置告警阈值。排障时先看日志再复现,比盲目重试高效得多。

六、成本视角的对比

维度 常驻 ECS 服务 FunctionGraph + OBS
固定成本 实例按月计费,闲时也付费 无请求时几乎为零
弹性速度 依赖伸缩组,分钟级 平台自动扩展,秒级
运维投入 系统补丁、进程守护、容量规划 只需维护函数代码
适用场景 稳定高并发、长连接 突发、低频、事件驱动

需要说明的是,Serverless 并非银弹。当图片处理 QPS 长期稳定在高位时,函数按 GB-s 累计的费用可能超过包年包月的 ECS;此时把热点路径迁回容器或裸金属反而更经济。架构选型应该跟着负载曲线走,而不是跟着概念走。

结语

用 OBS 事件触发 FunctionGraph,本质上是把"轮询任务"改造成"事件响应":少了一层调度器,也少了一台需要守护的服务器。真正决定这套方案能否长期稳定运行的,不是函数代码本身,而是防回环、幂等、重试与可观测这些工程细节。把这些约束提前写进模板和检查清单,Serverless 才能从演示 Demo 变成可以托付生产的工作负载。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。