4、云函数触发器:API网关触发器、定时触发器、对象存储触发器、数据库触发器

触发器这东西,说白了就是云函数的「开关」。没有触发器,你的函数就是一潭死水,啥也干不了。我刚开始接触Serverless时,总觉得触发器就是简单的「谁调谁」,后来踩了不少坑才明白——每种触发器的脾气都不一样,用错了地方,轻则浪费钱,重则线上事故。

今天咱们就把四种最常用的触发器掰开揉碎讲清楚。嗯,这四种基本覆盖了90%以上的业务场景。

4.1 API网关触发器——最常用的入口

API网关触发器,说白了就是把HTTP请求转成函数调用。你写一个RESTful接口,用户发个GET/POST请求,网关收到后直接触发你的云函数。

我个人习惯把API网关触发器分成两类:

  • 代理集成:网关把原始请求原封不动丢给函数,你自己解析HTTP头、body、参数。灵活,但代码量大。
  • 非代理集成:网关帮你解析好,函数收到的就是格式化好的JSON。省事,但有些高级功能受限。

我在项目中遇到过一个问题:用非代理集成时,网关默认只支持JSON格式的响应。有一次前端非要返回XML,折腾了半天才发现是网关层给拦截了。后来我统一改用代理集成,虽然多写几行解析代码,但心里踏实。

核心要点:API网关触发器适合对外暴露接口,比如用户注册、数据查询、文件上传。但要注意——网关有超时限制,通常30秒左右。长时间任务别走这条路。

来看一个简单的示例,腾讯云的SCF配合API网关:

exports.main_handler = async (event, context) => {
    // event里包含了HTTP请求的全部信息
    const { httpMethod, path, headers, body } = event;
    
    if (httpMethod === 'GET') {
        return {
            isBase64Encoded: false,
            statusCode: 200,
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ message: 'Hello from Serverless!' })
        };
    }
    
    if (httpMethod === 'POST') {
        const data = JSON.parse(body);
        // 处理业务逻辑...
        return {
            statusCode: 201,
            body: JSON.stringify({ id: 'new_record_id' })
        };
    }
};

避坑指南:我曾经在API网关的CORS配置上卡了整整一天。前端发OPTIONS预检请求,网关直接返回403。后来发现——网关的CORS配置和函数的CORS配置是两回事。建议在网关层统一处理CORS,别让函数背这个锅。

4.2 定时触发器——像闹钟一样准时

定时触发器,就是给云函数设个闹钟。到点了,自动执行。你想想看,每天凌晨清理日志、每小时同步数据、每5分钟检查服务健康状态——这些场景都离不开它。

定时触发器的配置方式一般有两种:

方式 格式 示例 说明
Cron表达式 秒 分 时 日 月 星期 0 0 3 * * * 每天凌晨3点执行
固定频率 每N分钟/小时/天 每30分钟 简单粗暴,适合新手

我个人更推荐用Cron表达式,虽然写起来麻烦点,但精确到秒,而且可以避开业务高峰期。比如我习惯把数据备份任务设在凌晨4:15,因为4点整是很多系统的自动运维时间,错开能避免资源争抢。

注意:定时触发器的执行时间不是绝对精确的。云平台通常有±1秒的误差。如果你的业务要求毫秒级准时,定时触发器可能不适合你。另外,函数执行时间不能超过定时周期——比如你设了每5分钟执行一次,但函数跑了10分钟还没结束,下一次触发会被跳过或排队。

代码示例,一个简单的定时清理任务:

exports.main_handler = async (event, context) => {
    // event里包含触发时间信息
    const { Time } = event;
    console.log(`定时任务触发于: ${Time}`);
    
    // 清理7天前的临时文件
    const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
    const oldFiles = await listTempFiles(sevenDaysAgo);
    
    for (const file of oldFiles) {
        await deleteFile(file.path);
        console.log(`已删除: ${file.name}`);
    }
    
    return `清理完成,共删除 ${oldFiles.length} 个文件`;
};

4.3 对象存储触发器——文件一上传,函数就干活

对象存储触发器,就是监听存储桶里的文件变化。上传、删除、修改,任何一个动作都能触发函数执行。这玩意儿在图片处理、视频转码、日志分析场景下特别好用。

为什么会这么方便?你想想看,传统架构里你要写个定时任务去轮询文件夹有没有新文件,又low又费资源。对象存储触发器是事件驱动的——文件来了,函数立刻执行,零延迟。

我在项目中遇到过最典型的场景是图片压缩。用户上传一张10MB的高清图,触发器自动调用函数压缩成200KB的缩略图,整个过程用户无感知。嗯,这里要注意——函数执行时间不能太长,否则用户上传完图片要等半天才能看到缩略图。

