examlab .net 用最有效率的方法,考取最有價值的證照
本篇導覽 約 18 分鐘

Google Cloud 上的微服務架構 (Microservices Architecture)

3,586 字 · 約 18 分鐘閱讀 ·

為 PCD 考試設計與營運 Google Cloud 雲原生微服務:DDD 邊界上下文、OpenAPI/gRPC 合約、Anthos Service Mesh、Pub/Sub 事件、Saga 模式,以及 Spanner、Firestore、Bigtable 的 polyglot persistence 多元儲存。

立即做 20 題練習 → 免費 · 不用註冊 · PCD

微服務架構簡介

微服務把單體式應用拆成小型、可獨立部署的服務,彼此透過網路 API 通訊。每個服務擁有自己的資料、走自己的發佈節奏,甚至用不同的程式語言撰寫。Google Cloud 的執行平面 (GKE、Cloud Run、Cloud Run for Anthos)、資料層 (Spanner、Firestore、Bigtable、BigQuery) 與整合面 (Pub/Sub、Eventarc、API Gateway、Anthos Service Mesh、Service Directory) 使其成為這種架構的標準平台。

對於 Professional Cloud Developer (PCD) 考試,你必須能夠推理各個組件如何協同:服務該如何切、合約如何維持誠實、身分如何在每一跳之間流動、交易如何跨越多個資料庫、以及在大量 fan-out 之下觀測性如何存活。本章節以具體的 GCP 服務名稱、API 欄位與 gcloud 指令逐一展開。

微服務 是一個可以獨立部署的業務能力單元,擁有自己的資料儲存、暴露具版本的合約 (REST/OpenAPI 或 gRPC/Protobuf)、透過短期憑證 (OIDC ID token 或 mTLS) 驗證呼叫端,並將事件發佈到 Pub/Sub 這類非同步匯流排。

以 Domain-Driven Design 切出服務邊界

把系統切成服務是最重要的一個決策。切得太粗,又重組出一個單體;切得太細,每個業務操作都變成分散式交易。

邊界上下文與通用語言

DDD 的 bounded context (邊界上下文) 是一個明確界線,特定的領域模型只在這個界線內有效。電商系統中,「Order」在 Checkout 上下文裡指的是價格、品項、稅;在 Fulfillment 上下文裡指的是揀貨與物流單號。把每個邊界上下文對應到唯一一個微服務,模型才不會混亂,也避免「共享 kernel」的耦合。

Aggregate 與服務所有權

在上下文內找出 aggregate (聚合):一群會在強一致性界限內一起被修改的實體 (例如 Order + OrderLine)。Aggregate root 是寫入的唯一入口。一個服務應該擁有一個聚合;如果兩個服務經常都要寫同一個聚合,那它們其實是同一個服務。

Anti-Corruption Layer (ACL)

新的微服務要和老舊單體或第三方 SaaS 對話時,建立 anti-corruption layer (反腐敗層):一個小型翻譯服務,對內暴露乾淨的現代合約,對外擋下老舊 schema 的污染。在 GCP 上常以 Cloud Run 服務搭配 Apigee proxy 部署。

畫服務方塊圖之前先跑一次 EventStorming 工作坊。把領域事件 (OrderPlacedPaymentAuthorizedShipmentDispatched) 攤出來,自然會浮現邊界。以事件切的服務通常會貼合邊界上下文;以名詞 (UserProduct) 切則會做出 anemic CRUD 服務,等於把單體分散化了。

API 合約:OpenAPI 與 gRPC

合約是服務之間不讀對方原始碼也能合作的依據。

REST 用 OpenAPI

對外、對合作夥伴的 API 通常是 REST/JSON,並以 OpenAPI 3.0 描述。GCP 的 API Gateway 透過 x-google-backend 擴充欄位讀取 OpenAPI 文件,把每個 path 連到 Cloud Run、Cloud Functions 或 App Engine 後端。版本放在 URL (/v1/orders) 或 header (Accept: application/vnd.example.v2+json)。

gRPC 與 Protocol Buffers

