欢迎光临
我们一直在努力

Google Cloud Functions(第2代)与Eventarc事件驱动架构实战指南

为什么选择Cloud Functions v2 + Eventarc

Google Cloud Functions 在2023年迎来了重大升级——第2代(Gen 2)基于 Cloud Run 基础设施重构,彻底改变了原先的函数即服务(FaaS)体验。相比第1代,Gen 2 带来了更长的超时时间(最高60分钟)、更大的实例内存(最高32GiB)、以及最关键的特性——通过 Eventarc 实现的事件驱动架构。

传统上,Cloud Functions 第1代只能响应有限的触发器类型(HTTP、Cloud Storage、Pub/Sub、Firestore 等),而且每个触发器的配置方式各不相同。Gen 2 + Eventarc 将这个模型统一化:Eventarc 作为事件路由层,将 Google Cloud 服务(以及第三方服务)产生的所有事件路由到 Cloud Functions。这意味着你只需要学习一种事件处理模式,就能对接数十种服务。

本指南将从实际项目角度出发,详细介绍 Cloud Functions Gen 2 的部署、配置、事件处理模式以及生产环境最佳实践。

Google Cloud 无服务器架构示意图

Cloud Functions Gen 2 核心架构

基于 Cloud Run 的底层重构

Cloud Functions Gen 2 不再使用自有的隔离运行时,而是将每个函数部署为一个 Cloud Run 服务(或者更准确地说,一个 Knative Serving 服务)。这意味着:

  • 并发请求支持:Gen 1 的每个实例一次只处理一个请求,而 Gen 2 可以并发处理多个请求,极大提高了资源利用率
  • 更长的超时:最大超时从 Gen 1 的 9 分钟/HTTP 60分钟提升到统一的 60 分钟
  • 更大的内存:最大 32 GiB(Gen 1 最大 8 GiB)
  • 更灵活的实例数:最小实例数可以设置为 0(缩零)或更高(预预热),最大实例数也可以自定义
  • 原生 gRPC 支持:Gen 1 只支持 HTTP/1.1,Gen 2 支持 HTTP/2 和 gRPC

Eventarc 事件路由层

Eventarc 是 Cloud Functions Gen 2 中最重要的新组件。它是一个事件路由和管理服务,支持来自 90+ 个 Google Cloud 服务的事件源、以及通过自定义通道接入的第三方事件。

Eventarc 使用 CloudEvents 标准格式(由 Cloud Native Computing Foundation 定义),所有事件都采用统一的结构:

{
  "specversion": "1.0",
  "id": "event-001",
  "source": "//storage.googleapis.com/projects/_/buckets/my-bucket",
  "type": "google.cloud.storage.object.v1.finalized",
  "datacontenttype": "application/json",
  "time": "2026-01-15T10:00:00Z",
  "data": {
    "bucket": "my-bucket",
    "name": "uploads/report.pdf",
    "size": 1024000,
    "contentType": "application/pdf"
  }
}

无论事件来自哪个服务,CloudEvents 保持一致的元数据结构(id、source、type、time),而具体的业务数据则放在 data 字段中。这种统一性让事件处理逻辑可以高度抽象化和可复用。

实战:部署一个事件驱动的图片处理流水线

让我们通过一个完整的实战案例来理解 Cloud Functions Gen 2 + Eventarc 的工作方式。假设我们需要构建一个自动图片处理系统:当用户上传图片到 Cloud Storage 时,自动生成缩略图,并将结果记录到 Firestore 中。

事件驱动架构流水线示意图

创建函数代码

以下是一个使用 Node.js 20 运行时的 Cloud Function Gen 2 示例:

// index.js
const functions = require('@google-cloud/functions-framework');
const {Storage} = require('@google-cloud/storage');
const {Firestore} = require('@google-cloud/firestore');
const sharp = require('sharp');

const storage = new Storage();
const firestore = new Firestore();
const THUMBNAIL_BUCKET = process.env.THUMBNAIL_BUCKET;
const THUMBNAIL_SIZE = 300;

