华为云 FunctionGraph 实战:定时触发、OBS 事件驱动与冷启动优化

举报
yd_292302676 发表于 2026/10/30 09:44:51 2026/10/30
【摘要】 用函数工作流替掉一台只为跑定时脚本的常驻 ECS:定时触发器与 UTC 时区换算、OBS 事件驱动的幂等与递归触发防护、冷启动的四个优化手段,附 Python 代码、方案对比表和踩坑清单。

项目里有一台 ECS,唯一的作用是每天凌晨跑一次清理脚本,把对象存储里七天前的临时文件删掉。这台机器 4 核 8G,月账单六十多块,而它的 CPU 平均利用率是 0.3%——99% 的时间在空转,剩下 1% 里还有大半在等 IO。后来把这套逻辑搬到函数工作流上,代码量没变,账单直接归零(在免费额度内)。这篇把迁移过程中的配置细节和几个真实的坑写清楚。

一、先判断这件事该不该用函数

函数工作流不是万能的。判断标准只有一条:这段逻辑是不是"短时、偶发、无状态"。满足就用,不满足就别硬上。

方案 适合场景 主要代价
ECS 常驻 长连接、本地缓存、需要固定出口 IP 空转也要付整机钱,还要自己运维补丁
CCE CronJob 已有集群、任务较重、需要镜像环境 集群本身有固定成本,节点闲置照样计费
函数工作流 定时清理、事件回调、轻量数据转换 单次执行有超时上限,冷启动有延迟

我这次的需求是典型的第三种:每天跑一次,单次十几秒,跑完就没事。换成函数之后,没有机器要维护,也没有闲置成本。

二、定时触发:清理 OBS 临时对象

在函数工作流控制台创建函数,运行时选 Python 3.9,内存 256MB 就够,超时时间从默认的 3 秒改到 60 秒——这一点很关键,后面会讲。

依赖方面,对象存储的 SDK 需要随代码一起上传。做法是在本地建一个目录,把 SDK 装在目录里,再连代码一起打成 zip:

mkdir -p pkg && cd pkg
pip install esdk-obs-python -t .
# 代码放到根目录,然后整体压缩上传

函数本体不长,核心是把列举和删除做成翻页循环,不要一次性拉全量:

import os
from datetime import datetime, timedelta, timezone
from obs import ObsClient

BUCKET = os.environ.get('BUCKET', 'demo-temp')
PREFIX = os.environ.get('PREFIX', 'tmp/')
KEEP_DAYS = int(os.environ.get('KEEP_DAYS', '7'))

client = ObsClient(
    access_key_id=os.environ['OBS_AK'],
    secret_access_key=os.environ['OBS_SK'],
    server=os.environ['OBS_ENDPOINT'])

def handler(event, context):
    deadline = (datetime.now(timezone.utc) - timedelta(days=KEEP_DAYS)).isoformat()
    scanned, deleted, marker = 0, 0, None
    while True:
        resp = client.listObjects(BUCKET, prefix=PREFIX,
                                  marker=marker, max_keys=1000)
        if resp.status_code >= 300:
            return {'ok': False, 'msg': resp.errorMessage}
        for obj in resp.body.contents:
            scanned += 1
            if obj.lastModified < deadline:
                client.deleteObject(BUCKET, obj.key)
                deleted += 1
        if not resp.body.is_truncated:
            break
        marker = resp.body.next_marker
    return {'ok': True, 'scanned': scanned, 'deleted': deleted}

几个容易忽略的点:lastModified 是 UTC 的 ISO8601 字符串,和同样格式的 deadline 直接做字符串比较就是正确的时间比较,不需要转成 datetime;翻页一定要用 is_truncated 判断,靠"返回条数小于 1000"来结束循环在并发写入的桶里会漏数据。

定时触发器填 Cron 表达式,格式是六段(秒 分 时 日 月 周),每天凌晨三点写成 0 0 3 * * ?。注意时区是 UTC,想让它在北京时间三点跑,实际要写 0 0 19 * * ?。这个坑我踩过一次,第二天早上发现任务在上午十一点执行了。

