Google Cloud Pub/Sub 簡介
Google Cloud Pub/Sub 是一個全球分散式、全代管的非同步訊息服務,將發布者(publisher)與訂閱者(subscriber)解耦。和傳統區域型訊息代理不同,Pub/Sub 的 topic 是全球資源:位於 us-central1 的發布者,可以對著同一個 topic 發佈訊息,由 asia-east1 的訂閱者讀取,而 Pub/Sub 服務面會自動複製與路由訊息。這讓它成為 Google Cloud 上事件驅動服務、BigQuery 與 Dataflow 攝取管線、以及 Cloud Run、Cloud Functions、GKE 背後非同步工作負載的預設骨幹。
Pub/Sub 預設是 at-least-once(至少一次)送達;對於 pull 型訂閱,可以在單一區域內啟用 exactly-once(精確一次)送達。它保證每則發佈的訊息會持久儲存,直到訂閱者 ack 為止,預設保留 7 天(最長可調到 31 天)。對 PCD 考試而言,你不只要懂資料模型(topic、subscription、message、ack),還要懂送達模式、ordering、schema、dead-letter 之間怎麼跟 Cloud Run、Cloud Functions、BigQuery 互動。
核心資源與術語
- Topic:具名資源(
projects/PROJECT/topics/TOPIC),發布者把訊息送到這裡。Topic 本身不一定保留訊息——保留是 subscription 的屬性,或者 topic 為了支援重播也可以設定保留。 - Subscription:附掛在剛好一個 topic 的具名資源。每個 subscription 都有自己獨立的 backlog、ack 狀態、與送達設定。同一個 topic 上多個 subscription 就是 fan-out 的基礎。
- Message:最多 10 MB 的
data(binary),加上選用的字串attributes與ordering_key。 - Ack deadline:每個 subscription 都有
ackDeadlineSeconds(10 到 600 秒),決定訂閱者在 Pub/Sub 重送之前有多久可以處理該訊息。
Pub/Sub 的 subscription 不像 Kafka consumer group offset 那種被動偏移量——它是伺服器端的資源,有自己的 backlog、保留時間、ack 狀態與送達型態(pull、push、bigquery、cloudStorage)。刪除 subscription 就會把 backlog 一起刪掉;新建的 subscription 從空 backlog 開始(除非你 seek 回 snapshot 或時間戳)。
Push 與 Pull 訂閱
Push 與 pull 是兩種長期存在的送達模式,對應的執行模型差異很大。選錯邊是 PCD 考試最常測的架構錯誤之一。
Pull 訂閱
在 pull 訂閱中,訂閱者應用程式(或 Dataflow pipeline,或 client library 的 StreamingPull)會主動向 Pub/Sub 索取訊息。Client library 會開一條長連線的 StreamingPull gRPC stream,Pub/Sub 一有訊息就推進來。Pull 適合:
- 需要極高吞吐量(單一 topic 每秒數百萬則訊息)。
- 訂閱者在防火牆後方,或沒有公開的 HTTPS 端點。
- 需要 exactly-once 送達(只有 pull 訂閱支援)。
- 想要 backpressure——client 透過
flow_control_settings.max_messages與max_bytes控制同時 outstanding 訊息數量。
Compute Engine 機群、GKE 工作負載、Dataflow streaming job 通常用 pull。手動除錯時 gcloud pubsub subscriptions pull SUB --auto-ack 很好用。
Push 訂閱
push 訂閱會把每則訊息以 HTTPS POST 送到你設定的 pushEndpoint。任何 non-2xx 回應或逾時都會由 Pub/Sub 以指數退避重試(最少 10 秒,最多 600 秒)。Push 是 serverless 訂閱者的天作之合:
- Cloud Run、Cloud Run functions、App Engine 等以 HTTPS 請求數量自動伸縮的服務。
- 跨專案或跨雲的 webhook,原本就接受簽章過的 HTTPS 呼叫。
- 想讓 Pub/Sub 自己負責流量控制(依照 ack 速率的 slow-start 演算法)。
Push 每個 subscription 有最大送達速率(Pub/Sub 會根據觀測到的 ack 延遲自動調整),在極高 QPS 下不如 pull 有效率,但維運上簡單很多。
考試常問「Cloud Run 服務要處理尖峰式事件流量——push 還是 pull?」答案是 push 配合 OIDC 驗證。從 Cloud Run 服務做 pull 很彆扭,因為 Cloud Run 依請求數伸縮,而 pull 迴圈是一條長連線、不是請求。改用 push,讓 Cloud Run 因為 Pub/Sub 的 HTTP POST 自動 scale。
Push 與 Pull 的選擇
| 情境 | 建議 | 原因 |
|---|---|---|
| Cloud Run / Cloud Functions 消費者 | Push(OIDC) | Serverless 依 HTTPS 請求 scale |
| Dataflow streaming 寫入 BigQuery | Pull(透過 I/O connector) | 吞吐量 + ordering 控制 |
| 需要 exactly-once 送達 | Pull | Push 不支援 exactly-once |
| 地端 worker 在 NAT 後 | Pull | 不需要 inbound HTTPS |
| Webhook 到第三方 SaaS | Push | 直接 HTTPS POST |
Exactly-Once 送達
預設情況下 Pub/Sub 是 at-least-once:可能會收到重複訊息(例如 ack 在 ack deadline 過後才到伺服器)。從 2022 年起,Pub/Sub 也支援 exactly-once 送達 作為 subscription 的選項。
Exactly-Once 到底保證什麼
當 pull 訂閱設定 enableExactlyOnceDelivery: true,Pub/Sub 保證:
- 訊息一旦被 ack 並由伺服器確認後(client library 透過 future 把這個確認暴露給你),就不會再重送。
- 在訂閱者的 lease(ack deadline)內,訊息不會被重送。
- 這個保證 僅限單一 Cloud 區域。要拿到區域行為,必須設定
messageStoragePolicy,並使用區域端點,例如us-east1-pubsub.googleapis.com:443。
限制與取捨
- Exactly-once 只支援 pull。Push 訂閱沒辦法啟用。
- 不會對 publish 端去重——如果你的發布者重試 publish RPC 且兩次都成功,你會拿到兩個不同的
messageId。要做跨 publish 去重,請用發布者端的messageId或在 attributes 放冪等性 token。 - 每個 subscription 的吞吐量會比 at-least-once 略低,因為伺服器端需要額外協調。
- Ack deadline 的有效最小值會變大(伺服器內部會用更長的 lease)。
「Exactly-once」不等於「整條 pipeline 端到端只送一次」。它只保證「在單一區域內,訊息被 ack 之後不會再從 subscription 重送」。如果你的訂閱者寫進 BigQuery 後在 ack 之前 crash,訊息會被重送,這時你必須讓 BigQuery 寫入本身冪等(例如用 insert_id)。
使用 Ordering Keys 保證訊息順序
預設情況下 Pub/Sub 不保證順序——訊息可能以任意順序送達,特別是來自不同發布主機或不同區域的訊息。要拿到順序保證,必須:
- 在 subscription 上開啟
enableMessageOrdering: true。 - 每則發佈的訊息上設
ordering_key(字串)。 - 從會遵守順序的 client 發佈(官方 client library 會——它們會以 key 為單位序列化 publish)。
Pub/Sub 接著保證:相同 ordering_key 的訊息會以發佈順序送到單一訂閱者。不同 key 之間仍然可以平行送達。
Ordering 何時會失效
- 如果某個 ordering key 的 publish 失敗,client library 會暫停該 key,直到你呼叫
resume_publish(ordering_key)。忘了呼叫的話,那個 key 的吞吐量會無聲無息地卡住。 - 每個 ordering key 的吞吐量超過 1 MB/s,該 key 就會出現 backpressure。
- Ordering 只在區域內生效;跨區域訂閱者不保證 ordering。
- 用
seek會把游標重置,可能造成訊息以和原本發佈時不一樣的相對順序送達。
常見模式是把 entity ID(user ID、order ID、device ID)當作 ordering key。這給你「每個 entity 內保序、entity 之間大規模平行」的效果——沒有全域瓶頸,但每個 entity 的事件保持順序。
訊息保留、Snapshot 與 Seek
保留時間
Pub/Sub 會持續儲存每則訊息直到所有 subscription 都 ack 為止,上限是 subscription 的 messageRetentionDuration。預設值與上限:
- subscription 預設保留:7 天。
- subscription 最大保留:對未 ack 訊息是 7 天;但若搭配
retainAckedMessages: true,已 ack 的訊息也會保留到設定的時間(方便重播)。 - Topic 訊息保留:選用,10 分鐘到 31 天。設在 topic 上之後,新建立的 subscription 可以
seek回到它建立之前的歷史。
Snapshot
Snapshot 會擷取某 subscription 在某時間點的 ack 狀態。之後你可以 seek 回該 snapshot,等於重播當時所有未 ack 的訊息。Snapshot 最多存活 7 天,且是綁定 subscription 的。
gcloud pubsub snapshots create my-snap --subscription=orders-sub
# ... 部署了一個會誤 ack 的有 bug 消費者 ...
gcloud pubsub subscriptions seek orders-sub --snapshot=my-snap
Seek 到時間戳
你也可以 seek 到任意 RFC 3339 時間戳:
gcloud pubsub subscriptions seek orders-sub \
--time=2026-05-01T00:00:00Z
這要求 topic(或啟用 retainAckedMessages 的 subscription)的保留時間涵蓋該時間戳。Seek 從某種意義上是破壞性操作——它會改變 subscription 的讀取游標,並沒有「單則訊息倒帶」的概念。
Snapshot 是綁在 subscription 上,不是 topic。如果你刪掉再重建 subscription,snapshot 就失效了。要做跨 subscription 重建的長期重播,請在 topic 層級啟用 --message-retention-duration,最長 31 天。
Dead-Letter Topic 與重試策略
Dead-letter topic(DLT) 是一個獨立的 Pub/Sub topic,會接收 subscription 嘗試送達失敗超過設定次數的訊息。這可以把毒丸訊息隔離出來,不讓它卡住主管線。
設定 DLT
gcloud pubsub subscriptions create orders-sub \
--topic=orders \
--dead-letter-topic=orders-dlq \
--max-delivery-attempts=5 \
--min-retry-delay=10s \
--max-retry-delay=600s
max-delivery-attempts範圍為 5 到 100。- Pub/Sub 的服務代理
[email protected]需要 DLT 的pubsub.publisher與來源 subscription 的pubsub.subscriber。gcloudflag 會自動處理;如果用 Terraform,必須自己授權。
重試策略
獨立於 DLT,你可以設定 指數退避 重試策略,使用 min-retry-delay 與 max-retry-delay。Push 與 pull 訂閱都適用。原本的 ack deadline 仍然成立;重試延遲是 nack 或 ack deadline 過期後到下次重送之間的等待時間。
DLT 側要做什麼
在 dead-letter topic 上設一個專屬訂閱者,把訊息寫到 Cloud Logging、BigQuery,或用 Cloud Monitoring alert 通知 on-call。永遠不要靜靜地丟掉 DLT 訊息——它們幾乎一定代表 schema 不匹配或下游故障。
訊息過濾
Subscription 可以依 attributes(不是 payload)過濾訊息。過濾條件是 CEL 風格的布林表達式,對訊息屬性求值:
attributes.eventType = "order.created" AND attributes.region = "us"
- 過濾語法支援
=、!=、:(子字串)、hasPrefix、hasSuffix、NOT、AND、OR。 - 過濾在伺服器端執行。被過濾掉的訊息會自動 ack,不會計入訂閱吞吐量或 ack deadline。
- 過濾條件在 subscription 建立後不可變——要改必須重建 subscription。
過濾在「只有一個 topic 中的子集對某消費者重要」時可以降低成本與負載。也是讓多個微服務共用同一個 topic、各自只關心不同事件型態的乾淨做法。
Schema 與 Schema 驗證
Pub/Sub 支援 topic 層級的 schema,可以在 publish 時強制訊息結構。支援兩種格式:
- Avro(
AVRO):JSON schema 定義;payload 可以是 Avro binary 或 JSON。 - Protocol Buffers(
PROTOCOL_BUFFER):.proto定義;payload 可以是 Protobuf wire format 或 JSON。
操作流程
- 把 schema 建立為頂層資源:
gcloud pubsub schemas create order-v1 --type=AVRO --definition-file=order.avsc。 - 建立 topic 時掛上:
--schema=order-v1 --message-encoding=BINARY。 - 違反 schema 的發布者,會在
Publish收到INVALID_ARGUMENT。訂閱者只會看到通過驗證的訊息。
Schema 透過 revision 版本化。你可以演進 schema(新增選填欄位、加 enum 值),並把 topic 釘到特定 revision 範圍以控制相容性。
要做 BigQuery 訂閱時,topic 上掛 Avro 或 Protobuf schema 會讓目標表對應變得直觀——Pub/Sub 自動把 schema 欄位對到 column,並在訊息到達 BigQuery 之前就拒絕格式錯誤的訊息。
BigQuery 訂閱
BigQuery 訂閱 會把訊息直接寫進 BigQuery 表,完全不需要你自己維運 Dataflow 或 Cloud Run 消費者。設定目標表,Pub/Sub 就會替你做 streaming insert。
兩種寫入模式
- Topic schema 模式(
useTopicSchema: true):topic 上有 Avro 或 Protobuf schema,目標表有對應 column。每個 schema 欄位對應一個 column。 writeMetadata模式:訊息 data 寫進單一datacolumn(bytes 或 string),可選地把subscription_name、message_id、publish_time、attributes寫到 metadata column。
權限
Pub/Sub 服務代理([email protected])需要目標 dataset 的 roles/bigquery.dataEditor 與 roles/bigquery.metadataViewer。
與 Dataflow 的取捨
BigQuery 訂閱是「原始寫入」最便宜、最簡單的路徑,但它不能做轉換、enrich、join 或 windowing。要做這些就回到讀取一般 pull 訂閱的 Dataflow streaming pipeline。
Cloud Storage 訂閱
Cloud Storage 訂閱 會把批次化的訊息以檔案形式寫到 GCS bucket。適合歸檔、data lake 落地區、離線 ML 訓練資料。
設定
你要指定:
bucket與filenamePrefix/filenameSuffix。- 批次觸發:
maxDuration(預設 5 分鐘,範圍 1 到 10 分鐘)與maxBytes(1 KB 到 10 GiB)。 - 輸出格式:
text(換行分隔)或avro(可選地搭配writeMetadata)。
權限
Pub/Sub 服務代理需要 bucket 的 roles/storage.objectCreator。檔案命名為 <prefix><timestamp>-<UUID><suffix>,依批次設定接近即時抵達。
這種訂閱型態取代了過去常見的「Pub/Sub → Dataflow → GCS」單純歸檔模式,省下 Dataflow 的費用與維運負擔。
OIDC Push 驗證
公開 push 端點本身是安全風險:知道 URL 的人就能 POST。Pub/Sub 用 OIDC token push 驗證 解決。
運作方式
- 透過
pushConfig.oidcToken.serviceAccountEmail把一個服務帳號掛到 push 訂閱上。 - 可選地設定
pushConfig.oidcToken.audience(預設是 push 端點 URL)。 - 每次 push,Pub/Sub 都會為該服務帳號簽出一個 Google 簽章的 OIDC ID token,放在
Authorization: Bearer ...header。 - 你的端點(Cloud Run、Cloud Functions、App Engine,或任何配 IAP / 自寫驗證的服務)驗證該 token。
必要權限
Pub/Sub 服務代理 需要對 push 服務帳號有 roles/iam.serviceAccountTokenCreator。Push 服務帳號則需要呼叫端點所需的權限——例如 Cloud Run 服務的 roles/run.invoker。
私有 Cloud Run 服務,OIDC push 是 唯一支援 的驗證方式。設定 pushConfig.oidcToken.serviceAccountEmail 並授予該 SA roles/run.invoker。Cloud Run 會自動驗證 OIDC token,你不用自己寫驗證程式。
IAM 角色與安全
Pub/Sub 的角色面很精簡且界線清楚。請套用最小權限原則:
roles/pubsub.publisher:可以對 topicPublish。授權在 topic 上,不要授權在專案。roles/pubsub.subscriber:可以Pull、Ack、ModifyAckDeadline。授權在 subscription 或 topic。roles/pubsub.viewer:唯讀 metadata,dashboard 用。roles/pubsub.editor:在專案內建立 / 更新 / 刪除 topic 與 subscription。roles/pubsub.admin:完整控制,包含 IAM policy 變更。只保留給平台團隊。
VPC Service Controls
Pub/Sub 支援 VPC Service Controls,讓周界內的 publish 與 pull 流量沒辦法把資料 egress 到周界外其他專案的 topic。這是法遵環境的標準控制。搭配 topic 上的 CMEK(messageStoragePolicy 加 Cloud KMS 客戶管理金鑰)做信封加密。
服務帳號 impersonation
跨專案事件流上,較好的做法是把生產者服務帳號授予消費者專案 特定 topic 的 roles/pubsub.publisher,而不是複製憑證。這樣 Cloud Audit Logs 的稽核軌跡會乾淨許多。
Pub/Sub Lite:什麼時候、為什麼
Pub/Sub Lite 是另一個獨立服務(不同 API、不同 client library),為了成本而犧牲便利性。和一般 Pub/Sub 的主要差異:
- 可用區或區域 級別,不是全球。Topic 建立時就選定位置。
- 預先配置容量:publish/subscribe MiB/s 與儲存 GiB 都要預訂——按預訂容量計費,不是按操作數。在持續高吞吐情境下成本可以低非常多。
- 分區(類 Kafka):你選分區數量,順序保證以分區為單位。
- 沒有 push 訂閱、沒有 BigQuery 訂閱、沒有 schema、沒有 exactly-once、沒有 過濾。只給你原始分區化訊息傳遞。
何時選 Lite
- 可預測的高吞吐量(持續數百 MB/s),標準 Pub/Sub 成本太高。
- 已經有 Kafka 風格消費者,只想要一個代管 broker。
- 可以接受可用區可用性,或願意用 regional Lite 取得 HA。
考試常用「Pub/Sub Lite 比較便宜」當誘餌。它確實比較便宜,但缺的功能很關鍵:沒有 BigQuery 訂閱、沒有 push、沒有 exactly-once、沒有 schema,而且 必須預先配置容量。除非情境明確提到持續高吞吐與 Kafka 遷移,否則答案是一般 Pub/Sub。
監控與維運
Pub/Sub 在 Cloud Monitoring 上暴露每 topic 與每 subscription 的指標:
pubsub.googleapis.com/subscription/num_undelivered_messages——backlog。pubsub.googleapis.com/subscription/oldest_unacked_message_age——最舊未 ack 訊息的年齡;對 SLO 至關重要。pubsub.googleapis.com/subscription/ack_message_count——每分鐘成功 ack 數量。pubsub.googleapis.com/topic/send_request_count——publish QPS。
對 oldest_unacked_message_age 超過保留時間 60%(舉例)設警報,可以在資料遺失前抓到壞掉的消費者。對 DLT publish 數量非零設警報,可以抓到毒丸訊息。
要做單則訊息追蹤,啟用 client 的 OpenTelemetry,傳遞 googclient_OpenTelemetrySpanContext 屬性,Pub/Sub 會把這個屬性穿越 topic 保留下來。
Pub/Sub 速查數字:訊息大小上限 10 MB;subscription 預設保留 7 天;最大保留 subscription 是 7 天、topic 上 messageRetentionDuration 可達 31 天;ack deadline 範圍 10–600 秒;DLT maxDeliveryAttempts 範圍 5–100;ordering 吞吐量 每個 key 1 MB/s;exactly-once 限定 pull 且單一區域。
白話文解釋
類比 1:報紙派送服務
Topic 就是報紙。發布者是記者,他們把文章發出去,不知道誰會看。每個 subscription 是不同讀者的信箱:體育迷與政治宅訂同一份報,但各自拿到獨立的副本——體育迷把他那份撕了不看(沒 ack),政治宅照樣能讀她那份。Push 訂閱是送報到府;pull 訂閱是讀者自己挑時間去報攤拿。Ordering keys 像是漫畫系列的編號——發行商保證在同一系列裡,第 2 期一定在第 1 期之後到。
類比 2:餐廳出單軌
想像一個忙碌的廚房有一條出單軌。服務生(發布者)把單子夾上軌道(topic)。廚師(訂閱者)一張一張拿來做。如果廚師把單子弄丟(沒 ack),它會留在軌上,最後另一個廚師會撿——這就是 at-least-once 送達。Exactly-once 送達是廚房經理站在軌道邊,物理阻止兩個廚師同時搶同一張單。Dead-letter topic 是「奇怪訂單」白板:如果五個廚師都看不懂「超辣隱形麵」是什麼,這張單會被移到那塊板讓經理處理,而不是繼續塞住主軌。
類比 3:辦公室收發室
收發室(Pub/Sub)接受任何部門的信封,並把副本送進收件人的信格(subscription)。每個信格獨立——清空你的不會清空別人的。收發室每封信都會保留 7 天(預設保留時間);如果你休假,信格滿了,第 8 天最舊的會被碎掉。Snapshot 是星期一早上幫你信格拍的拍立得;如果你星期三不小心丟了重要的信,可以叫收發室把信格「seek」回星期一早上的狀態,把當時所有的信重送一次。
常見問題(FAQs)
Q1:訊息在 Pub/Sub 中保留多久?
A1:subscription 把未 ack 訊息保留 messageRetentionDuration 那麼久,預設 7 天,範圍 10 分鐘到 7 天。Topic 也可以獨立設定 messageRetentionDuration 達 31 天,這支援 seek 到時間戳,並讓新建的 subscription 可以讀到歷史訊息。
Q2:訊息大小上限是多少?
A2:每則訊息 10 MB。更大的 payload 請存進 Cloud Storage,再發一則 Pub/Sub 訊息帶 GCS URI——這就是 claim-check 模式。
Q3:Pub/Sub 保證 exactly-once 送達嗎?
A3:是的,但有但書。Exactly-once 是 opt-in(enableExactlyOnceDelivery: true),只支援 pull 訂閱、只在單一區域。它防止訊息被 ack 之後在該區域內被重送,但不會在 publish 時去重,也不會跨區域生效。
Q4:Cloud Run 該用 push 還是 pull?
A4:Push 配 OIDC。Cloud Run 依 HTTPS 請求數自動 scale,所以 push 訂閱把每則訊息 POST 到 Cloud Run URL,剛好讓你拿到原生的 autoscaling。設定 pushConfig.oidcToken.serviceAccountEmail,並授予該 SA Cloud Run 服務的 roles/run.invoker。私有 Cloud Run 服務一定要用 OIDC push——沒別的支援模式。
Q5:什麼時候該用 Pub/Sub Lite 而不是 Pub/Sub?
A5:只有當你有 持續高吞吐(數百 MB/s)、可以接受可用區或單區域運作,而且不需要 push、不需要 BigQuery 訂閱、不需要 exactly-once、不需要 schema、不需要過濾時,才用 Pub/Sub Lite。Lite 因為要預先配置 MiB/s 與 GiB 容量所以便宜得多,但它是不同 API、不同服務層級——不是「比較便宜的 Pub/Sub」這種 drop-in 替換。
Q6:消費者有 bug,要怎麼重播訊息?
A6:要嘛在壞 deploy 之前先建 snapshot(gcloud pubsub snapshots create),之後 seek 回去;要嘛啟用 topic 層級訊息保留(最長 31 天),再用 seek 把 subscription 拉回某個時間戳。Snapshot 綁 subscription、最長 7 天;topic 保留則可以跨 subscription 重建仍然有效。
Q7:怎麼避免未授權的 POST 打到我的 push 端點?
A7:在 push 訂閱上啟用 OIDC token 驗證,透過 pushConfig.oidcToken.serviceAccountEmail 設定。Pub/Sub 每次請求都會在 Authorization header 帶一個簽章 ID token,你的端點(或 Cloud Run / IAP)負責驗證。Pub/Sub 服務代理需要 push SA 上的 roles/iam.serviceAccountTokenCreator。