// 注册 CloudEvent 处理函数
functions.cloudEvent('processImage', async (cloudEvent) => {
  const data = cloudEvent.data;

  // 只处理 JPEG 和 PNG 图片
  const allowedTypes = ['image/jpeg', 'image/png'];
  if (!allowedTypes.includes(data.contentType)) {
    console.log(`Skipping non-image: ${data.contentType}`);
    return;
  }

  // 忽略缩略图目录的变化,防止无限循环
  if (data.name.startsWith('thumbnails/')) {
    console.log('Skipping thumbnail upload');
    return;
  }

  const sourceBucket = data.bucket;
  const fileName = data.name;

  console.log(`Processing image: gs://${sourceBucket}/${fileName}`);

  try {
    // 下载原图
    const sourceFile = storage.bucket(sourceBucket).file(fileName);
    const [buffer] = await sourceFile.download();

    // 使用 sharp 生成缩略图
    const thumbnail = await sharp(buffer)
      .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE, {
        fit: 'cover',
        position: 'center'
      })
      .jpeg({ quality: 85 })
      .toBuffer();

    // 上传缩略图
    const thumbName = `thumbnails/${fileName.replace(/\.(jpg|jpeg|png)$/i, '.jpg')}`;
    const thumbFile = storage.bucket(THUMBNAIL_BUCKET).file(thumbName);
    await thumbFile.save(thumbnail, {
      metadata: { contentType: 'image/jpeg' }
    });

    // 记录处理结果到 Firestore
    const docRef = firestore.collection('imageProcessingLogs').doc();
    await docRef.set({
      sourceFile: `gs://${sourceBucket}/${fileName}`,
      thumbnailFile: `gs://${THUMBNAIL_BUCKET}/${thumbName}`,
      originalSize: buffer.length,
      thumbnailSize: thumbnail.length,
      processedAt: new Date().toISOString(),
      status: 'completed'
    });

    console.log(`Thumbnail created: ${thumbName}`);
  } catch (error) {
    console.error(`Error processing ${fileName}:`, error.message);

    // 记录失败状态
    const docRef = firestore.collection('imageProcessingLogs').doc();
    await docRef.set({
      sourceFile: `gs://${sourceBucket}/${fileName}`,
      error: error.message,
      processedAt: new Date().toISOString(),
      status: 'failed'
    });
  }
});

编写部署配置

Cloud Functions Gen 2 推荐使用 gcloud 命令行工具进行部署。以下是完整的部署命令和配置文件:

# 设置环境变量
export REGION=asia-east1
export FUNCTION_NAME=process-image
export SOURCE_BUCKET=my-uploads-bucket
export THUMBNAIL_BUCKET=my-thumbnails-bucket

# 创建存储桶
gsutil mb -l $REGION gs://$SOURCE_BUCKET
gsutil mb -l $REGION gs://$THUMBNAIL_BUCKET

# 部署 Cloud Function Gen 2
gcloud functions deploy $FUNCTION_NAME \
  --gen2 \
  --runtime=nodejs20 \
  --region=$REGION \
  --source=. \
  --entry-point=processImage \
  --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
  --trigger-event-filters="bucket=$SOURCE_BUCKET" \
  --set-env-vars="THUMBNAIL_BUCKET=$THUMBNAIL_BUCKET" \
  --memory=512Mi \
  --cpu=1 \
  --max-instances=10 \
  --min-instances=0 \
  --concurrency=80 \
  --timeout=300 \
  --service-account=image-processor-sa@${PROJECT_ID}.iam.gserviceaccount.com

关键参数说明

参数 说明 推荐值
--gen2 明确使用第2代运行时 必须设置
--trigger-event-filters 通过 Eventarc 设置事件过滤条件,可以指定多个条件(AND 关系) 至少指定 type
--concurrency 每个实例最大并发请求数(Gen 2 独有) CPU密集型任务=1,I/O密集型=80+
--min-instances 最小保持运行的实例数(0=允许缩零,1+=预预热) 生产环境建议 1+
--max-instances 最大实例数,控制成本上限 根据预期负载设置
--cpu CPU 核数,最大 8 1 或 2

注意:--trigger-event-filters 取代了 Gen 1 的 --trigger-bucket 等专用参数。所有事件类型通过一致的 filter 语法配置,这是 Eventarc 统一事件模型的核心优势。

Eventarc 事件类型详解

Eventarc 支持 90+ 种预定义事件类型。以下是开发中最常用的事件类型分类:

存储事件

  • google.cloud.storage.object.v1.finalized — 对象上传完成
  • google.cloud.storage.object.v1.archived — 对象归档
  • google.cloud.storage.object.v1.deleted — 对象删除
  • google.cloud.storage.object.v1.metadataUpdated — 元数据更新

发布/订阅事件

  • google.cloud.pubsub.topic.v1.messagePublished — Pub/Sub 主题收到消息(最常用的事件类型之一)

Firestore 事件

  • google.cloud.firestore.document.v1.created — 文档创建
  • google.cloud.firestore.document.v1.updated — 文档更新
  • google.cloud.firestore.document.v1.deleted — 文档删除
  • google.cloud.firestore.document.v1.written — 文档写入(创建/更新/删除任意一种)

BigQuery 事件

  • google.cloud.bigquery.job.v1.jobCompleted — 查询作业完成
  • google.cloud.bigquery.table.v1.tableCreated — 表创建

