dubbo-go v3 版本 go module 踩坑記

阿里巴巴雲原生發表於2021-05-31

董劍輝 盛傲飛

1. 問題背景

該問題源於我們想對 dubbo-go 的 module path 做一次變更,使用dubbo.apache.org/dubbo-go/v3 替換之前的 github.com/apache/dubbo-go

首先,我們做了路徑對映,在 dubbo.apache.org 下放置了一個 dubbogo/v3 檔案,內容如下:

<html>
  <head>
    <meta name="go-import" content="dubbo.apache.org/dubbo-go/v3 git <https://github.com/apache/dubbo-go>">
    <meta name="go-source" content="dubbo.apache.org/dubbo-go/v3 git <https://github.com/apache/dubbo-go/tree/3.0{/dir}> <https://github.com/apache/dubbo-go/blob/3.0{/dir}/{file}#L{line}>">
    <meta http-equiv="refresh" content="0; url=https://pkg.go.dev/dubbo.apache.org/dubbo-go/v3">
  </head>
  <body>
    <p>Redirecting to <a href="<https://pkg.go.dev/dubbo.apache.org/dubbo-go/v3>">pkg.go.dev/dubbo.apache.org/dubbo-go/v3</a>...</p>
  </body>
</html>

其次,我們修改了 go.mod 的 module 和對應的所有 import,並修改了所有子模組引用 dubbo-go 使用的 module 路徑。

2. 問題分析

在做完上述的修改後,我們提 PR 時,發現 CI 失敗,經過進一步的日誌排查,我們確定是 CI 在跑整合測試時發生了錯誤,具體的錯誤提示資訊如下:

0.png

這一段的執行邏輯是希望利用 docker 對 dubbo-go 中的整合測試內容構建映象,並啟動容器進行測試,該映象打包所用的 Dockerfile 路徑在 github.com/apache/dubbo-go/test/integrate/dubbo/go-server 目錄下,對照錯誤日誌的 STEP 標識,我們可以定位到具體錯誤發生下面的兩個步驟:

# ...
# STEP 9
RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=dubbo.apache.org/dubbo-go/v3=github.com/{PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u dubbo.apache.org/dubbo-go/v3@develop
# ...
# STEP 11
RUN go mod tidy && go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server

在 STEP 9 中,我們使用 go mod edit -replace 替換了 dubbogo 的依賴路徑,將其替換為發起 PR 請求的倉庫地址和 commit id。在此基礎上,當映象構建跑到 STEP11 ,嘗試使用 go mod tidy 拉包時發生了錯誤。

回過頭檢視錯誤日誌,我們可以發現:

由於我們只指定了 commit id,因此在 go mod 實際執行過程中,它會為我們生成一個假定版本號(關於假定版本號的更多說明可以檢視本文最後的 問題擴充),這個假定版本號抓取遠端倉庫的最新有效 tag 為 v1.4.1【注:此處遠端倉庫為 github.com/Mulavar/dubbo-go,這是我自己的 dubbo-go 分支,該分支拉取 fork 的時間比較早,其最後 tag 是 v1.4.1】,並自增為 v1.4.2,主版本是 v1。但在先前,我們的 dubbogo module path 是宣告為 dubbo.apache.org/dubbogo/v3 ,主版本是 v3,這導致了 go mod edit -replace 前後的依賴主版本分別是 v3 和 v1 ,出現了不一致,實際拉取 dubbogo 依賴的時候出錯。

1.png

3. 問題解決

在問題分析一節中我們定位到這個問題在映象構建的 STEP 9,即

# STEP 9
RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=dubbo.apache.org/dubbo-go/v3=github.com/{PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u dubbo.apache.org/dubbo-go/v3@develop

這一步,我們使用 go mod edit -replace將一個 v3 版本的 go module 替換成了一個 v1 版本的 module,導致主版本不一致發生錯誤,因此我們只需在替換依賴路徑時指定替換後的 module 主版本也為 v3 即可,我們在 go mod edit -replace 後的模組依賴路徑後也加上 v3 字尾如下:

修改前:

go mod edit -replace=dubbo.apache.org/dubbo-go/v3=github.com/{PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}

⇒修改後:

go mod edit -replace=dubbo.apache.org/dubbo-go/v3=github.com/${PR_ORIGIN_REPO}/v3@${PR_ORIGIN_COMMITID}

修復該問題後我們提交程式碼檢視 CI,在 STEP 8 列印的日誌資訊中,可以看到替換後的 dubbogo 路徑多了 v3,並且在拉包的時候跟的版本號(v3.0.0-20210509140455-2574eab5ad0b)主版本同樣是 v3,成功拉取了依賴。

4. 問題擴充

4.1 Semantic Import Versioning

在我們使用 Go modules 去構建專案的依賴關係時,對 go 專案的依賴都需要宣告我們所使用的版本號,考慮到每次釋出新版本時,相容一直是開發者最為重視和頭痛的問題,因此 go 通過語義匯入版本控制(Semantic Import Versioning)來制定版本號的標準,來確保每個開發者能夠根據自己的專案需求指定使用的依賴版本。根據 go 的語義匯入版本控制準則,版本號由三部分構成:

3.png

注:上面圖片以及 4.1 小節部分內容源自參考文件 4《Go Modules 詳解》一文。

其中 Major version 表示這是一個新的大版本,甚至這個新版本可能和舊版本是不相容的。

Minor version 則表示這是一個大版本中的迭代,主要用於新增 feature 時遞增。

Patch version 則是粒度最細的版本更新,表示一些 bug 的修復。

而指定主版本則有兩種方式,一種是如上的直接宣告,另一種則是在專案路徑的最後加上版本字尾,如 dubbogo 3.0 版本宣告的 module 則是 dubbo.apache.org/dubbo-go/v3,其中 v3 就是一個主版本的宣告。

4.2 pseudo-version

Go 在使用依賴時推崇我們指定明確的版本號,比如 dubbogo 的 go.mod 中宣告的對 cloud.google.com/go 的依賴:

4.png

但考慮到在某些場景下,我們想要使用模組依賴的某個未發版的版本(該模組只有一個已知的 commit id),如 dubbogo 宣告的 github.com/Microsoft/go-winio 依賴,就可以使用一個假定版本號( pseudo-version )去替代真實的版本號。假定版本號的格式為

// (1) vX.0.0-yyyymmddhhmmss-abcdef123456
// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456
// (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible
// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456
// (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible

可以看到 pseudo-version 被短斜槓分為三部分,其中第一部分 X.Y.Z 與 4.1 節提到的一致,分別是 major versionminor versionpatch version,而第二部分是一個格式為 yyyymmddhhmmss 的時間戳,第三部分是一個 12 位的 commit hash,可以手動指定,如果沒有,則由 go 自動生成。

關於 pseudo-version ,go 的生成規則如下:

  1. 查詢該專案對應主版本(若專案依賴路徑沒有顯式的 v2/v3 字尾,則預設為 v1 版本)的最新 tag
  2. 有 tag 則使用最新 tag 遞增 patch version
  3. 無 tag ,根據專案路徑帶的字尾版本號自動生成(如 v3 則自動生成一個 3.0.0 開頭的 pseudo-version

遵循這個規則,回頭看前文的問題分析和問題解決,我們就可以明白為什麼在一開始 dubbo-go 的 pseudo-version 是 v1.4.2,這正是因為 go 認為 replace 後的 dubbogo 依賴主版本是 v1,去查詢了該主版本下的最新 tag,並遞增了 patch version,從而導致前後主版本不一致,當我們對版本路徑加上 v3 後,go 查詢不到對應主版本下的 tag,為我們自動生成了 v3.0.0,從而通過了 CI 。

4.3 Go 模組巢狀

和 Java 不同,go 其實是沒有子模組概念的。即使有些時候,我們會看到有個 repo 裡有多個 Go modules,比如專案的根目錄有一個 go.mod ,裡面有些子目錄裡又有 go.mod 。

這在 Go modules 被稱為巢狀模組,而非父子模組,即兩個模組沒有任何關係,是相互獨立的。

那什麼時候才需要單 repo 多模組呢?一般來說,碰到以下兩種情況我們才會考慮使用單 repo 多模組的開發形式。

某個巢狀模組變動非常頻繁,需要經常發版。

當中的巢狀模組僅僅依賴某個固定版本的根模組。

兩種情況其實本質都是兩個模組之間沒什麼強的版本繫結關係,但是由於一些其他原因需要放在一個 rpeo 下,因此形成了單 repo 多模組的局面。

4.4 dubbogo 靜態對映檔案內容解析

dubbo-go 使用了靜態檔案對映的方式實現了模組重定向,靜態檔案的內容如下:

其中的核心部分是 meta 標籤 go-import 和 go-source。

<html>
  <head>
    <meta name="go-import" content="dubbo.apache.org/dubbo-go/v3 git <https://github.com/apache/dubbo-go>">
    <meta name="go-source" content="dubbo.apache.org/dubbo-go/v3 git <https://github.com/apache/dubbo-go/tree/3.0{/dir}> <https://github.com/apache/dubbo-go/blob/3.0{/dir}/{file}#L{line}>">
    <meta http-equiv="refresh" content="0; url=https://pkg.go.dev/dubbo.apache.org/dubbo-go/v3">
  </head>
  <body>
    <p>Redirecting to <a href="<https://pkg.go.dev/dubbo.apache.org/dubbo-go/v3>">pkg.go.dev/dubbo.apache.org/dubbo-go/v3</a>...</p>
  </body>
</html>

【go-import】 go-import 的作用,是告訴 go get 去哪兒可以找到原始碼,content 分為三部分:

dubbo.apache.org/dubbo-go/v3:這個專案的 module 宣告。

git:使用的版本控制工具。

https://github.com/apache/dubbo-go:告訴 go get 這個專案應該去哪兒找原始碼。

【go-source】 go-source 的作用,則是給專案生成具體的 go doc(現為 pkg.go.dev ) 文件,一共有 4 部分,前兩部分和 go-import 一樣,是該專案的 module 宣告和版本控制工具,後兩部分則分別起如下作用:

https://github.com/apache/dubbo-go/tree/3.0/dir}{:宣告該專案的原始碼所在位置。

https://github.com/apache/dubbo-go/blob/3.0/dir}/file}#Lline}{:對映文件和程式碼,幫助我們在點選文件的目錄樹時,可以跳轉到對應的具體內容。

比如在 https://pkg.go.dev/dubbo.apache.org/dubbo-go/v3 上,我們點選其中一個檔案:

6.png

就會跳轉到對應檔案對應的文件。

7.png

歡迎對 apache/dubbo-go 專案有興趣的同學通過釘釘掃碼加入交流群【或搜尋釘釘群號 31363295】:

8.png

同時歡迎掃描如下微信二維碼,關注 dubbogo 社群微信公眾號:

9.png

5 作者

董劍輝 (github @Mulavar),剛從 浙江大學 VLIS 實驗室畢業,目前在 猿輔導 任職服務端開發工程師。 盛傲飛 (github @aofei),goproxy.cn 專案作者,go 原始碼【如 mod 工具】貢獻者。

參考資料

更多原創文章乾貨分享,請關注公眾號
  • dubbo-go v3 版本 go module 踩坑記
  • 加微信實戰群請加微信(註明:實戰群):gocnio