區域 ID
REGION_ID
是 Google 根據您在建立應用程式時選取的地區所指派的簡寫代碼。雖然某些區域 ID 可能看起來與常用的國家/地區代碼相似,但此代碼並非對應國家/地區或省份。如果是 2020 年 2 月後建立的應用程式,App Engine 網址會包含 REGION_ID.r
。如果是在此日期之前建立的現有應用程式,網址中則可選擇加入地區 ID。
進一步瞭解區域 ID。
App Engine 中的微服務通常會使用 HTTP 為基礎的 RESTful API 呼叫彼此。您也可以使用工作佇列在背景叫用微服務,在這種情況下,您必須遵守下述 API 設計原則。請務必遵循特定模式,以確保微服務型應用程式能夠穩定運作、安全無虞且效能良好。
使用完善合約
微服務型應用程式的一大要點,就是能夠部署彼此完全獨立的微服務。為達到這種獨立性,每項微服務都必須提供定義完善的版本化合約給用戶端,而這些用戶端是其他微服務。除非確定沒有其他微服務會使用特定的合約版本,否則每項服務均須遵守這些依據版本提供的合約。請注意,其他微服務可能需要復原至先前的程式碼版本,而該版本可能需要先前的合約,因此在制定淘汰及終止政策時請務必考量這一點。
如何培養出使用完善版本化合約的風氣,或許是機構在打造穩定微服務型應用程式的過程中最大的挑戰。開發團隊必須充分瞭解破壞性和非破壞性變更之間的差異、知道何時需要推出新的主要版本,以及從服務中移除舊合約的方式和時機。團隊必須採用適當的溝通技巧 (包含淘汰和終止通知),確保成員隨時掌握微服務合約的異動情形。這聽起來似乎是項大工程,但如果開發團隊培養出使用這類做法的風氣,就能隨著時間大幅提高作業速度和品質。
呼叫微服務
服務和程式碼版本可直接呼叫。因此,您可以在使用現有程式碼版本的同時部署新的程式碼版本,而且可先對新的程式碼進行測試,再將它設為預設提供的版本。
每項 App Engine 專案都有預設服務,且每項服務都有預設程式碼版本。如要針對專案的預設版本呼叫其預設服務,請使用以下網址:
https://PROJECT_ID.REGION_ID.r.appspot.com
如果您部署了名為 user-service
的服務,可以使用以下網址存取該服務的預設提供版本:
https://user-service-dot-my-app.REGION_ID.r.appspot.com
如果您另外部署了名為 banana
的非預設程式碼版本到 user-service
服務,可以使用以下網址直接存取該程式碼版本:
https://banana-dot-user-service-dot-my-app.REGION_ID.r.appspot.com
此外,如果您另外部署了名為 cherry
的非預設程式碼版本到 default
服務,可以使用以下網址存取該程式碼版本:
https://cherry-dot-my-app.REGION_ID.r.appspot.com
App Engine 規定預設服務中的程式碼版本名稱不得與服務名稱相牴觸。
只有在進行基本測試,或是為了方便進行 A/B 版本測試、更新和復原作業時,才可直接呼叫特定程式碼版本。用戶端程式碼應該只呼叫預設服務或特定服務的預設提供版本:
https://PROJECT_ID.REGION_ID.r.appspot.com
https://SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com
這種呼叫方式可讓微服務部署新版服務 (包括錯誤修正內容),而不需變更用戶端。
使用 API 版本
每個微服務 API 的網址中都應有主要 API 版本,例如:
/user-service/v1/
這個主要 API 版本會在記錄中清楚標明呼叫的是微服務的哪個 API 版本。更重要的是,主要 API 版本會產生不同的網址,讓新的主要 API 版本可與舊的主要 API 版本並行提供:
/user-service/v1/ /user-service/v2/
您不需要在網址中加入次要 API 版本,因為次要 API 版本就定義上來說不會造成任何破壞性變更。事實上,在網址中加入次要 API 版本會使得網址數量增加,導致用戶端不一定能移至新的次要 API 版本。
請注意,本文假設有一個持續整合和持續推送軟體更新的環境,其中主要分支版本一律會部署到 App Engine。本文會提到兩種不同的版本概念:
程式碼版本:直接對應到 App Engine 服務版本,並代表主要分支版本的特定修訂版本標記。
API 版本:直接對應到 API 網址,並代表要求引數的型態、回應文件的型態,以及 API 行為。
本文同時假設進行單一程式碼部署作業後,會在常用程式碼版本中同時實作舊版和新版 API。舉例來說,您部署的主要分支版本可能會同時實作 /user-service/v1/
和 /user-service/v2/
。在發佈新的次要和修補版本時,這種做法可讓您將兩個程式碼版本的流量分開,不受程式碼實際實作的 API 版本影響。
您的機構可選擇在不同的程式碼分支版本中開發 /user-service/v1/
和 /user-service/v2/
;也就是說,沒有任何程式碼部署作業會同時實作這兩者。App Engine 也支援這種模型,但如要將流量分開,您必須將主要 API 版本移到服務名稱中。舉例來說,用戶端必須使用下列網址:
http://user-service-v1.my-app.REGION_ID.r.appspot.com/user-service/v1/ http://user-service-v2.my-app.REGION_IDappspot.com/user-service/v2/
主要 API 版本移入服務名稱中,例如 user-service-v1
和 user-service-v2
。(在這個模型中,路徑的 /v1/
、/v2/
部分是多餘的,您可以將其移除,不過在進行記錄分析時,這些部分或許仍可派上用場)。使用這個模型時,您可能需要更新部署指令碼,才能在主要 API 版本有所異動時部署新服務,因此需要多費點功夫。另外也請留意每個 App Engine 應用程式允許的服務數量上限。
破壞性和非破壞性變更的比較
請務必瞭解破壞性和非破壞性變更之間的差異。破壞性變更往往具有削減性質,意思是會移除要求或回應文件的某些部分。變更文件的型態或金鑰名稱可能會導致破壞性變更。新的必要引數一律是破壞性變更。如果微服務的行為有所改變,也會發生破壞性變更。
非破壞性變更往往具有添加性質。新的選用要求引數或回應文件中新的額外部分都是非破壞性變更。如果想達成非破壞性變更,傳輸過程中的序列化選擇將是關鍵。許多序列化格式都支援非破壞性變更,例如 JSON、Protocol Buffers 或 Thrift。在去序列化時,這些序列化格式會默默忽略非預期的額外資訊。在動態程式語言中,額外資訊會直接顯示在去序列化的物件中。
請參考下列適用於 /user-service/v1/
服務的 JSON 定義:
{
"userId": "UID-123",
"firstName": "Jake",
"lastName": "Cole",
"username": "[email protected]"
}
以下破壞性變更需要將服務的版本重設為 /user-service/v2/
:
{
"userId": "UID-123",
"name": "Jake Cole", # combined fields
"email": "[email protected]" # key change
}
不過,以下非破壞性變更不需要新版本:
{
"userId": "UID-123",
"firstName": "Jake",
"lastName": "Cole",
"username": "[email protected]",
"company": "Acme Corp." # new key
}
部署新的非破壞性次要 API 版本
部署新的次要 API 版本時,App Engine 可讓您同時發佈新的程式碼版本和舊的程式碼版本。在 App Engine 中,雖然您可以直接呼叫任何已部署的版本,但只有一個版本是預設提供的版本;請回想一下,每項服務都有一個預設提供的版本。在這個範例中,有一個舊的程式碼版本叫做 apple
,其剛好是預設提供的版本,而我們同時要部署叫做 banana
的新程式碼版本。請注意,因為我們部署的是非破壞性次要 API 變更,因此這兩個版本的微服務網址都是 /user-service/v1/
。
App Engine 提供相關機制,可將新的程式碼版本 banana
標示為預設提供的版本,進而自動將流量從 apple
遷移到 banana
。設定新的預設服務版本後,系統就不會將任何新要求轉送至 apple
,而是轉送至 banana
。如要更新至實作新的次要或修補 API 版本的新程式碼版本,但不想對用戶端微服務造成影響,就可以採用這種做法。
如果發生錯誤,只要反向進行上述流程即可復原至舊的版本,也就是將預設提供的版本改回我們範例中的舊版本 apple
。這樣一來,所有的新要求都會轉送至舊的程式碼版本,而非 banana
。不過請注意,處理中的要求仍可繼續完成。
App Engine 也可讓您只將特定百分比的流量導向新的程式碼版本;這項程序通常稱為「初期測試版」程序,而這項機制在 App Engine 中稱為流量拆分。您可以將 1%、10%、50% 或任何所需百分比的流量導向新的程式碼版本,並隨著時間調整這個數值。舉例來說,您可以發佈新的程式碼版本 15 分鐘,並在這段時間內慢慢提高流量,看看是否有任何問題發生,導致您可能要復原至舊的版本。這項機制還可讓您對兩種程式碼版本進行 A/B 版本測試:將流量拆分設定為 50%,然後比較這兩種程式碼版本的成效和錯誤率,以確認是否達成預期中的改善效果。
下圖顯示Google Cloud 主控台的流量拆分設定:
部署新的破壞性主要 API 版本
部署破壞性主要 API 版本時,更新和復原的流程與非破壞性次要 API 版本相同。不過,由於破壞性 API 版本是新發布的網址 (例如 /user-service/v2/
),因此您通常不會進行任何流量拆分或 A/B 版本測試。當然,如果您變更了舊的主要 API 版本的基礎實作設定,您可能仍會想要使用流量拆分功能來進行測試,確認舊的主要 API 版本可繼續正常運作。
部署新的主要 API 版本時,請務必記得系統仍可能會繼續提供舊的主要 API 版本。舉例來說,發布 /user-service/v2/
後,系統仍可能會提供 /user-service/v1/
。這是獨立程式碼版本的一大要點。如要終止舊的主要 API 版本,您必須確認沒有任何其他微服務需要這些版本,包括可能需要復原至舊程式碼版本的其他微服務。
以具體實例來說明,假設您有一個名為 web-app
的微服務需依賴另一個名為 user-service
的微服務。假設 user-service
需要變更一些基礎實作設定,像是將 firstName
和 lastName
合併成叫做 name
的單一欄位,而這類變更會造成無法支援 web-app
目前使用的舊版主要 API。也就是說,user-service
必須終止舊的主要 API 版本。
為了進行這項變更,有三項不同的部署作業必須完成:
首先,
user-service
必須部署/user-service/v2/
,但同時仍支援/user-service/v1/
。如要進行這項部署作業,您可能需要編寫暫時性的程式碼來支援回溯相容性,微服務型應用程式常常會出現這類結果接著,
web-app
必須部署更新過的程式碼,將依附元件從/user-service/v1/
變成/user-service/v2/
最後,
user-service
團隊確認web-app
不再需要/user-service/v1/
,且web-app
不需要復原後,即可部署程式碼,移除舊的/user-service/v1/
端點和支援舊版本所需的任何暫時性程式碼。
雖然看起來是項大工程,但對於微服務型應用程式和獨立的開發版本週期而言,這是必經的流程。明確來說,這項流程看似環環相扣,但重點在於上述每個步驟都可依獨立的時程進行,且更新和復原只會發生在單一微服務範圍中。這些步驟只有順序是固定的,實際執行時間可以是好幾個小時、好幾天,甚至是好幾個星期。
後續步驟
- 概略瞭解 App Engine 的微服務架構。
- 瞭解如何透過 App Engine 的微服務建立及命名開發、測試、品質確保、測試環境和實際工作環境。
- 瞭解提高微服務效能的最佳做法。
- 瞭解如何將現有的單體式應用程式遷移至具有微服務的應用程式。