微服務的版本號要怎么設計?
今天我們來聊一下微服務項目中的版本號要怎么設計。
小伙伴們平時看到的項目版本號,基本上都是分為了三部分 X.Y.Z,版本升級的時候版本號都會變,那么版本號怎么變,這可不是拍腦門決定的,今天我們就一起來探討一下這個話題。
一、語義化版本控制規范
版本號該如何控制?其實是有一個標準規范的,規范地址:
這個規范非常友好的提供了中文版的內容。
語義化的版本控制規范要求版本號由三部分構成:
- MAJOR(X):這個是主版本號,一般是涉及到不兼容的 API 更改時,這個會變化。
- MINOR(Y):這個是次版本號,當我們對 API 進行向后兼容的增強時,這個版本號會變化,換句話說,也就是有新增的功能時,這里會變化。
- PATCH(Z):這個是修訂號,當我們進行一些 BUG 的修復,然后要發版的時候,這里會發生變化。
語義化的版本控制規范主要做了如下一些要求:
- 使用語義化版本控制的軟件必須(MUST)定義公共 API。該 API 可以在代碼中被定義或出現于嚴謹的文檔內。無論何種形式都應該力求精確且完整。
- 標準的版本號必須(MUST)采用 X.Y.Z 的格式,其中 X、Y 和 Z 為非負的整數,且禁止(MUST NOT)在數字前方補零。X 是主版本號、Y 是次版本號、而 Z 為修訂號。每個元素必須(MUST)以數值來遞增。例如:1.9.1 -> 1.10.0 -> 1.11.0。
- 標記版本號的軟件發行后,禁止(MUST NOT)改變該版本軟件的內容。任何修改都必須(MUST)以新版本發行。有的小伙伴可能會說我們的項目處于快速開發階段,API 不穩定,天天變,要是按照這個要求來得發多少個版本才夠用呀!其實,一般 API 快速變化主要有兩種情況,一種是項目剛立項的時候,此時主版本號為 0,那么這個時候的 API 就不能算是穩定的 API;另外一種情況則是下個主版本處于快速開發中,但是這種情況一般會有一個新的分支用來管理下個版本的代碼,所以和這里的要求實際上并不沖突(具體參見第 4、5 條)。
- 主版本號為零(0.y.z)的軟件處于開發初始階段,一切都可能隨時被改變。這樣的公共 API 不應該被視為穩定版。
- 1.0.0 的版本號用于界定公共 API 的形成。這一版本之后所有的版本號更新都基于公共 API 及其修改內容。那么有的小伙伴可能會糾結什么時候版本號從 0.Y.Z 變為 1.Y.Z 呢?一般來說,當你的項目已經上了生產環境或者說有穩定的 API 提供給別人使用的時候,基本上就可以算是 1.Y.Z 了。
- 修訂號 Z(x.y.Z | x > 0)必須(MUST)在只做了向下兼容的修正時才遞增。這里的修正指的是針對不正確結果而進行的內部修改。
- 次版本號 Y(x.Y.z | x > 0)必須(MUST)在有向下兼容的新功能出現時遞增。在任何公共 API 的功能被標記為棄用時也必須(MUST)遞增。也可以(MAY)在內部程序有大量新功能或改進被加入時遞增,其中可以(MAY)包括修訂級別的改變。每當次版本號遞增時,修訂號必須(MUST)歸零。
- 主版本號 X(X.y.z | X > 0)必須(MUST)在有任何不兼容的修改被加入公共 API 時遞增。其中可以(MAY)包括次版本號及修訂級別的改變。每當主版本號遞增時,次版本號和修訂號必須(MUST)歸零。
- 先行版本號可以(MAY)被標注在修訂版之后,先加上一個連接號再加上一連串以句點分隔的標識符來修飾。標識符必須(MUST)由 ASCII 字母數字和連接號 [0-9A-Za-z-] 組成,且禁止(MUST NOT)留白。數字型的標識符禁止(MUST NOT)在前方補零。先行版的優先級低于相關聯的標準版本。被標上先行版本號則表示這個版本并非穩定而且可能無法滿足預期的兼容性需求。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。
- 版本編譯信息可以(MAY)被標注在修訂版或先行版本號之后,先加上一個加號再加上一連串以句點分隔的標識符來修飾。標識符必須(MUST)由 ASCII 字母數字和連接號 [0-9A-Za-z-] 組成,且禁止(MUST NOT)留白。當判斷版本的優先層級時,版本編譯信息可(SHOULD)被忽略。因此當兩個版本只有在版本編譯信息有差別時,屬于相同的優先層級。范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。
- 版本的優先層級指的是不同版本在排序時如何比較。
- 只有數字的標識符以數值高低比較。
- 有字母或連接號時則逐字以 ASCII 的排序來比較。
- 數字的標識符比非數字的標識符優先層級低。
- 若開頭的標識符都相同時,欄位比較多的先行版本號優先層級比較高。例如:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。
- 判斷優先層級時,必須(MUST)把版本依序拆分為主版本號、次版本號、修訂號及先行版本號后進行比較(版本編譯信息不在這份比較的列表中)。
- 由左到右依序比較每個標識符,第一個差異值用來決定優先層級:主版本號、次版本號及修訂號以數值比較。例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。
- 當主版本號、次版本號及修訂號都相同時,改以優先層級比較低的先行版本號決定。例如:1.0.0-alpha < 1.0.0。
- 有相同主版本號、次版本號及修訂號的兩個先行版本號,其優先層級必須(MUST)透過由左到右的每個被句點分隔的標識符來比較,直到找到一個差異值后決定:
二、微服務中的版本號
那么在微服務中,我們的版本號該怎么設計呢?
首先,整體上的思路,就是按照上文所說的語義化版本控制規范來。
其次,上面雖然給出了很多條條框框,然而我們實際開發中,一般只需要從以下幾個方面簡單考慮即可,每次發版的時候都去翻這個規范顯然也不現實:
- 理想情況下,我們應該只進行向后兼容的更新。
我們要為項目添加新功能、新特性,我們必須要考慮到項目的兼容性。例如接口中新加了一個參數,那么為了老版本的客戶端能夠順利訪問這個接口,服務端應該考慮為老版本客戶端缺少的請求參數提供一個默認值。我們也可能為響應添加新的屬性,或者提供了一些新的接口,當然這些一般都不影響老客戶端。
- 必須進行不兼容的升級。
有時候我們必須進行一些不兼容的升級,對 API 做一些主要的修改,考慮到微服務之間的松耦合性,我們沒法強迫客戶端進行立馬升級,此時可能會考慮在某一個時間段內,兩個版本的 API 共存。
多個 API 共存的時候,一個比較簡單的辦法是在 API 設計的時候,加上版本號,例如 /v1/xxx 或者 /v2/xxx,不過這種寫法有一個小小的缺陷,就是路徑中加了版本號之后,這個路徑看起來就不是一個完美的 REST 路徑了。
所以這塊還有一個方案,就是把請求的 API 的版本號寫到請求頭中。
具體的實現思路是這樣:
首先,在微服務中,我們所有的請求一般來說都會經過網關,我們可以在網關中提取出請求頭的 Accept 參數,然后根據 Accept 中的請求版本號,做不同的請求轉發,如果版本號是 1.0,就轉發到 1.0 的服務上去;如果版本號是 2.0,則轉發到 2.0 的服務上去。基本上就是這個樣子。
以現在微服務中主流的網關 Spring Cloud Gateway 為例,我們可以做如下配置:
spring:
application:
name: gateway
cloud:
nacos:
discovery:
password: nacos
username: nacos
server-addr: a.b.c.d:8848
namespace: public
gateway:
discovery:
locator:
# enabled: true
lower-case-service-id: true
routes:
- id: v1_provider
uri: lb://provider
predicates:
- Path=/p/**
- Header=Accept,.*;?version=1\.0(|;.*)
filters:
- StripPrefix=1
server:
port: 8082
大家看一下這個配置:
- 首先記得關閉服務自動發現,否則通過默認的服務名進行代理就不會經過我們配置的過濾器了。
- 然后我們手動配置服務轉發,上面的配置基本上都是常規配置,跟版本號相關的配置是 Header=Accept,.*;?version=1\.0(|;.*),這個配置就是對請求頭提出要求,首先前面的 Accept 表示這里是要判斷請求頭中的 Accept 字段,然后后面緊跟著的是 value(兩者之間用 , 隔開),這個 value 是一個正則表達式 .*;?version=1\.0(|;.*),意思就是在 version=1.0 之前和之后可以有任意字符串,只要 value 中包含 version=1.0 就算匹配上了。只有匹配上了,才會進行請求轉發,否則不會進行請求轉發。
- 最后,我們在發送請求的時候,設置如下請求頭即可:
圖片
如果版本號是 versinotallow=2.0,則會報一個 404 錯誤:
圖片
好啦,一個小小的版本號話題,感興趣的小伙伴可以試試最后這段代碼哦!