三、事件驱动:上传即处理

同一个函数还可以挂对象存储的事件触发器,比如图片上传后自动生成缩略图。触发器配置里选事件类型 ObjectCreated:Put,指定桶和前缀即可,不需要写轮询逻辑。

def handler(event, context):
    for rec in event.get('Records', []):
        bucket = rec['s3']['bucket']['name']
        key = rec['s3']['object']['key']
        etag = rec['s3']['object']['eTag']
        if is_done(bucket, key, etag):   # 幂等判断
            continue
        process(bucket, key)
    return {'ok': True}

幂等必须自己做。事件通知是至少一次投递,网络抖动、函数超时重试都会让同一个对象被处理两次。最省事的做法是把 对象名 + eTag 记一张表,处理前查一下。

更要命的是递归触发。如果函数把处理结果写回同一个桶的同一个前缀,写入动作又会触发一次事件,函数再处理、再写回……几秒钟就能把调用次数刷到几十万。规避方式只有两个:结果写到不同的前缀(比如 thumb/ 对 raw/),或者干脆写到另一个桶。我在测试环境用错前缀跑出过一次,靠配额告警才发现。

四、冷启动:四个真正有效的动作

函数按需拉起,第一次调用要初始化运行环境,这就是冷启动。实测下来,Python 运行时从零到就绪在 200ms 到 2s 之间,取决于依赖体积。四个改善手段按性价比排序:

1. 把初始化逻辑挪到 handler 外面。模块级代码只在实例创建时执行一次,后续复用同一实例的调用都能省掉这段开销。SDK 客户端、数据库连接、配置读取都该放在外面——这也是上面代码里 ObsClient 写在函数体之外的原因。

2. 压缩依赖体积。SDK 和第三方库是冷启动时间的大头。只装真正用到的包,别把整个数据分析栈打进去;体积大的公共依赖做成层,多个函数共用一份。

3. 合理提高内存。函数工作流的 CPU 配额和内存是绑定的,内存给太小,初始化阶段反而慢。把 128MB 提到 512MB,单次费用略增,但执行时间缩短,总成本经常是下降的。

4. 预留实例。对延迟敏感的场景(比如同步接口回调),直接配置预留实例,让实例常驻,冷启动就消失了。代价是按预留时长计费,只适合调用频繁的函数,一天跑一次的定时任务配它纯属浪费。

五、观测与成本

函数默认把日志投递到云日志服务,每个函数一个日志组,排查时按请求 ID 检索最方便。指标面板重点看四个:调用次数、错误次数、最大运行时长、并发数。最大运行时长持续贴近超时阈值,说明该拆任务或调大超时;错误率突然抬升,多半是权限或下游限流。

计费由两部分组成:调用次数和资源消耗(内存 × 执行时长)。按这个模型,一天跑一次、每次十几秒的清理任务,成本几乎可以忽略;反过来,一个被高频触发、每次跑满 300 秒的函数,费用可能比一台小规格 ECS 还高。上线前把"每月调用次数 × 平均时长"估一遍,比事后看账单靠谱。

六、踩坑清单

最后把这次迁移遇到的问题集中列一下,都是配置层面能避开的:

超时时间默认 3 秒,涉及批量列举或大文件处理一定要提前调大,否则表现为"偶发超时";Cron 表达式按 UTC 计算,本地时间要换算;函数的返回体必须能被 JSON 序列化,返回 SDK 的响应对象会直接报错;访问其他云服务需要在函数配置里绑定委托,只给 AK/SK 在临时凭证轮换后会失效;环境变量里的密钥明文可见,敏感信息走委托或密钥管理服务更稳妥。

整件事做下来,最大的收获不是省了那六十块钱,而是把"一台机器只为了跑一个脚本"这种结构性问题消掉了。这类任务在云上很常见,先看一眼它的资源利用率,再决定要不要为它留一台机器。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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