事件类型:一般支持以下几种——

  • cos:ObjectCreated:Put(上传完成)
  • cos:ObjectCreated:Post(通过表单上传)
  • cos:ObjectCreated:Copy(复制文件)
  • cos:ObjectRemove:Delete(删除文件)

代码示例,监听图片上传并生成缩略图:

exports.main_handler = async (event, context) => {
    // 解析存储桶事件
    const cosEvent = event.Records[0];
    const bucket = cosEvent.cos.bucket.name;
    const key = cosEvent.cos.object.key;
    
    // 只处理图片文件
    if (!key.match(/\.(jpg|jpeg|png|gif)$/i)) {
        return '非图片文件,跳过处理';
    }
    
    // 下载原图
    const imageBuffer = await downloadFromCOS(bucket, key);
    
    // 生成缩略图(假设用sharp库)
    const thumbnail = await sharp(imageBuffer)
        .resize(200, 200)
        .toBuffer();
    
    // 上传缩略图
    const thumbKey = key.replace(/(\.\w+)$/, '_thumb$1');
    await uploadToCOS(bucket, thumbKey, thumbnail);
    
    return `缩略图已生成: ${thumbKey}`;
};

避坑指南:我曾经犯过一个低级错误——在对象存储触发器里又去写同一个存储桶,结果触发了无限循环。函数上传文件 → 触发函数 → 又上传文件 → 又触发函数... 最后账单爆了。解决方案很简单:在函数里加个判断,只处理特定前缀或后缀的文件,或者把输出文件放到另一个存储桶。

4.4 数据库触发器——数据变了,通知我

数据库触发器,就是监听数据库的变化。增、删、改,任何一个操作都能触发函数。这玩意儿在数据同步、缓存更新、审计日志场景下特别好用。

说白了,数据库触发器就是「Change Data Capture(CDC)」的Serverless实现。传统做法要装个Debezium或者Canal,配置起来麻烦得很。云函数直接帮你搞定,你只需要写业务逻辑。

我个人习惯把数据库触发器用在两个场景:

  • 缓存更新:数据库里的用户信息改了,触发器自动刷新Redis缓存
  • 数据同步:主库写入数据,触发器同步到ES或数仓

来看一个腾讯云TDSQL触发器的示例:

exports.main_handler = async (event, context) => {
    // event里包含数据库变更记录
    const { Records } = event;
    
    for (const record of Records) {
        const { eventName, eventSource, tableName } = record;
        const newImage = record.dynamodb?.NewImage;
        const oldImage = record.dynamodb?.OldImage;
        
        console.log(`操作类型: ${eventName}`);
        console.log(`表名: ${tableName}`);
        
        if (eventName === 'INSERT') {
            // 新数据插入,同步到缓存
            await redis.set(`user:${newImage.id}`, JSON.stringify(newImage));
        }
        
        if (eventName === 'MODIFY') {
            // 数据更新,刷新缓存
            await redis.del(`user:${oldImage.id}`);
            await redis.set(`user:${newImage.id}`, JSON.stringify(newImage));
        }
        
        if (eventName === 'REMOVE') {
            // 数据删除,清理缓存
            await redis.del(`user:${oldImage.id}`);
        }
    }
    
    return `处理了 ${Records.length} 条变更记录`;
};

重要提醒:数据库触发器是「最终一致性」的。什么意思?就是数据库写入成功后,触发器可能延迟几毫秒甚至几秒才触发。如果你的业务要求「写入后立即读取到最新数据」,别依赖触发器,直接在业务代码里同步更新缓存。

4.5 四种触发器怎么选?

最后做个总结,方便你快速决策:

触发器类型 适用场景 延迟 注意事项
API网关 对外接口、RESTful API 毫秒级 超时限制30秒,不适合长任务
定时触发器 定时任务、数据清理、报表生成 秒级 执行时间不能超过周期
对象存储 图片处理、视频转码、日志分析 毫秒级 小心无限循环,区分输入输出桶
数据库 数据同步、缓存更新、审计日志 毫秒~秒级 最终一致性,不适合强一致场景

嗯,四种触发器讲完了。你想想看,其实每种触发器都有自己的「脾气」。API网关适合对外,定时触发器适合后台,对象存储适合文件处理,数据库触发器适合数据联动。用对了地方,你的Serverless应用就能跑得又快又稳。

下一章咱们聊聊「云函数间的通信与编排」,到时候会讲怎么把多个函数串起来完成复杂业务。敬请期待。