审计日志事件

所有 Google Cloud 服务都可以通过 Audit Logs 通道触发事件,格式为 google.cloud.audit.log.v1.written,配合 serviceNamemethodName 过滤条件可以精准捕获特定 API 调用。

高级 Eventarc 过滤语法

Eventarc 的过滤器不仅限于事件类型,还支持对事件数据字段进行精确匹配、前缀匹配和后缀匹配。以下是一些高级用法示例:

# 只处理特定目录下的文件
gcloud functions deploy process-report \
  --gen2 \
  --runtime=nodejs20 \
  --region=$REGION \
  --source=. \
  --entry-point=processReport \
  --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
  --trigger-event-filters="bucket=reports-bucket" \
  --trigger-event-filter-path-pattern="name=reports/**/*.pdf"

# 只处理特定 Firestore 集合的更新
gcloud functions deploy on-user-update \
  --gen2 \
  --runtime=nodejs20 \
  --region=$REGION \
  --source=. \
  --entry-point=onUserUpdate \
  --trigger-event-filters="type=google.cloud.firestore.document.v1.updated" \
  --trigger-event-filters-path-pattern="database=(default)/documents/users/{userId}"

# 监听多个事件类型(使用 AND 组合)
gcloud functions deploy on-complex-event \
  --gen2 \
  --runtime=nodejs20 \
  --region=$REGION \
  --source=. \
  --entry-point=handleEvent \
  --trigger-event-filters="type=google.cloud.audit.log.v1.written" \
  --trigger-event-filters="serviceName=bigquery.googleapis.com" \
  --trigger-event-filters-path-pattern="methodName=google.cloud.bigquery.v2.JobService.InsertJob"