高流量的內部呼叫偏好走 HTTP/2 上的 gRPC + Protobuf:二進位編碼比 JSON 省 60-80% 體積,雙向串流是內建能力。Cloud Run、GKE、Anthos Service Mesh 都原生支援 gRPC 與雙向 streaming。使用 proto3 語法,把熱欄位放在 tag 1-15 (單 byte tag),並且永遠不要重用已淘汰的 tag 編號。

合約演進與向後相容

OpenAPI 與 Protobuf 都允許 additive 演進:新增 optional 欄位、新增 enum 值、新增 RPC method。被禁止的操作包含改欄位型別、改 tag 編號,或者把 optional 欄位變成必填。在 CI 跑 Bufprism 來於部署前偵測 breaking change。

在 body 裡塞 {"error": "..."} 卻回傳 HTTP 200 OK,會讓所有 gateway、重試函式庫與 SLO 工具失效。請把領域錯誤對映到正確的 status code (400404409422),或在 gRPC 用標準的 google.rpc.Code,例如 INVALID_ARGUMENTFAILED_PRECONDITION

服務對服務驗證

在微服務網格內,每一個內部 hop 都是經過驗證的呼叫。GCP 提供兩個互補的原語。

透過 Metadata Server 取得 OIDC ID Token

當服務 A (跑在 Cloud Run、啟用 Workload Identity 的 GKE、GCE 或 Cloud Functions) 要呼叫服務 B 時,A 向本機 metadata server 索取 OIDC ID token:URL 為 http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://service-b-xyz.a.run.app,且必須帶 header Metadata-Flavor: Google。回傳的 JWT 由 Google 簽署,sub claim 是 A 的服務帳號電子郵件,audience 鎖定 B 的 URL。B 用 Google 公開的 JWKS 驗章後,透過 IAM 角色 roles/run.invoker 授權。

在 Anthos Service Mesh 內走 mTLS

在 Anthos Service Mesh (ASM) 叢集中,sidecar 會自動以 SPIFFE 身分 spiffe://PROJECT.svc.id.goog/ns/NAMESPACE/sa/SERVICE_ACCOUNT 把每條連線包成 mutual TLS。授權策略 (AuthorizationPolicy CRD) 可以指定「只有 checkout 服務帳號能呼叫 /payments」。

Workload Identity Federation

對於跑在 GCP 之外 (地端、AWS、GitHub Actions) 卻需要呼叫 GCP API 的工作負載,設定 Workload Identity Federation,讓它用原生 OIDC token 透過 sts.googleapis.com 換取短期 Google access token。沒有任何長期服務帳號金鑰會離開 Google。

OIDC ID token 的 audience claim 必須完全等於 callee 的 URL (Cloud Run 服務 URL,或對 load-balanced 後端設定的自訂 audience)。Audience 萬用字元或不符會回傳 HTTP 401 Unauthorized,並附帶 WWW-Authenticate: Bearer error="invalid_token"

Service Mesh:Anthos Service Mesh 與 Istio

Service mesh 把橫切關注點 (mTLS、retry、traffic shifting、telemetry) 從每個微服務搬到 sidecar proxy。

Data Plane 與 Control Plane

ASM 是 Google 管理的 Istio 發行版。Data plane 是注入到每個 Pod 的 Envoy sidecar;control plane (istiod,在 ASM 由 Google 託管) 透過 xDS 下發設定。每個 L7 請求都從 Envoy 進出 Pod,mTLS 結束、請求紀錄、流量策略都發生在那裡。

流量管理原語

  • VirtualService 定義路由:加權拆分 (90/10 canary)、依 header 路由、故障注入。
  • DestinationRule 定義子集 (v1v2) 與連線池上限。
  • Gateway 把 mesh 對外暴露,通常由 Google Cloud HTTP(S) Load Balancer 承載。

韌性預設

ASM 允許你宣告 timeout (spec.http.timeout: 2s)、retry (spec.http.retries.attempts: 3retryOn: 5xx,reset,connect-failure) 與 circuit breaker (outlierDetection.consecutive5xxErrors: 5)。再搭配 PeerAuthentication 設為 mode: STRICT,整個 mesh 都會強制走 mTLS。

