ACE-209:Cloud Pub/Sub 與事件驅動架構——解耦微服務的核心技術
前言
系統從單體架構走向微服務,最頭痛的往往不是技術選型,而是怎麼讓服務之間不互相綁死。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
死信處理流程:
- 訊息交付失敗(消費者 nack 或超時)
- Pub/Sub 重試(依退避策略)
- 達到最大嘗試次數(max-delivery-attempts)
- 訊息轉移到死信 Topic
- 另一個消費者(或告警)處理死信
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/Sub | Cloud Tasks | Eventarc |
|---|---|---|---|
| 主要用途 | 非同步訊息傳遞 | 任務排程與重試 | GCP 事件路由 |
| 目標 | 多個 Subscriber | 單一 Worker | Cloud 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_messages | Backlog 訊息數 | > 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 考試重點整理
必背知識點
- Pub/Sub Lite 即將 EOL(2026/6/30),考試選 Cloud Pub/Sub
- 一個 Topic 多個 Subscription = Fan-out;一個 Subscription 多個消費者 = 競爭消費
- 最大訊息大小 10MB,保留 7 天
- Ordering Key 保證相同 key 的訊息按序交付
- Dead Letter Topic 需要給 Pub/Sub SA 發布權限
- Exactly-once delivery 是 GA 功能,但只支援 Pull Subscription
- BigQuery Subscription 和 Cloud Storage Subscription 都是 GA 功能
選型題公式
| 場景 | 答案 |
|---|---|
| 多個服務同時收到事件 | Cloud Pub/Sub(fan-out) |
| 1 小時後執行任務 | Cloud Tasks(延遲執行) |
| GCS 上傳觸發 Cloud Run | Eventarc |
| 限制 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 入門,學習如何零運維部署容器化應用程式。