路径模式(--trigger-event-filter-path-pattern)支持两种通配符:

  • * — 匹配一层路径段(例如 users/* 匹配 users/abc,但不匹配 users/abc/profile
  • ** — 匹配零层或多层路径段(例如 users/** 匹配 users/abcusers/abc/profile

生产环境最佳实践

生产环境监控仪表盘

1. 冷启动优化

虽然 Gen 2 基于 Cloud Run,冷启动时间已经比 Gen 1 有显著改善,但在延迟敏感的场景下仍然需要注意:

  • 设置最小实例数:对于生产环境中的关键路径函数,设置 --min-instances=1 可以保证始终有至少一个实例在运行,彻底消除冷启动延迟
  • 优化依赖加载:将第三方库的 require() / import 放在全局作用域(函数定义外部),这样依赖只在实例启动时加载一次,后续请求复用
  • 使用减肥后的基础镜像:Gen 2 支持自定义基础镜像,如果默认镜像太大,可以基于 gcr.io/distroless/nodejs 构建更小的运行时镜像
  • 减少包体积:只安装生产依赖(npm install --production),移除不必要的 node_modules 文件

2. 错误处理与重试策略

Cloud Functions Gen 2 对事件驱动函数提供了内置的重试机制。当函数抛出异常或返回错误时,Eventarc 会根据配置自动重试:

# 部署时启用重试
gcloud functions deploy my-function \
  --gen2 \
  --runtime=nodejs20 \
  --region=$REGION \
  --source=. \
  --entry-point=myHandler \
  --trigger-event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
  --retry \
  --max-retry-attempts=5 \
  --min-retry-delay=10s \
  --max-retry-delay=600s

# 或者在函数代码中手动控制确认/否认
functions.cloudEvent('reliableHandler', async (cloudEvent) => {
  try {
    await processEvent(cloudEvent.data);
    // 函数成功返回 → 事件自动确认
  } catch (error) {
    if (error instanceof TransientError) {
      // 抛出异常 → Eventarc 会重试(如果配置了 --retry)
      throw error;
    } else {
      // 对于永久性错误,记录日志但不重试
      console.error(`Permanent failure: ${error.message}`);
      // 函数正常返回,不抛出异常 → 不触发重试
    }
  }
});

重要:重试只在配置了 --retry 标志时启用。对于非关键事件处理(如日志分析),不建议启用重试,因为大量重试可能导致下游系统压力过大。

3. 监控与日志

使用 Cloud Monitoring 设置关键指标的告警策略:

指标 告警阈值 说明
function/execution_count 异常突降或突增 检测事件源是否中断
function/execution_times p99 > 5秒 函数执行缓慢
function/instance_count 接近 max-instances 可能需要扩容
eventarc/undelivered_events > 0 事件送达失败
storage/object_count 死信队列增长 持续失败的事件堆积

建议在函数中添加结构化日志,方便后续查询分析:

// 推荐的结构化日志格式
console.log(JSON.stringify({
  severity: 'INFO',
  function: 'processImage',
  eventId: cloudEvent.id,
  fileName: data.name,
  fileSize: data.size,
  processingTimeMs: duration,
  timestamp: new Date().toISOString()
}));

4. 权限与安全配置

遵循最小权限原则配置函数使用的服务账号:

# 创建专用服务账号
gcloud iam service-accounts create image-processor-sa \
  --display-name="Image Processor Service Account"

# 授予最小权限
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:image-processor-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer" \
  --condition="expression=resource.name.startsWith('projects/_/buckets/${SOURCE_BUCKET}/objects/'),title=source_bucket_access"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:image-processor-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/storage.objectCreator" \
  --condition="expression=resource.name.startsWith('projects/_/buckets/${THUMBNAIL_BUCKET}/objects/'),title=thumb_bucket_access"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:image-processor-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/datastore.user"  # Firestore 写入权限

对于 Eventarc 的事件源权限,需要确保 Eventarc 触发器使用的服务账号(默认为 Compute Engine 默认服务账号)具有接收事件的权限。如果 Eventarc 无法触发函数,通常是因为服务账号缺少 roles/eventarc.eventReceiver 角色:

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
  --role="roles/eventarc.eventReceiver"

5. 成本控制

Cloud Functions Gen 2 的计费模型与 Gen 1 不同,因为它底层运行在 Cloud Run 之上:

  • 请求费用:每百万请求 $0.40(与 Gen 1 相同)
  • 计算时间:按 vCPU 秒和内存 GB 秒计费,价格约为 Gen 1 的 1.5-2 倍
  • 始终在线成本:如果设置了 --min-instances > 0,空闲时也会产生费用

控制成本的策略:

  • 对于低频函数,设置 --min-instances=0 并接受冷启动
  • 设置合理的 --max-instances 上限(建议默认 10,高负载场景不超过 100)
  • 使用 --concurrency 提高单个实例的吞吐量,减少所需实例数
  • 监控 Cloud Billing 的每日花费,设置预算告警

从 Gen 1 迁移到 Gen 2

如果你已经在使用 Cloud Functions 第1代,迁移到 Gen 2 需要考虑以下事项:

触发器的迁移映射

Gen 1 触发器 Gen 2 等效配置
--trigger-bucket --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" --trigger-event-filters="bucket=NAME"
--trigger-topic --trigger-event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" + 通过 Eventarc 通道指定主题
--trigger-http 相同的 --trigger-http 参数,但自动获得 Cloud Run 的并发和 gRPC 能力
--trigger-event-filters (未生效) 正式支持精确和模式匹配过滤

代码兼容性

Gen 2 使用 CloudEvents 格式接收事件,而 Gen 1 使用 Background Functions 格式(参数顺序为 (data, context)(data, context, callback))。这意味着 Gen 1 的函数代码需要调整事件处理参数。Functions Framework 提供了两种方式注册函数:

  • functions.cloudEvent() — 接收完整的 CloudEvent 对象(推荐用于 Gen 2)
  • functions.http() — 接收纯 HTTP 请求(适用于 HTTP 触发器和需要自定义路由的场景)
// Gen 1 风格(已废弃)
exports.processImage = (data, context) => {
  const bucket = data.bucket;
  // ...
};

// Gen 2 风格(当前推荐)
functions.cloudEvent('processImage', (cloudEvent) => {
  const data = cloudEvent.data;
  const bucket = data.bucket;
  // ...
});

总结

Google Cloud Functions Gen 2 + Eventarc 的组合代表了 Google Cloud 无服务器计算的重要进化方向。通过底层基于 Cloud Run 的重构和统一的事件路由层 Eventarc,开发者现在可以:

  • 用一套事件处理模型对接 90+ 种云服务
  • 获得堪比 Cloud Run 的性能和灵活性(并发、长超时、大内存)
  • 使用精确的事件过滤语法,避免不必要的函数触发
  • 在 Cloud Console 中统一监控所有函数和事件流

对于新项目,建议直接从 Gen 2 开始;对于现有 Gen 1 部署,可以按服务逐一迁移,Eventarc 的事件过滤能力让迁移过程中的灰度发布成为可能。

如果你正在考虑将现有的微服务架构迁移到事件驱动模型,Cloud Functions Gen 2 + Eventarc 是一个值得认真评估的技术选项——它既降低了事件驱动架构的入门门槛(不需要管理 Kafka 或 RabbitMQ 集群),又保留了足够的灵活性和扩展性来支撑生产级工作负载。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » Google Cloud Functions(第2代)与Eventarc事件驱动架构实战指南
分享到: 更多 (0)