做 ASM canary 時請用 VirtualService 的加權拆分,不要靠抽換 Kubernetes Service 的 selector。加權拆分提供 per-request 粒度 (1% → 5% → 25% → 100%),並且能搭配 Cloud Monitoring SLO 燒蝕率做自動 rollback。

Saga 模式處理分散式交易

跨服務做 ACID 不切實際。Saga 模式 用一連串本地交易取代單一分散式交易,每一步都搭配一個 compensating action,後續失敗時在語義上抵銷它。

用 Pub/Sub 做 Choreography Saga

Choreography saga 中,每個服務對事件做反應並發出新事件。下單流程:OrdersOrderPlacedPayments 扣款並發 PaymentAuthorizedInventory 鎖庫存並發 StockReservedShipping 排出貨。如果 Inventory 失敗就發 StockReservationFailedPayments 訂閱這個事件並退款 (補償)。沒有中央協調者;每個服務只知道自己的輸入與輸出。

用 Workflows 做 Orchestration Saga

當流程複雜或需要可審計性時,用 Cloud Workflows 當顯式 orchestrator。一個 YAML workflow 同步呼叫各服務、捕捉錯誤、明確觸發補償步驟。整個 saga 變成單一資源,可檢視、可重跑、可追蹤。

Idempotency Key

Saga 一定會重試。沒有 idempotency,補償退款可能重複發出。所有會修改狀態的端點都必須接受 Idempotency-Key header (UUID v4),並在資料層去重——通常是用 idempotency key 為主鍵的 Firestore 文件或 Spanner row,TTL 24-72 小時。

補償動作 不是 rollback,它是一筆全新的業務交易,在語義上抵銷前一筆。出貨不能被「un-shipped」,只能被退貨。把補償明確建模到領域裡 (PaymentRefundedStockReleasedOrderCancelled),不要假裝原本事件沒發生過。

以 Pub/Sub 構建事件驅動架構

Pub/Sub 是 GCP 微服務的非同步骨幹:解耦生產者與消費者、吸收流量尖峰、消費者宕機也撐得住。

Topic、Subscription 與遞送語意

生產者發佈到 topic;消費者掛 subscription (pull 或 push)。每個 subscription 都是獨立 fan-out:10 個訂閱者各看到 100% 訊息。預設遞送是 at-least-once,搭配 order key 可以做到 per-key FIFO。Exactly-once delivery 是 opt-in (enable_exactly_once_delivery: true),會用較高延遲換更強語意。

Dead-Letter Topic 與 Retry

設定 dead-letter topic (deadLetterPolicy.deadLetterTopic) 加上 maxDeliveryAttempts: 5。失敗訊息會落到另一個 topic 供檢查,不會毒害主 subscription。指數退避透過 retryPolicy.minimumBackoff: 10smaximumBackoff: 600s 設定。

Schema 驗證

幫 topic 掛一個 Pub/Sub schema (Avro 或 Protobuf),broker 會在邊界就拒絕格式錯的發佈,避免一個壞掉的生產者污染所有下游訂閱者。

完全「fire and forget」、不調 ack deadline 等於保證將來會出現重複處理風暴。預設 ack deadline 是 10 秒;如果你的消費者經常要跑 30 秒,必須用 modify_ack_deadline 延長,或把 subscription 的 ackDeadlineSeconds 設為 60。否則 Pub/Sub 會在你還在處理時就重送。

分散式追蹤與 Context 傳遞

一次使用者點擊可能 fan-out 到 20 個服務。沒有關聯後的 trace,根本沒辦法除錯延遲或錯誤。

Cloud Trace 與 OpenTelemetry

用 OpenTelemetry SDK (Java、Go、Python、Node) 做 instrumentation,把資料送到 Cloud Trace。每個 span 帶有 trace_id (16 bytes)、span_id (8 bytes)、parent span_id、service name 以及自訂屬性。Trace UI 會以 trace_id 把 span 串成 flame graph。

W3C Trace Context Header

跨 HTTP 與 gRPC 的傳遞採用 W3C 標準 header traceparent: 00-<trace_id>-<span_id>-<flags>,並可選擇性帶 tracestate。每個微服務必須 (a) 在 ingress 取出 traceparent、(b) 在對外請求附上、(c) 發佈到 Pub/Sub 時透過 attribute googclient_traceparent 重新注入。ASM 的 Envoy 會自動處理;mesh 之外則需要手動 instrumentation。

