你們好,我是一隻普通的煎魚,週四晚上頗有幸邀請到 goproxy.cn 的做者 @盛傲飛(@aofei) 到 Go 夜讀給咱們進行第 61 期 《Go Modules、Go Module Proxy 和 goproxy.cn》的技術分享。git
本次 @盛傲飛 的夜讀分享,是對 Go Modules 的一次很好的解讀,比較貼近工程實踐,我必然但願把這塊的知識更多的分享給你們,所以有了今天本篇文章,同時你們也能夠多關注 Go 夜讀,每週會經過 zoom 在線直播的方式分享 Go 相關的技術話題,但願對你們有所幫助。github
Go 1.11 推出的模塊(Modules)爲 Go 語言開發者打開了一扇新的大門,理想化的依賴管理解決方案使得 Go 語言朝着計算機編程史上的第一個依賴烏托邦(Deptopia)邁進。隨着模塊一塊兒推出的還有模塊代理協議(Module proxy protocol),經過這個協議咱們能夠實現 Go 模塊代理(Go module proxy),也就是依賴鏡像。golang
Go 1.13 的發佈爲模塊帶來了大量的改進,因此模塊的扶正就是此次 Go 1.13 發佈中開發者能直接感受到的最大變化。而問題在於,Go 1.13 中的 GOPROXY 環境變量擁有了一個在中國大陸沒法訪問到的默認值 proxy.golang.org
,通過你們在 golang/go#31755 中激烈的討論(有些人甚至將話提上升到了「自由世界」的層次),最終 Go 核心團隊仍然沒法爲中國開發者提供一個可在中國大陸訪問的官方模塊代理。算法
爲了從此中國的 Go 語言開發者能更好地進行開發,七牛雲推出了非營利性項目 goproxy.cn
,其目標是爲中國和世界上其餘地方的 Gopher 們提供一個免費的、可靠的、持續在線的且通過 CDN 加速的模塊代理。能夠預見將來是屬於模塊化的,因此 Go 語言開發者能越早切入模塊就能越早進入將來。編程
若是說 Go 1.11 和 Go 1.12 時因爲模塊的不完善你不肯意切入,那麼 Go 1.13 你則能夠大膽地開始放心使用。本次分享將討論如何使用模塊和模塊代理,以及在它們的使用中會常碰見的坑,還會講解如何快速搭建本身的私有模塊代理,並簡單地介紹一下七牛雲推出的 goproxy.cn
以及它的出現對於中國 Go 語言開發者來講重要在何處。緩存
Go modules (前身 vgo) 是 Go team (Russ Cox) 強推的一個理想化的類語言級依賴管理解決方案,它是和 Go1.11 一同發佈的,在 Go1.13 作了大量的優化和調整,目前已經變得比較不錯,若是你想用 Go modules,但還停留在 1.11/1.12 版本的話,強烈建議升級。安全
首先這並非亂說的,由於 Go modules 確實是被強推出來的,以下:bash
從他強制要求使用語義化版本控制這一點來講就很理想化了,以下:網絡
這個關鍵詞實際上是我本身瞎編的,我只是單純地我的認爲 Go modules 在設計上就像個語言級特性同樣,好比若是你的主版本號發生變動,那麼你的代碼裏的 import path 也得跟着變,它認爲主版本號不一樣的兩個模塊版本是徹底不一樣的兩個模塊。此外,Go moduels 在設計上跟 go 整個命令都結合得至關緊密,無處不在,因此我才說它是一個有點兒像語言級的特性,雖然不是太嚴謹。app
那麼在上文中提到的 Russ Cox 何許人也呢,不少人應該都知道他,他是 Go 這個項目目前代碼提交量最多的人,甚至是第二名的兩倍還要多。
Russ Cox 仍是 Go 如今的掌舵人(你們應該知道以前 Go 的掌舵人是 Rob Pike,可是據說因爲他本人不喜歡特朗普執政因此離開了美國,而後他歲數也挺大的了,因此也正在逐漸交權,不過如今仍是在參與 Go 的發展)。
Russ Cox 的我的能力至關強,看問題的角度也很獨特,這也就是爲何他剛一提出 Go modules 的概念就能引發那麼大範圍的響應。雖然是被強推的,但事實也證實當下的 Go modules 表現得確實很優秀,因此這代表必定程度上的 「獨裁」 仍是能夠接受的,至少能夠保證一個項目能更加專注地朝着一個方向發展。
總之,不管如何 Go modules 如今都成了 Go 語言的一個密不可分的組件。
Go modules 出現的目的之一就是爲了解決 GOPATH 的問題,也就至關因而拋棄 GOPATH 了。
Go modules 還處於 Opt-in 階段,就是你想用就用,不用就不用,不強制你。可是將來頗有可能 Go2 就強制使用了。
有一點須要糾正,就是「模塊」和「包」,也就是 「module」 和 「package」 這兩個術語並非等價的,是 「集合」 跟 「元素」 的關係,「模塊」 包含 「包」,「包」 屬於 「模塊」,一個 「模塊」 是零個、一個或多個 「包」 的集合。
module example.com/foobar
go 1.13
require (
example.com/apple v0.1.2
example.com/banana v1.2.3
example.com/banana/v2 v2.3.4
example.com/pineapple v0.0.0-20190924185754-1b0db40df49a
)
exclude example.com/banana v1.2.4
replace example.com/apple v0.1.2 => example.com/rda v0.1.0
replace example.com/banana => example.com/hugebanana
複製代碼
go.mod 是啓用了 Go moduels 的項目所必須的最重要的文件,它描述了當前項目(也就是當前模塊)的元信息,每一行都以一個動詞開頭,目前有如下 5 個動詞:
這裏的填寫格式基本爲包引用路徑+版本號,另外比較特殊的是 go $version
,目前從 Go1.13 的代碼裏來看,還只是個標識做用,暫時未知將來是否有更大的做用。
go.sum 是相似於好比 dep 的 Gopkg.lock 的一類文件,它詳細羅列了當前項目直接或間接依賴的全部模塊版本,並寫明瞭那些模塊版本的 SHA-256 哈希值以備 Go 在從此的操做中保證項目所依賴的那些模塊版本不會被篡改。
example.com/apple v0.1.2 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
example.com/apple v0.1.2/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= example.com/banana v1.2.3 h1:qHgHjyoNFV7jgucU8QZUuU4gcdhfs8QW1kw68OD2Lag=
example.com/banana v1.2.3/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= example.com/banana/v2 v2.3.4 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= example.com/banana/v2 v2.3.4/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
...
複製代碼
咱們能夠看到一個模塊路徑可能有以下兩種:
example.com/apple v0.1.2 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
example.com/apple v0.1.2/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
複製代碼
前者爲 Go modules 打包整個模塊包文件 zip 後再進行 hash 值,然後者爲針對 go.mod 的 hash 值。他們二者,要不就是同時存在,要不就是隻存在 go.mod hash。
那什麼狀況下會不存在 zip hash 呢,就是當 Go 認爲確定用不到某個模塊版本的時候就會省略它的 zip hash,就會出現不存在 zip hash,只存在 go.mod hash 的狀況。
這個環境變量主要是 Go modules 的開關,主要有如下參數:
auto:只在項目包含了 go.mod 文件時啓用 Go modules,在 Go 1.13 中仍然是默認值,詳見 :golang.org/issue/31857。
on:無腦啓用 Go modules,推薦設置,將來版本中的默認值,讓 GOPATH 今後成爲歷史。
off:禁用 Go modules。
這個環境變量主要是用於設置 Go 模塊代理,主要以下:
它的值是一個以英文逗號 「,」 分割的 Go module proxy 列表(稍後講解)
做用:用於使 Go 在後續拉取模塊版本時可以脫離傳統的 VCS 方式從鏡像站點快速拉取。它擁有一個默認:https://proxy.golang.org,direct
,但很惋惜 proxy.golang.org
在中國沒法訪問,故而建議使用 goproxy.cn
做爲替代,能夠執行語句:go env -w GOPROXY=https://goproxy.cn,direct
。
設置爲 「off」 :禁止 Go 在後續操做中使用任 何 Go module proxy。
剛剛在上面,咱們能夠發現值列表中有 「direct」 ,它又有什麼做用呢。其實值列表中的 「direct」 爲特殊指示符,用於指示 Go 回源到模塊版本的源地址去抓取(好比 GitHub 等),當值列表中上一個 Go module proxy 返回 404 或 410 錯誤時,Go 自動嘗試列表中的下一個,碰見 「direct」 時回源,碰見 EOF 時終止並拋出相似 「invalid version: unknown revision...」 的錯誤。
它的值是一個 Go checksum database,用於使 Go 在拉取模塊版本時(不管是從源站拉取仍是經過 Go module proxy 拉取)保證拉取到的模塊版本數據未經篡改,也能夠是「off」即禁止 Go 在後續操做中校驗模塊版本
格式 1:<SUMDB_NAME>+<PUBLIC_KEY>
。
格式 2:<SUMDB_NAME>+<PUBLIC_KEY> <SUMDB_URL>
。
擁有默認值:sum.golang.org
(之因此沒有按照上面的格式是由於 Go 對默認值作了特殊處理)。
可被 Go module proxy 代理 (詳見:Proxying a Checksum Database)。
sum.golang.org
在中國沒法訪問,故而更加建議將 GOPROXY 設置爲 goproxy.cn
,由於 goproxy.cn
支持代理 sum.golang.org
。
Go checksum database 主要用於保護 Go 不會從任何源頭拉到被篡改過的非法 Go 模塊版本,其做用(左)和工做機制(右)以下圖:
若是有興趣的小夥伴能夠看看 Proposal: Secure the Public Go Module Ecosystem,有詳細介紹其算法機制,若是想簡單一點,查看 go help module-auth
也是一個不錯的選擇。
這三個環境變量都是用在當前項目依賴了私有模塊,也就是依賴了由 GOPROXY 指定的 Go module proxy 或由 GOSUMDB 指定 Go checksum database 沒法訪問到的模塊時的場景
在使用上來說,好比 GOPRIVATE=*.corp.example.com
表示全部模塊路徑以 corp.example.com
的下一級域名 (如 team1.corp.example.com
) 爲前綴的模塊版本都將不通過 Go module proxy 和 Go checksum database,須要注意的是不包括 corp.example.com
自己。
這個主要是針對 Go modules 的全局緩存數聽說明,以下:
$GOPATH/pkg/mod
和 $GOPATH/pkg/sum
下,將來或將移至 $GOCACHE/mod
和$GOCACHE/sum
下( 可能會在當 $GOPATH
被淘汰後)。go clean -modcache
清理全部已緩存的模塊版本數據。另外在 Go1.11 以後 GOCACHE 已經不容許設置爲 off 了,我想着這也是爲了模塊數據緩存移動位置作準備,所以你們應該儘快作好適配。
go env -w GOBIN=$HOME/bin
。go env -w GO111MODULE=on
。go env -w GOPROXY=https://goproxy.cn,direct
# 在中國是必須的,由於它的默認值被牆了。go mod init <OPTIONAL_MODULE_PATH>
以生成 go.mod 文件。go help module-get
和 go help gopath-get
分別去了解 Go modules 啓用和未啓用兩種狀態下的 go get 的行爲go get
拉取新的依賴
go get golang.org/x/text@latest
master
分支的最新 commit:go get golang.org/x/text@master
go get golang.org/x/text@v0.3.2
go get golang.org/x/text@342b2e
go get -u
更新現有的依賴go mod download
下載 go.mod 文件中指明的全部依賴go mod tidy
整理現有的依賴go mod graph
查看現有的依賴結構go mod init
生成 go.mod 文件 (Go 1.13 中惟一一個能夠生成 go.mod 文件的子命令)go mod edit
編輯 go.mod 文件go mod vendor
導出現有的全部依賴 (事實上 Go modules 正在淡化 Vendor 的概念)go mod verify
校驗一個模塊是否被篡改過這裏咱們注意到有兩點比較特別,分別是:
go mod vendor
,由於 Go modules 正在淡化 Vendor 的概念,頗有可能 Go2 就去掉了。這裏主要是提到 Go1.13 新增了 go env -w
用於寫入環境變量,而寫入的地方是 os.UserConfigDir
所返回的路徑,須要注意的是 go env -w
不會覆寫。
這裏主要是指從舊有的依賴包管理工具(dep/glide 等)進行遷移時,由於 BUG 的緣由會致使不通過 GOPROXY 的代理,解決方法有以下兩個:
這裏主要想涉及兩塊知識點,以下:
在這裏再次強調了 Go Module Proxy 的做用(圖左),以及其對應的協議交互流程(圖右),有興趣的小夥伴能夠認真看一下。
在這塊主要介紹了 Goproxy 的實踐操做以及 goproxy.cn 的一些 Q&A 和 近況,以下:
Q:若是中國 Go 語言社區沒有我們本身家的 Go Module Proxy 會怎麼樣?
**A:**在 Go 1.13 中 GOPROXY 和 GOSUMDB 這兩個環境變量都有了在中國沒法 訪問的默認值,儘管我在 golang.org/issue/31755 裏努力嘗 試過,但最終仍然沒法爲我們中國的 Go 語言開發者謀得一個完美的解決方案。因此從今之後咱 們中國的全部 Go 語言開發者,只要是 使用了 Go modules 的,那麼都必須先修改 GOPROXY 和 GOSUMDB 才能正常使用 Go 作開發,不然可能連一個最簡單的程序都跑不起 來(只要它有依 賴第三方模 塊)。
Q: 我建立 Goproxy 中國(goproxy.cn)的主要緣由?
**A:**其實更早的時候,也就是今年年初我也曾 試圖在 golang.org/issue/31020 中請求 Go team 能想辦法避免那時的 GOPROXY 即將擁有的默認值能夠在中國正常訪問,但 Go team 彷佛也無能爲力,爲此我才堅決了建立 goproxy.cn 的信念。既然別人無法兒幫忙,那我們就 得本身動手,不爲別的,就爲了讓你們之後可以更愉快地使用 Go 語言配合 Go modules 作開發。
最初我先是和七牛雲的 許叔(七牛雲的 創始人兼 CEO 許式偉)提出了我打算 建立 goproxy.cn 的想法,本是抱着 試試看的目的,但沒想 到 許叔幾乎是沒有超過一分鐘的考慮便承認了個人想法並表示願意一塊兒推 動。那一陣子恰好遇上我在寫畢業論文,因此項目開發完後就 一直沒和七牛雲作交接,一直跑在個人我的服 務器上。直到有一次 goproxy.cn 被攻擊了,一下午的功夫 燒了我一百多美圓,而後我才 意識到這種項目真不能我的來作。我的來作不靠 譜,萬一依賴這個項目的人多了,項目再出什麼事兒,那就會給你們成沒必要要的損 失。因此我趕忙和七牛雲作了交接,把 goproxy.cn 徹底交給了七牛雲,甚至連域名都過戶了去。
此處呈現的是存儲大小,主要是針對模塊包代碼,而通常來說代碼並不會有多大,0-10MB,10-50MB 佔最大頭,也是可以理解,可是大於 100MB 的模塊包代碼就比較誇張了。
此時主要是展現了一下近期 goproxy.cn 的網絡數據狀況,我相信將來是會愈來愈高的,值得期待。
Q:如何解決 Go 1.13 在從 GitLab 拉取模塊版本時遇到的,Go 錯誤地按照非指望值的路徑尋找目標模塊版本結果導致最終目標模塊拉取失敗的問題?
**A:**GitLab 中配合 goget 而設置的 <meta>
存在些許問題,致使 Go 1.13 錯誤地識別了模塊的具體路徑,這是個 Bug,聽說在 GitLab 的新版本中已經被修復了,詳細內容能夠看 github.com/golang/go/i… 這個 Issue。而後目前的解決辦法的話除了升級 GitLab 的版本外,還能夠參考 github.com/developer-l… 這條回覆。
Q:使用 Go modules 時能夠同時依賴同一個模塊的不一樣的兩個或者多個小版本(修訂版本號不一樣)嗎?
**A:**不能夠的,Go modules 只能夠同時依賴一個模塊的不一樣的兩個或者多個大版本(主版本號不一樣)。好比能夠同時依賴 example.com/foobar@v1.2.3
和 example.com/foobar/v2@v2.3.4
,由於他們的模塊路徑(module path)不一樣,Go modules 規定主版本號不是 v0 或者 v1 時,那麼主版本號必須顯式地出如今模塊路徑的尾部。可是,同時依賴兩個或者多個小版本是不支持的。好比若是模塊 A 同時直接依賴了模塊 B 和模塊 C,且模塊 A 直接依賴的是模塊 C 的 v1.0.0 版本,而後模塊 B 直接依賴的是模塊 C 的 v1.0.1 版本,那麼最終 Go modules 會爲模塊 A 選用模塊 C 的 v1.0.1 版本而不是模塊 A 的 go.mod 文件中指明的 v1.0.0 版本。
這是由於 Go modules 認爲只要主版本號不變,那麼剩下的均可以直接升級採用最新的。可是若是採用了最新的結果致使項目 Break 掉了,那麼 Go modules 就會 Fallback 到上一個老的版本,好比在前面的例子中就會 Fallback 到 v1.0.0 版本。
Q:在 go.sum 文件中的一個模塊版本的 Hash 校驗數據什麼狀況下會成對出現,什麼狀況下只會存在一行?
**A:**一般狀況下,在 go.sum 文件中的一個模塊版本的 Hash 校驗數據會有兩行,前一行是該模塊的 ZIP 文件的 Hash 校驗數據,後一行是該模塊的 go.mod 文件的 Hash 校驗數據。可是也有些狀況下只會出現一行該模塊的 go.mod 文件的 Hash 校驗數據,而不包含該模塊的 ZIP 文件自己的 Hash 校驗數據,這個狀況發生在 Go modules 斷定爲你當前這個項目徹底用不到該模塊,根本也不會下載該模塊的 ZIP 文件,因此就不必對其做出 Hash 校驗保證,只須要對該模塊的 go.mod 文件做出 Hash 校驗保證便可,由於 go.mod 文件是用得着的,在深刻挖取項目依賴的時候要用。
Q:能不能更詳細地講解一下 go.mod 文件中的 replace 動詞的行爲以及用法?
**A:**這個 replace 動詞的做用是把一個「模塊版本」替換爲另一個「模塊版本」,這是「模塊版本」和「模塊版本(module path)」之間的替換,「=>」標識符前面的內容是待替換的「模塊版本」的「模塊路徑」,後面的內容是要替換的目標「模塊版本」的所在地,即路徑,這個路徑能夠是一個本地磁盤的相對路徑,也能夠是一個本地磁盤的絕對路徑,還能夠是一個網絡路徑,可是這個目標路徑並不會在從此你的項目代碼中做爲你「導入路徑(import path)」出現,代碼裏的「導入路徑」仍是得以你替換成的這個目標「模塊版本」的「模塊路徑」做爲前綴。
另外須要注意,Go modules 是不支持在 「導入路徑」 裏寫相對路徑的。舉個例子,若是項目 A 依賴了模塊 B,好比模塊 B 的「模塊路徑」是 example.com/b
,而後它在的磁盤路徑是 ~/b
,在項目 A 裏的 go.mod 文件中你有一行 replace example.com/b=>~/b
,而後在項目 A 裏的代碼中的「導入路基」就是 import"example.com/b"
,而不是 import"~/b"
,剩下的工做是 Go modules 幫你自動完成了的。
而後就是我在分享中也提到了, exclude 和 replace 這兩個動詞只做用於當前主模塊,也就是當前項目,它所依賴的那些其餘模塊版本中若是出現了你待替換的那個模塊版本的話,Go modules 仍是會爲你依賴的那個模塊版本去拉取你的這個待替換的模塊版本。
舉個例子,好比項目 A 直接依賴了模塊 B 和模塊 C,而後模塊 B 也直接依賴了模塊 C,那麼你在項目 A 中的 go.mod 文件裏的 replace c=>~/some/path/c
是隻會影響項目 A 裏寫的代碼中,而模塊 B 所用到的仍是你 replace 以前的那個 c,並非你替換成的 ~/some/path/c
這個。
在 Go1.13 發佈後,接觸 Go modules 和 Go module proxy 的人愈來愈多,常常在各類羣看到各類小夥伴在諮詢,包括我本身也貢獻了好幾枚 「坑」,所以我以爲傲飛的這一次 《Go Modules、Go Module Proxy 和 goproxy.cn》的技術分享,很是的有實踐意義。若是後續你們還有什麼建議或問題,歡迎隨時來討論。
最後,感謝 goproxy.cn 背後的人們(@七牛雲 和 @盛傲飛)對中國 Go 語言社區的無私貢獻和奉獻。