3. Cloud Functions事件驱动函数开发
Cloud Functions 是 Google Cloud 上最轻量的计算形态:你只提交一段函数代码,平台负责运行环境、扩缩容、补丁与可用性。它的核心特征是事件驱动——函数不会自己常驻等待,而是由某个事件(HTTP 请求、消息到达、对象变更、定时器)触发一次执行。理解“谁触发、传什么、返回什么、失败怎么办”,比记住 API 名称更重要。
两类触发机制:HTTP 触发与后台触发
Cloud Functions 的触发方式可归为两大类。第一类是 HTTP 触发:平台为函数分配一个 HTTPS 端点,任何能发 HTTP 请求的客户端(浏览器、移动端、另一个服务、curl)都可以调用。函数接收请求对象,返回响应体、状态码与响应头,语义上等价于一个极简 Web 服务。第二类是后台触发(事件触发):函数订阅某个事件源,事件发生时由平台把事件数据推给函数。常见事件源包括 Cloud Storage 的对象创建/删除、Pub/Sub 主题消息、Firestore 文档写入、Cloud Audit Logs,以及 Cloud Scheduler 配合 Pub/Sub 实现的定时触发。
两者的关键差异在于“有没有调用方在等结果”。HTTP 触发有同步调用方,函数必须返回响应;后台触发没有等待者,函数返回值的意义有限,真正重要的是是否成功处理——失败时平台会按策略重试。因此后台函数必须写成幂等的:同一事件可能被投递多次,重复执行不能产生重复副作用(例如重复扣款、重复发信)。
编写:入口函数与事件对象
无论哪种触发,函数代码都要暴露一个入口函数,部署时用 --entry-point 指定其名称。HTTP 函数签名通常接收请求与响应对象;后台函数接收事件数据与上下文对象。以 Node.js 为例,一个处理 Cloud Storage 新对象的后台函数大致如下:
// index.js —— 后台触发:GCS 对象创建
const { Storage } = require('@google-cloud/storage');
const storage = new Storage();
exports.onImageUploaded = async (event, context) => {
const file = event; // 事件数据:bucket、name、size...
const bucket = file.bucket;
const name = file.name;
console.log(`新对象: gs://${bucket}/${name}, 大小 ${file.size} 字节`);
// 幂等保护:先检查是否已处理过(示例用元数据标记)
const gcsFile = storage.bucket(bucket).file(name);
const [meta] = await gcsFile.getMetadata();
if (meta.metadata && meta.metadata.processed === 'true') {
console.log('已处理,跳过');
return;
}
// ... 业务处理(如生成缩略图、写 Firestore)...
await gcsFile.setMetadata({ metadata: { processed: 'true' } });
};
HTTP 函数则更接近普通 Web 处理器:读取 req.method、req.query、req.body,用 res.status(200).send(...) 返回。注意 Cloud Functions 的 HTTP 函数不会自动解析 JSON 请求体,需要按内容类型自行解析,或使用框架(如 Express)包装。
部署:一条命令背后的关键参数
部署命令是学习重点,因为触发类型、运行时、入口、内存、超时都在这里确定。以 gcloud 为例:
# HTTP 触发
gcloud functions deploy helloHttp \
--gen2 --runtime=nodejs20 --region=asia-east1 \
--source=. --entry-point=helloHttp \
--trigger-http --allow-unauthenticated
# 后台触发:Pub/Sub 主题
gcloud functions deploy processOrder \
--gen2 --runtime=python312 --region=asia-east1 \
--source=. --entry-point=process_order \
--trigger-topic=order-events \
--retry
几个参数值得逐一理解:--runtime 决定基础镜像与语言版本;--entry-point 必须与代码中导出的函数名一致,否则部署成功但调用报“function not found”;--trigger-http 与 --trigger-topic 互斥,决定触发类型;--allow-unauthenticated 允许匿名访问,生产环境通常应改为要求身份认证;--retry 让后台函数在失败时自动重试,代价是必须处理重复投递;--memory 与 --timeout 影响可处理的数据规模与最长执行时间。部署完成后,控制台会给出函数详情页,其中“触发器”标签页显示事件源与端点 URL,“日志”标签页可查看每次执行的输出。
本地调试:Functions Framework
把函数部署到云端再调试,反馈太慢。Google 提供的 Functions Framework 让你在本地以 HTTP 服务方式运行函数,模拟平台调用。以 Node.js 为例,安装后执行:
npm install @google-cloud/functions-framework
npx functions-framework --target=helloHttp --port=8080
# 另开终端
curl -X POST http://localhost:8080 -H "Content-Type: application/json" \
-d '{"name":"Ada"}'
对后台函数,本地没有真实事件源,需要手动构造事件载荷。Functions Framework 支持通过 --signature-type=event 启动,并用 curl 把事件 JSON 作为请求体发送,从而在本地复现“事件到达”的路径。这样可以在不部署的情况下验证解析逻辑、依赖是否齐全、异常分支是否被覆盖。
测试与结果判断
测试要覆盖三类情况:正常输入、边界输入、失败输入。对 HTTP 函数,用 curl 或测试框架断言状态码与响应体;对后台函数,重点验证幂等性与重试行为——可以故意让第一次执行抛错,观察平台是否重投,以及第二次执行是否因幂等标记而安全跳过。判断一次开发是否达标的依据不是“部署成功”,而是:触发后函数被调用、日志中出现预期输出、下游数据(如 Firestore 文档、GCS 对象)状态正确、失败路径可被观测且不会造成重复副作用。