Trace 取樣

100% 取樣昂貴又吵雜。用 OpenTelemetry Collector 做 tail-based sampling:保留所有錯誤 trace 加上 1% 健康 trace;或在 SDK 用 probabilistic 取樣,例如 0.05 (5%)。

W3C traceparent header 必須是四段以連字號分隔的欄位:versiontrace-id (32 個 hex 字元)、parent-id (16 個 hex 字元)、flags (2 個 hex 字元)。少一段或改寫其中任何一段,那一跳的 trace 就斷了。

API Gateway 模式

外部用戶端絕不該直接呼叫個別微服務。API gateway 負責 TLS 結束、使用者驗證、配額執行與後端路由。

API Gateway vs Apigee vs ESPv2

GCP 提供三層選項:

  • Google Cloud API Gateway——託管、OpenAPI 驅動、成本低,適合 serverless 後端 (Cloud Run、Cloud Functions)。一次設定 (gcloud api-gateway api-configs create) 部署一份 immutable revision;切換流量靠 gateway 升級。
  • Cloud Endpoints with ESPv2——以 Envoy 為基礎、自行部署的 proxy (在 GKE 當 sidecar 或當 Cloud Run 服務)。控制力更大,營運負擔也更大。
  • Apigee X——企業級完整 API 生命週期管理:開發者入口、貨幣化、複雜策略 (OAuth、JWT、轉換、配額)。考題中只要提到合作夥伴上架、貨幣化、分析,答案幾乎都指向 Apigee。

邊界關注點放在 Gateway

驗證 (Firebase Auth JWT、API key、OAuth2)、限流 (x-google-quota)、CORS、請求/回應轉換、請求紀錄都該住在 gateway,不要在每個後端服務裡複製一份。

Backend-for-Frontend (BFF)

針對行動 / 網頁用戶端,為每個通道部署一個薄薄的 BFF 服務,匯總多個內部微服務的呼叫並把 payload 整型成該用戶端需要的樣子。避免行動端做 12 次來回。

透過 x-google-api-name 與 config-id 將 gateway 用戶端鎖到特定 OpenAPI revision。發佈新的 config 後,必須顯式更新 gateway 才會切流量,讓你在新合約破壞消費者時有乾淨的 rollback 點。

Service Discovery:Service Directory

把主機名稱寫死非常脆弱。Service Directory 是 GCP 託管的服務註冊中心。

Namespace、Service、Endpoint

Namespace (per-region) 內含 service;每個 service 有 endpoint (addressport、metadata 註解)。用戶端可以透過 gRPC 的 xds:/// URI scheme、REST API (projects/*/locations/*/namespaces/*/services/*),或者 Service Directory 自動填充的 private DNS zone 來查詢。

與 GKE 及 Anthos 整合

ASM 可以把 Kubernetes Service 物件同步進 Service Directory,讓叢集外的用戶端 (Cloud Run、GCE VM、透過 Cloud Interconnect 連回來的地端) 看得到叢集內工作負載。反過來,註冊在 Service Directory 的外部 endpoint 會以 ServiceEntry 形式出現在 mesh 裡。

IAM 與網路範圍

Service Directory 在查詢時就執行 IAM (roles/servicedirectory.viewer),未驗證的用戶端無法列舉你的拓樸。再搭配 VPC Service Controls,註冊中心就鎖在邊界內。

Service Directory 是一個全託管服務註冊中心,提供跨 GCP、地端與其他雲的單一服務位置 / 健康 / metadata 真相來源。和 DNS 不同,它儲存結構化 metadata,並且原生支援 gRPC。

依邊界上下文採用 Polyglot Persistence

微服務的核心信條:每個服務擁有自己的資料。延伸出來就是 polyglot persistence 多元儲存——不同服務依存取模式選不同資料庫。

Spanner 用於交易核心

訂單管理、付款、庫存需要強一致性、多列交易與全球分佈。Cloud Spanner 提供 external consistency (TrueTime)、SQL 與水平擴充 (可達 PB 等級)。用 interleaved table 把 OrderLine 物理上放在 Order 之下,讀取免 join。

