跳至主要內容
ESC
ACE 服務實戰 — 第 4/11 篇

ACE-209:Cloud Pub/Sub 與事件驅動架構——解耦微服務的核心技術

ACE-209

前言

系統從單體架構走向微服務,最頭痛的往往不是技術選型,而是怎麼讓服務之間不互相綁死。Cloud Pub/Sub 就是來解這個問題的:它把生產者和消費者完全拆開,各自獨立擴展,彼此根本不知道對方存在。

這篇是 ACE 進階系列第 9 課。我們會從 Pub/Sub 的核心概念開始,搞懂事件驅動架構有哪些設計模式,再學會在實際場景裡挑對工具。


Pub/Sub 核心概念

基本架構

Publisher (發布者)


┌──────────┐
│  Topic   │  ← 訊息的頻道/主題
└────┬─────┘
     │ 一個 Topic 可有多個 Subscription
     ├──────────────────────┐
     ▼                      ▼
┌─────────────┐    ┌─────────────┐
│ Subscription│    │ Subscription│
│     A       │    │     B       │
└──────┬──────┘    └──────┬──────┘
       │                  │
       ▼                  ▼
  Subscriber A       Subscriber B

三個核心元素

元素說明類比
Topic訊息頻道,發布者往這裡送訊息YouTube 頻道
Subscription訂閱,消費者從這裡取訊息YouTube 訂閱
Message訊息本體,含 data(bytes)和 attributes(鍵值對)影片

訊息規格限制

限制
最大訊息大小10 MB
訊息保留期限7 天(預設)
最長確認期限(ack deadline)600 秒
每個 Topic 的 Subscription 上限10,000 個

訂閱類型

Pull Subscription(拉取訂閱)

消費者主動向 Pub/Sub 拉取訊息:

# 建立 pull subscription
gcloud pubsub subscriptions create my-sub \
  --topic=my-topic \
  --ack-deadline=60

# 拉取訊息
gcloud pubsub subscriptions pull my-sub --limit=10

# 確認訊息(ack)
gcloud pubsub subscriptions ack my-sub \
  --ack-ids=ACK_ID_1,ACK_ID_2

適合場景

  • 處理速度不均勻(批次處理)
  • 消費者在防火牆後面(無法接受 HTTP 請求)
  • 需要精確控制消費速率

運作機制

  • 消費者拉取訊息後,必須在 ack deadline 內確認(ack)
  • 若超時未 ack,訊息會重新投遞(至少一次交付,at-least-once)
  • Exactly-once delivery(GA):啟用後可確保訊息只被處理一次,但會略微增加延遲

Push Subscription(推送訂閱)

Pub/Sub 主動將訊息 POST 到指定 HTTPS Endpoint:

# 建立 push subscription
gcloud pubsub subscriptions create my-push-sub \
  --topic=my-topic \
  --push-endpoint=https://my-service.run.app/pubsub/push \
  --push-auth-service-account=sa@project.iam.gserviceaccount.com \
  --ack-deadline=60

訊息格式(POST body):

{
  "message": {
    "data": "SGVsbG8gV29ybGQ=", // base64 encoded
    "attributes": { "key": "value" },
    "messageId": "2070443601311540",
    "publishTime": "2026-03-11T10:00:00Z"
  },
  "subscription": "projects/my-project/subscriptions/my-push-sub"
}

消費者回傳 2xx 狀態碼 表示成功 ack;回傳其他狀態碼則訊息重試。

適合場景

  • Cloud Run、App Engine 等無伺服器服務
  • 不需要維護 pull loop 的簡單整合
  • 事件觸發型工作負載

BigQuery Subscription(直接寫入 BigQuery)

無需中間消費者,直接將訊息寫入 BigQuery 表格:

gcloud pubsub subscriptions create my-bq-sub \
  --topic=my-topic \
  --bigquery-table=my-project:my-dataset.my-table \
  --write-metadata  # 同時寫入 publish_time、subscription_name 等元數據

要求

  • BigQuery 表格 Schema 必須包含 data 欄位(BYTES 或 STRING 型別)
  • Pub/Sub service account 需要 BigQuery Data Editor 角色

適合場景

  • 串流分析 pipeline
  • 直接從事件建立分析報表
  • 不需要業務邏輯處理

Cloud Storage Subscription(直接寫入 GCS)

將訊息批次寫入 Cloud Storage:

gcloud pubsub subscriptions create my-gcs-sub \
  --topic=my-topic \
  --cloud-storage-bucket=my-bucket \
  --cloud-storage-file-prefix=pubsub/ \
  --cloud-storage-max-duration=600s \
  --cloud-storage-max-bytes=10MB

適合場景

  • 長期存檔
  • 後續批次處理(Dataproc、Cloud Batch)
  • Data Lake 攝取

進階特性

Ordering Keys(排序鍵)

保證相同 key 的訊息按發布順序交付:

from google.cloud import pubsub_v1

publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path("my-project", "my-topic")

# 使用 ordering key,相同 user_id 的訊息按順序處理
future = publisher.publish(
    topic_path,
    data=b"Order placed",
    ordering_key="user-123"  # 相同 key 保序交付
)

注意事項

  • Subscription 也必須啟用 ordering(--enable-message-ordering
  • 若某訊息處理失敗,相同 key 的後續訊息會暫停,直到失敗的訊息被 nack 或超時
  • 可能影響吞吐量(相同 key 的訊息只會分配到單一消費者)

Dead Letter Topic(死信主題)

當訊息多次交付失敗後,轉移到死信 Topic:

# 建立死信 topic
gcloud pubsub topics create dead-letter-topic

# 建立 subscription 並配置死信策略
gcloud pubsub subscriptions create my-sub \
  --topic=my-topic \
  --dead-letter-topic=dead-letter-topic \
  --max-delivery-attempts=5  # 失敗 5 次後轉移

# 給 Pub/Sub service account 必要權限
PROJECT_NUM=$(gcloud projects describe my-project --format='value(projectNumber)')
PUBSUB_SA="service-${PROJECT_NUM}@gcp-sa-pubsub.iam.gserviceaccount.com"

gcloud pubsub topics add-iam-policy-binding dead-letter-topic \
  --member="serviceAccount:${PUBSUB_SA}" \
  --role=roles/pubsub.publisher

gcloud pubsub subscriptions add-iam-policy-binding my-sub \
  --member="serviceAccount:${PUBSUB_SA}" \
  --role=roles/pubsub.subscriber

死信處理流程

  1. 訊息交付失敗(消費者 nack 或超時)
  2. Pub/Sub 重試(依退避策略)
  3. 達到最大嘗試次數(max-delivery-attempts)
  4. 訊息轉移到死信 Topic
  5. 另一個消費者(或告警)處理死信

Exactly-Once Delivery(精確一次交付)

# 啟用精確一次交付(GA 功能)
gcloud pubsub subscriptions create my-sub \
  --topic=my-topic \
  --enable-exactly-once-delivery

代價

  • 更高延遲(需要額外確認機制)
  • 較低吞吐量
  • 只在 Pull Subscription 支援

建議:大多數情況用 at-least-once 搭配消費者端的冪等設計就夠了,真的非用不可時再開 exactly-once。

訊息過濾(Message Filtering)

# 只接收特定 attribute 的訊息
gcloud pubsub subscriptions create filtered-sub \
  --topic=my-topic \
  --message-filter='attributes.region = "us-central1"'

事件驅動架構設計模式

模式一:Fan-Out(廣播)

一個事件觸發多個下游服務:

Order Placed Event (Topic)

    ├──► Email Service (Subscription A)
    ├──► Inventory Service (Subscription B)
    ├──► Analytics Service (Subscription C)
    └──► Fraud Detection (Subscription D)

實現:每個下游服務建立獨立的 Subscription,獨立接收同樣的訊息。

模式二:Task Queue(任務佇列)

多個 Worker 競爭消費訊息:

Image Upload Events (Topic)

    └──► processing-sub (Subscription)

             ├──► Worker 1 (Cloud Run 實例)
             ├──► Worker 2 (Cloud Run 實例)
             └──► Worker 3 (Cloud Run 實例)

特性:一個 Subscription 中的訊息只會被一個消費者處理(競爭消費)。

模式三:事件溯源(Event Sourcing)

所有狀態變更都以事件形式記錄:

User Service → account-events (Topic)

    ├──► account-events-bigquery-sub → BigQuery (分析)
    ├──► account-events-storage-sub → GCS (存檔)
    └──► account-events-sync-sub → 其他服務同步

模式四:CQRS(命令查詢職責分離)

寫入操作發布事件,讀取模型訂閱事件更新:

Write Model (Command) → domain-events (Topic) → Read Model (Query)

                    Subscription 更新快取/搜尋索引

Cloud Pub/Sub vs Cloud Tasks vs Eventarc

這三個服務乍看很像,但用途差很多,ACE 考試很愛考選型:

特性Cloud Pub/SubCloud TasksEventarc
主要用途非同步訊息傳遞任務排程與重試GCP 事件路由
目標多個 Subscriber單一 WorkerCloud Run/Functions
排程執行✅(指定時間執行)
明確任務✅(可查詢、刪除任務)
保序交付✅(ordering key)
Fan-out有限
觸發來源任何應用任何應用GCP 服務事件
最大延遲30 天近乎即時

選型決策:

用 Cloud Pub/Sub 當

  • 需要一對多廣播(fan-out)
  • 需要高吞吐量的訊息串流
  • 生產者和消費者需要解耦
  • 訊息可能有多個獨立消費者

用 Cloud Tasks 當

  • 需要延遲執行(e.g., 1 小時後發送 email)
  • 需要可查詢、可刪除的任務列表
  • 需要保證精確一次執行語義
  • 需要限制 Worker 的 QPS(速率控制)

用 Eventarc 當

  • 響應 GCP 服務事件(GCS 上傳、BigQuery 完成、Pub/Sub 訊息)
  • 統一管理 GCP 事件路由到 Cloud Run
  • CloudEvent 標準格式整合

⚠️ 注意Cloud Pub/Sub Lite 將於 2026 年 6 月 30 日(EOL)停止服務,不要選 Pub/Sub Lite。

Cloud Scheduler:定時觸發的補充

Cloud Scheduler 不是訊息系統,但經常與 Pub/Sub / Cloud Tasks 搭配使用:

# 每天早上 9 點觸發 Pub/Sub Topic
gcloud scheduler jobs create pubsub daily-report \
  --schedule="0 9 * * *" \
  --topic=report-trigger \
  --message-body='{"type": "daily"}'

# 每 5 分鐘呼叫 Cloud Run 端點
gcloud scheduler jobs create http health-check \
  --schedule="*/5 * * * *" \
  --uri=https://my-service-xxx.run.app/cron/health \
  --http-method=POST \
  --oidc-service-account-email=scheduler-sa@my-project.iam.gserviceaccount.com
場景選擇
定時觸發(cron-like)Cloud Scheduler → Pub/Sub 或 HTTP
延遲執行(X 分鐘後)Cloud Tasks(指定 scheduleTime)
即時非同步廣播Cloud Pub/Sub

實戰範例:電商訂單事件系統

底下是一個完整的電商訂單事件驅動系統,從頭到尾跑一遍:

# 1. 建立訂單事件 Topic
gcloud pubsub topics create order-events

# 2. 建立訂單死信 Topic
gcloud pubsub topics create order-events-dlq

# 3. 訂單通知服務(Push 訂閱 → Cloud Run)
gcloud pubsub subscriptions create order-notification-sub \
  --topic=order-events \
  --push-endpoint=https://notification-service.run.app/orders \
  --push-auth-service-account=notification-sa@project.iam.gserviceaccount.com \
  --ack-deadline=60 \
  --dead-letter-topic=order-events-dlq \
  --max-delivery-attempts=5

# 4. 庫存服務(Pull 訂閱,批次處理)
gcloud pubsub subscriptions create order-inventory-sub \
  --topic=order-events \
  --ack-deadline=120 \
  --max-delivery-attempts=5 \
  --dead-letter-topic=order-events-dlq \
  --enable-message-ordering  # 保序處理

# 5. 分析服務(BigQuery 直接訂閱)
gcloud pubsub subscriptions create order-analytics-sub \
  --topic=order-events \
  --bigquery-table=my-project:analytics.orders \
  --write-metadata

# 6. 發布訂單事件(Python)
import json
import base64
from google.cloud import pubsub_v1

publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path("my-project", "order-events")

order = {
    "order_id": "ORD-2026031100001",
    "user_id": "USER-123",
    "items": [{"sku": "SKU-001", "qty": 2}],
    "total": 1299.00
}

# 發布訊息(使用 ordering key 保序)
future = publisher.publish(
    topic_path,
    data=json.dumps(order).encode("utf-8"),
    ordering_key=f"user-{order['user_id']}",  # 同用戶訂單保序
    event_type="ORDER_PLACED",
    version="v1"
)
message_id = future.result()
print(f"Published message ID: {message_id}")

Pub/Sub 監控與維運

關鍵指標

# 查看 subscription 的未確認訊息數(backlog)
gcloud monitoring metrics list \
  --filter="metric.type:pubsub.googleapis.com/subscription/num_undelivered_messages"
指標說明告警建議
num_undelivered_messagesBacklog 訊息數> 1000 時告警
oldest_unacked_message_age最舊未確認訊息年齡> 300s 時告警
delivery_attempts_count交付嘗試次數Dead letter 增加時告警

調整 Subscription 配置

# 修改 ack deadline
gcloud pubsub subscriptions update my-sub \
  --ack-deadline=120

# 啟用 exactly-once delivery(慎用)
gcloud pubsub subscriptions update my-sub \
  --enable-exactly-once-delivery

# 查看 subscription 詳細資訊
gcloud pubsub subscriptions describe my-sub

IAM 與安全

# 給發布者 Topic 發布權限
gcloud pubsub topics add-iam-policy-binding my-topic \
  --member="serviceAccount:publisher-sa@project.iam.gserviceaccount.com" \
  --role=roles/pubsub.publisher

# 給訂閱者 Subscription 訂閱權限
gcloud pubsub subscriptions add-iam-policy-binding my-sub \
  --member="serviceAccount:subscriber-sa@project.iam.gserviceaccount.com" \
  --role=roles/pubsub.subscriber

# Push Subscription 的 Service Account 需要 Token Creator
gcloud iam service-accounts add-iam-policy-binding \
  push-endpoint-sa@project.iam.gserviceaccount.com \
  --member="serviceAccount:push-invoker-sa@project.iam.gserviceaccount.com" \
  --role=roles/iam.serviceAccountTokenCreator

ACE 考試重點整理

必背知識點

  1. Pub/Sub Lite 即將 EOL(2026/6/30),考試選 Cloud Pub/Sub
  2. 一個 Topic 多個 Subscription = Fan-out;一個 Subscription 多個消費者 = 競爭消費
  3. 最大訊息大小 10MB,保留 7 天
  4. Ordering Key 保證相同 key 的訊息按序交付
  5. Dead Letter Topic 需要給 Pub/Sub SA 發布權限
  6. Exactly-once delivery 是 GA 功能,但只支援 Pull Subscription
  7. BigQuery SubscriptionCloud Storage Subscription 都是 GA 功能

選型題公式

場景答案
多個服務同時收到事件Cloud Pub/Sub(fan-out)
1 小時後執行任務Cloud Tasks(延遲執行)
GCS 上傳觸發 Cloud RunEventarc
限制 Worker 處理速率Cloud Tasks(rate limiting)
高吞吐量訊息串流Cloud Pub/Sub
保證只執行一次的重要任務Cloud Tasks

常見陷阱題

Q:如何讓訊息只被一個消費者處理? A:多個消費者訂閱同一個 Subscription(競爭消費)。如果訂閱不同 Subscription,每個 Subscription 都會收到同一份訊息。

Q:Pub/Sub 能保證訊息順序嗎? A:預設不保證。需要使用 Ordering Key 並在 Subscription 啟用 enable-message-ordering,才能保證相同 key 的訊息按序交付。

Q:訊息未被 ack 會怎樣? A:超過 ack deadline 後,Pub/Sub 會重新投遞該訊息(at-least-once 語義)。要避免重複處理,消費者應設計為冪等(idempotent)

Q:如何處理持續處理失敗的訊息? A:設定 Dead Letter Topic,指定最大重試次數(max-delivery-attempts),失敗訊息自動轉移,避免阻塞正常訊息流。


總結

要做事件驅動架構,Cloud Pub/Sub 幾乎是繞不開的一塊。幾個重點:

  • 解耦設計:Publisher 和 Subscriber 互不知曉,獨立擴展
  • 訂閱類型:Pull(主動拉取),Push(被動接收),BigQuery/Storage(直接寫入)
  • 進階特性:Ordering Key(保序),Dead Letter(死信處理),Exactly-once(精確一次)
  • 選型原則:廣播/高吞吐用 Pub/Sub;延遲任務用 Cloud Tasks;GCP 事件路由用 Eventarc
  • Pub/Sub Lite 已 EOL,不要選它

下一課 GCP-109:Cloud Run 入門,學習如何零運維部署容器化應用程式。

ACE 服務實戰 — 4/11 完成 查看系列全覽 →

留言討論

徽章解鎖!