Firestore 用於使用者讀模型

UserProfile 服務或即時聊天的讀模型適合 Firestore in Native mode:文件模型、行動 / 網頁 SDK 內建 offline 同步、伺服器端事件推送即時更新、以 Firebase Auth 做 per-document ACL。

Bigtable 用於時序與 IoT

每秒寫入 100 萬筆的 DeviceTelemetryClickStream 服務歸 Bigtable:個位數毫秒延遲、以 row key 排序的儲存、可無縫匯出到 BigQuery 做分析。Row key 設計成 reverse_timestamp#device_id,避免熱分區。

Memorystore 用於熱快取

Session 狀態、TTL 小於 1 秒的 idempotency key、限流計數器歸 Memorystore for Redis (或 Valkey)。亞毫秒延遲,但沒有持久性保證——當快取用,不當真實資料來源。

BigQuery 當分析資料匯流

營運資料庫透過 Datastream (Spanner change stream、Firestore native change feed) 或 Pub/Sub → Dataflow 串到 BigQuery。分析師查詢 federated dataset;微服務從不承受分析負載。

即使「看起來很方便」也絕對不要在微服務間共用資料庫。共享資料庫會重新把 schema 耦合、阻塞 migration、跨團隊發佈互鎖——這些正是微服務想擺脫的病灶。如果兩個服務需要同一份資料,由其中一個擁有,另一個透過 API 或 event-sourced 讀模型來讀。

韌性模式

網路分區、依賴變慢、部分失敗都是常態,要把它們當作預設行為來設計。

Timeout、Retry 與 Jitter

內部 hop 預設 1-2 秒 timeout。只重試 idempotent (冪等) 的操作,最多 3 次,並使用帶 jitter 的指數退避 (base * 2^attempt + rand(0, base)),避免重試風暴。ASM 的 retries.perTryTimeout 維持每次嘗試的時間預算。

Circuit Breaker 與 Bulkhead

Circuit breaker (Envoy outlierDetection) 在連續失敗後跳開,暫停呼叫壞掉的依賴,給它喘息空間。Bulkhead (隔艙) 隔離資源:為每個依賴用獨立的連線池,避免慢的下游耗盡整個 thread pool。

優雅降級

當推薦服務掛掉時,商品頁應該還能顯示「熱門商品」這類通用清單,而不是 500。所有讀依賴都該設計 fallback (Java 的 @Fallback、Go 的 circuitbreaker middleware)。

把 gateway 的 timeout (例如 API Gateway 預設 60s) 設得比 load balancer 的 idle timeout (預設 10 分鐘) 短。timeout 不一致會讓用戶端卡在已經被取消的請求上,並把你的重試預算用光。

CI/CD 與獨立部署

只有當每個服務都能獨立部署,微服務才真的提速。

每個服務一條 Pipeline

每個 repo (或 monorepo 中的每個路徑) 都有自己的 Cloud Build trigger、自己在 Artifact Registry 的 image,以及自己的 Cloud Deploy delivery pipeline。Payments 的變更絕不能逼 Inventory 重新部署。

Progressive Delivery

用 Cloud Deploy 搭配 ASM 流量拆分,或者用 Cloud Run revision (gcloud run services update-traffic --to-revisions=v2=10,v1=90) 把流量逐步從 10% → 50% → 100% 切過去,並以 Cloud Monitoring SLO 把關。

零停機 Schema Migration

兩階段 migration:先部署一版同時寫新舊欄位的服務,回填資料,再部署一版從新欄位讀取,最後拿掉舊欄位。永遠不要在同一個 release 裡同時做合約變更與 schema 變更。

讓每個微服務都 import 同一個 「shared library」repo 並 branch 出去,會在 build 階段重建單體式耦合。要嘛接受短期重複,要嘛把 library 在 Artifact Registry 發成有版本的套件,讓服務依自己的節奏升級。

白話文解釋(Plain English Explanation)

類比 1:專賣店街 vs. 一間超大型超市

單體式架構是一間超大型超市——麵包店、肉舖、藥局、家電全擠在同一棟,共用同一個電路。一根保險絲燒掉就全店關門。微服務則像一整條專賣店街。麵包店裝修暫停營業,花店照樣賣玫瑰。每家店老闆挑自己的設備 (資料庫)、自己的營業時間 (發佈週期)、自己的找零幣別 (程式語言)。代價是客人得在店之間走來走去——這就是分散式系統裡的網路呼叫感受。

類比 2:餐廳廚房編制

一個運作良好的廚房就是微服務系統。冷盤師傅做冷菜、醬汁師傅管醬汁、甜點師傅顧甜點。他們用清楚的點單 ("API 合約") 溝通,透過出菜口 ("Pub/Sub topic") 傳遞菜品,主廚 ("API gateway") 協調出菜給外場。甜點師傅請假,主菜還是出得來。一個單體式廚房只靠一個人從頭做到尾,那個人一打噴嚏整間就停擺——而那個人的速度就是整間餐廳的上限。

類比 3:樂高城市 vs. 塑膠模型

塑膠模型 (單體) 是黏死的:剪錯一刀整個就毀了。樂高城市 (微服務) 由可替換的積木組成。只要凸點 (API 合約) 對得上,你就能把消防站 (一個服務) 換成醫院。代價是你要管的積木變多,需要專用底板 (叢集),還得幫每個盒子貼標籤 (Service Directory),這樣小朋友 (開發者) 才能快速找到對的零件。

常見問題 (FAQs)

Q1:什麼時候微服務該用 Cloud Run 而不是 GKE?

A1: Cloud Run 適合無狀態 HTTP/gRPC 服務,著重 per-request 擴充、scale-to-zero、零營運負擔。需要 DaemonSet、超出 mesh 範圍的自訂 sidecar、GPU、持久化磁碟、批次工作負載時就選 GKE (或 Cloud Run for Anthos)。很多團隊兩個都跑:邊界 BFF 與 Cloud Run job 走 Cloud Run,有狀態核心走 GKE。

Q2:沒有中央 orchestrator 要怎麼做 Saga?

A2: 用 Pub/Sub 做 choreography saga。每一步是一個服務,訂閱前一個事件並發佈自己的結果。補償就是在 *Failed 事件上多掛一個 subscription。加上 idempotency key 讓重試安全,再多做一個「Saga Monitor」服務把所有事件灌進 BigQuery,出問題時可以稽核端到端流程。

Q3:API Gateway 和 service mesh 差在哪?

A3: API Gateway 是 南北向 大門——外部用戶端從這裡進入平台 (驗證、限流、API key)。Service mesh 處理 東西向 流量——平台內部服務對服務的呼叫 (mTLS、retry、流量拆分、觀測)。兩者互補;典型架構是邊界放 API Gateway 或 Apigee,內部放 Anthos Service Mesh。

Q4:使用者身分要怎麼在多個內部服務間傳遞?

A4: 兩層。(1) Gateway 驗證最終使用者的 Firebase Auth / OAuth2 token,轉發已驗證的 X-End-User-Id header 與 X-Forwarded-Authorization 裡的 JWT。(2) 服務對服務再加上 自己 從 metadata server 拿到的 OIDC ID token。Callee 同時知道「人是誰」(從 header) 與「呼叫服務是誰」(從 Authorization: Bearer),可以做細緻授權。

Q5:微服務應該多小?

A5: 小到一個團隊 (≤ 8 人) 能完全擁有它,大到一個業務能力能完整放在一個邊界上下文裡。「Two-pizza team」是 heuristic,不是規則。如果一個對外功能需要動到 5 個服務,那些服務就太小了。如果一個團隊管 20 個服務且發佈節奏都相同,就是切過頭。

Q6:什麼時候用 Pub/Sub 而不是同步 gRPC 呼叫?

A6: 當生產者不需要立刻知道消費者結果、多個消費者要看到同一個事件、或需要吸收流量尖峰時用 Pub/Sub。當呼叫端需要阻塞等結果 (登入、付款授權) 且 SLA 要求 < 200 ms 時用 gRPC。常見混合:前面同步發 command RPC,成功後再非同步發 domain event,其他人對事件做反應。

官方資料來源

更多 PCD 主題