Modules
先把最重要的說了裸准,關(guān)于modules的最新詳細信息可以執(zhí)行命令go help modules
或者查這個長長的手冊Go Modules料身,另外modules弄清楚后很好用遷移成本低错英。
Go Module的好處,可以參考Demo:
- 代碼不用必須放GOPATH,可以放在任何目錄诈胜,終于不用做軟鏈了。
- Module依然可以用vendor冯事,如果不需要更新依賴焦匈,可以不必從遠程下載依賴代碼,同樣不必放GOPATH昵仅。
- 如果在一個倉庫可以直接引用缓熟,會自動識別模塊內(nèi)部的package,同樣不用鏈接到GOPATH岩饼。
Go最初是使用GOPATH存放依賴的包(項目和代碼)荚虚,這個GOPATH是公共的目錄,如果依賴的庫的版本不同就杯具了籍茧。2016年也就是7年后才支持vendor規(guī)范版述,就是將依賴本地化了,每個項目都使用自己的vendor文件夾寞冯,但這樣也解決不了沖突的問題(具體看下面的分析)渴析,相反導致各種包管理項目天下混戰(zhàn),參考pkg management tools吮龄。2017年也就是8年后俭茧,官方的vendor包管理器dep才確定方案,看起來命中注定的TheOne終于塵埃落定漓帚。不料2018年也就是9年后母债,又提出比較完整的方案versioning和vgo,這年Go1.11支持了Modules尝抖,2019年Go1.12和Go1.13改進了不少Modules內(nèi)容毡们,Go官方文檔推出一系列的Part 1 — Using Go Modules、Part 2 — Migrating To Go Modules和Part 3 — Publishing Go Modules昧辽,終于應該大概齊能明白衙熔,這次真的確定和肯定了,Go Modules是最終方案搅荞。
為什么要搞出GOPATH红氯、Vendor和GoModules這么多技術(shù)方案框咙?本質(zhì)上是為了創(chuàng)造就業(yè)崗位,一次創(chuàng)造了index痢甘、proxy和sum三個官網(wǎng)喇嘱,哈哈哈。當然技術(shù)上也是必須要這么做的塞栅,簡單來說是為了解決古老的DLL Hell
問題婉称,也就是依賴管理和版本管理的問題。版本說起來就是幾個數(shù)字构蹬,比如1.2.3
,實際上是非常復雜的問題悔据,推薦閱讀Semantic Versioning庄敛,假設定義了良好和清晰的API,我們用版本號來管理API的兼容性科汗;版本號一般定義為MAJOR.MINOR.PATCH
藻烤,Major變更時意味著不兼容的API變更,Minor是功能變更但是是兼容的头滔,Patch是BugFix也是兼容的怖亭,Major為0時表示API還不穩(wěn)定。由于Go的包是URL的坤检,沒有版本號信息兴猩,最初對于包的版本管理原則是必須一直保持接口兼容:
If an old package and a new package have the same import path, the new package must be backwards compatible with the old package.
試想下如果所有我們依賴的包,一直都是接口兼容的早歇,那就沒有啥問題倾芝,也沒有DLL Hell
〖可惜現(xiàn)實卻不是這樣晨另,如果我們提供過包就知道,對于持續(xù)維護和更新的包谱姓,在最初不可能提供一個永遠不變的接口借尿,變化的接口就是不兼容的了。就算某個接口可以不變屉来,還有依賴的包路翻,還有依賴的依賴的包,還有依賴的依賴的依賴的包奶躯,以此往復帚桩,要求世界上所有接口都不變,才不會有版本問題嘹黔,這么說起來账嚎,包管理是個極其難以解決的問題莫瞬,Go花了10年才確定最終方案就是這個原因了,下面舉例子詳細分析這個問題郭蕉。
備注:標準庫也有遇到接口變更的風險疼邀,比如Context是Go1.7才引入標準庫的,控制程序生命周期召锈,后續(xù)有很多接口的第一個參數(shù)都是
ctx context.Context
旁振,比如net.DialContext
就是后面加的一個函數(shù),而net.Dial
也是調(diào)用它涨岁。再比如http.Request.WithContext
則提供了一個函數(shù)拐袜,將context放在結(jié)構(gòu)體中傳遞,這是因為要再為每個Request的函數(shù)新增一個參數(shù)不太合適梢薪。從context對于標準庫的接口的變更蹬铺,可以看得到這里有些不一致性,有很多批評的聲音比如Context should go away for Go 2秉撇,就是覺得在標準庫中加context作為第一個參數(shù)不能理解甜攀,比如Read(ctx context.Context
等。
GOPATH & Vendor
咱們先看GOPATH的方式琐馆。Go引入外部的包规阀,是URL方式的,先在環(huán)境變量$GOROOT
中搜索瘦麸,然后在$GOPATH
中搜索谁撼,比如我們使用Errors,依賴包github.com/ossrs/go-oryx-lib/errors
瞎暑,代碼如下所示:
package main
import (
"fmt"
"github.com/ossrs/go-oryx-lib/errors"
)
func main() {
fmt.Println(errors.New("Hello, playground"))
}
如果我們直接運行會報錯彤敛,錯誤信息如下:
prog.go:5:2: cannot find package "github.com/ossrs/go-oryx-lib/errors" in any of:
/usr/local/go/src/github.com/ossrs/go-oryx-lib/errors (from $GOROOT)
/go/src/github.com/ossrs/go-oryx-lib/errors (from $GOPATH)
需要先下載這個依賴包go get -d github.com/ossrs/go-oryx-lib/errors
,然后運行就可以了了赌。下載后放在GOPATH中:
Mac $ ls -lh $GOPATH/src/github.com/ossrs/go-oryx-lib/errors
total 72
-rw-r--r-- 1 chengli.ycl staff 1.3K Sep 8 15:35 LICENSE
-rw-r--r-- 1 chengli.ycl staff 2.2K Sep 8 15:35 README.md
-rw-r--r-- 1 chengli.ycl staff 1.0K Sep 8 15:35 bench_test.go
-rw-r--r-- 1 chengli.ycl staff 6.7K Sep 8 15:35 errors.go
-rw-r--r-- 1 chengli.ycl staff 5.4K Sep 8 15:35 example_test.go
-rw-r--r-- 1 chengli.ycl staff 4.7K Sep 8 15:35 stack.go
如果我們依賴的包還依賴于其他的包墨榄,那么go get
會下載所有依賴的包到GOPATH。這樣是下載到公共的GOPATH的勿她,可以想到袄秩,這會造成幾個問題:
- 每次都要從網(wǎng)絡下載依賴,可能對于美國這個問題不存在逢并,但是對于中國之剧,要從GITHUB上下載很大的項目,是個很麻煩的問題砍聊,還沒有斷點續(xù)傳背稼。
- 如果兩個項目,依賴了GOPATH了項目玻蝌,如果一個更新會導致另外一個項目出現(xiàn)問題蟹肘。比如新的項目下載了最新的依賴庫词疼,可能會導致其他項目出問題。
- 無法獨立管理版本號和升級帘腹,獨立依賴不同的包的版本贰盗。比如A項目依賴1.0的庫,而B項目依賴2.0的庫阳欲。注意:如果A和B都是庫的話舵盈,這個問題還是無解的,它們可能會同時被一個項目引用球化,如果A和B是最終的應用是沒有問題秽晚,應用可以用不同的版本,它們在自己的目錄筒愚。
為了解決這些問題爆惧,引入了vendor,在src下面有個vendor目錄锨能,將依賴的庫都下載到這個目錄,同時會有描述文件說明依賴的版本芍耘,這樣可以實現(xiàn)升級不同庫的升級址遇。參考vendor,以及官方的包管理器dep斋竞。但是vendor并沒有解決所有的問題倔约,特別是包的不兼容版本的問題,只解決了項目或應用坝初,也就是會編譯出二進制的項目所依賴庫的問題浸剩。
咱們把上面的例子用vendor實現(xiàn),先要把項目軟鏈或者挪到GOPATH里面去鳄袍,若沒有dep工具可以參考Installation安裝绢要,然后執(zhí)行下面的命令來將依賴導入到vendor目錄:
dep init && dep ensure
這樣依賴的文件就會放在vendor下面,編譯時也不再需要從遠程下載了:
├── Gopkg.lock
├── Gopkg.toml
├── t.go
└── vendor
└── github.com
└── ossrs
└── go-oryx-lib
└── errors
├── errors.go
└── stack.go
Remark: Vendor也會選擇版本拗小,也有版本管理重罪,但每個包它只會選擇一個版本,也就是本質(zhì)上是本地化的GOPATH哀九,如果出現(xiàn)鉆石依賴和沖突還是無解剿配,下面會詳細說明。
何為版本沖突阅束?
我們來看GOPATH和Vencor無法解決的一個問題呼胚,版本依賴問題的一個例子Semantic Import Versioning,考慮鉆石依賴的情況息裸,用戶依賴于兩個云服務商的SDK蝇更,而他們可能都依賴于公共的庫沪编,形成一個鉆石形狀的依賴,用戶依賴AWS和Azure而它們都依賴OAuth:
如果公共庫package(這里是OAuth)的導入路徑一樣(比如是github.com/google/oauth)簿寂,但是做了非兼容性變更漾抬,發(fā)布了OAuth-r1和OAuth-r2,其中一個云服務商更新了自己的依賴常遂,另外一個沒有更新纳令,就會造成沖突,他們依賴的版本不同:
在Go中無論怎么修改都無法支持這種情況克胳,除非在package的路徑中加入版本語義進去平绩,也就是在路徑上帶上版本信息(這就是Go Modules了),這和優(yōu)雅沒有關(guān)系漠另,這實際上是最好的使用體驗:
另外做法就是改變包路徑捏雌,這要求包提供者要每個版本都要使用一個特殊的名字,但使用者也不能分辨這些名字代表的含義笆搓,自然也不知道如何選擇哪個版本性湿。
先看看Go Modules創(chuàng)造的三大就業(yè)崗位,index負責索引满败、proxy負責代理緩存和sum負責簽名校驗肤频,它們之間的關(guān)系在Big Picture中有描述∷隳可見go-get會先從index獲取指定package的索引宵荒,然后從proxy下載數(shù)據(jù),最后從sum來獲取校驗信息:
vgo全面實踐
還是先跟著官網(wǎng)的三部曲净嘀,先了解下modules的基本用法报咳,后面補充下特別要注意的問題就差不多齊了。首先是Using Go Modules挖藏,如何使用modules暑刃,還是用上面的例子,代碼不用改變膜眠,只需要執(zhí)行命令:
go mod init private.me/app && go run t.go
Remark:和vendor并不相同稍走,modules并不需要在GOPATH下面才能創(chuàng)建,所以這是非常好的柴底。
執(zhí)行的結(jié)果如下婿脸,可以看到vgo查詢依賴的庫,下載后解壓到了cache柄驻,并生成了go.mod和go.sum狐树,緩存的文件在$GOPATH/pkg
下面:
Mac:gogogo chengli.ycl$ go mod init private.me/app && go run t.go
go: creating new go.mod: module private.me/app
go: finding github.com/ossrs/go-oryx-lib v0.0.7
go: downloading github.com/ossrs/go-oryx-lib v0.0.7
go: extracting github.com/ossrs/go-oryx-lib v0.0.7
Hello, playground
Mac:gogogo chengli.ycl$ cat go.mod
module private.me/app
go 1.13
require github.com/ossrs/go-oryx-lib v0.0.7 // indirect
Mac:gogogo chengli.ycl$ cat go.sum
github.com/ossrs/go-oryx-lib v0.0.7 h1:k8ml3ZLsjIMoQEdZdWuy8zkU0w/fbJSyHvT/s9NyeCc=
github.com/ossrs/go-oryx-lib v0.0.7/go.mod h1:i2tH4TZBzAw5h+HwGrNOKvP/nmZgSQz0OEnLLdzcT/8=
Mac:gogogo chengli.ycl$ tree $GOPATH/pkg
/Users/winlin/go/pkg
├── mod
│ ├── cache
│ │ ├── download
│ │ │ ├── github.com
│ │ │ │ └── ossrs
│ │ │ │ └── go-oryx-lib
│ │ │ │ └── @v
│ │ │ │ ├── list
│ │ │ │ ├── v0.0.7.info
│ │ │ │ ├── v0.0.7.zip
│ │ │ └── sumdb
│ │ │ └── sum.golang.org
│ │ │ ├── lookup
│ │ │ │ └── github.com
│ │ │ │ └── ossrs
│ │ │ │ └── go-oryx-lib@v0.0.7
│ └── github.com
│ └── ossrs
│ └── go-oryx-lib@v0.0.7
│ ├── errors
│ │ ├── errors.go
│ │ └── stack.go
└── sumdb
└── sum.golang.org
└── latest
可以手動升級某個庫,即go get這個庫:
Mac:gogogo chengli.ycl$ go get github.com/ossrs/go-oryx-lib
go: finding github.com/ossrs/go-oryx-lib v0.0.8
go: downloading github.com/ossrs/go-oryx-lib v0.0.8
go: extracting github.com/ossrs/go-oryx-lib v0.0.8
Mac:gogogo chengli.ycl$ cat go.mod
module private.me/app
go 1.13
require github.com/ossrs/go-oryx-lib v0.0.8
升級某個包到指定版本鸿脓,可以帶上版本號抑钟,例如go get github.com/ossrs/go-oryx-lib@v0.0.8
涯曲。當然也可以降級,比如現(xiàn)在是v0.0.8在塔,可以go get github.com/ossrs/go-oryx-lib@v0.0.7
降到v0.0.7版本幻件。也可以升級所有依賴的包,執(zhí)行go get -u
命令就可以蛔溃。查看依賴的包和版本绰沥,以及依賴的依賴的包和版本,可以執(zhí)行go list -m all
命令贺待。查看指定的包有哪些版本徽曲,可以用go list -m -versions github.com/ossrs/go-oryx-lib
命令。
Note: 關(guān)于vgo如何選擇版本麸塞,可以參考Minimal Version Selection秃臣。
如果依賴了某個包大版本的多個版本,那么會選擇這個大版本最高的那個哪工,比如:
- 若a依賴v1.0.1奥此,b依賴v1.2.3,程序依賴a和b時雁比,最終使用v1.2.3得院。
- 若a依賴v1.0.1,d依賴v0.0.7章贞,程序依賴a和d時,最終使用v1.0.1非洲,也就是認為v1是兼容v0的鸭限。
比如下面代碼,依賴了四個包两踏,而這四個包依賴了某個包的不同版本败京,分別選擇不同的包,執(zhí)行rm -f go.mod && go mod init private.me/app && go run t.go
梦染,可以看到選擇了不同的版本赡麦,始終選擇的是大版本最高的那個(也就是滿足要求的最小版本):
package main
import (
"fmt"
"github.com/winlinvip/mod_ref_a" // 1.0.1
"github.com/winlinvip/mod_ref_b" // 1.2.3
"github.com/winlinvip/mod_ref_c" // 1.0.3
"github.com/winlinvip/mod_ref_d" // 0.0.7
)
func main() {
fmt.Println("Hello",
mod_ref_a.Version(),
mod_ref_b.Version(),
mod_ref_c.Version(),
mod_ref_d.Version(),
)
}
若包需要升級大版本,則需要在路徑上加上版本帕识,包括本身的go.mod中的路徑泛粹,依賴這個包的go.mod,依賴它的代碼肮疗,比如下面的例子晶姊,同時使用了v1和v2兩個版本(只用一個也可以):
package main
import (
"fmt"
"github.com/winlinvip/mod_major_releases"
v2 "github.com/winlinvip/mod_major_releases/v2"
)
func main() {
fmt.Println("Hello",
mod_major_releases.Version(),
v2.Version2(),
)
}
運行這個程序后,可以看到go.mod中導入了兩個包:
module private.me/app
go 1.13
require (
github.com/winlinvip/mod_major_releases v1.0.1
github.com/winlinvip/mod_major_releases/v2 v2.0.3
)
Remark: 如果需要更新v2的指定版本伪货,那么路徑中也必須帶v2们衙,也就是所有v2的路徑必須帶v2钾怔,比如
go get github.com/winlinvip/mod_major_releases/v2@v2.0.3
。
而庫提供大版本也是一樣的蒙挑,參考mod_major_releases/v2宗侦,主要做的事情:
- 新建v2的分支,
git checkout -b v2
忆蚀,比如https://github.com/winlinvip/mod_major_releases/tree/v2矾利。 - 修改go.mod的描述,路徑必須帶v2蜓谋,比如
module github.com/winlinvip/mod_major_releases/v2
梦皮。 - 提交后打v2的tag,比如
git tag v2.0.0
桃焕,分支和tag都要提交到git剑肯。
其中g(shù)o.mod更新如下:
module github.com/winlinvip/mod_major_releases/v2
go 1.13
代碼更新如下,由于是大版本观堂,所以就變更了函數(shù)名稱:
package mod_major_releases
func Version2() string {
return "mmv/2.0.3"
}
Note: 更多信息可以參考Modules: v2让网,還有Russ Cox: From Repository to Modules介紹了兩種方式,常見的就是上面的分支方式的例子师痕,還有一種文件夾方式溃睹。
Go Modules特別需要注意的問題:
- 對于公開的package,如果go.mod中描述的package胰坟,和公開的路徑不相同因篇,比如go.mod是
private.me/app
,而發(fā)布到github.com/winlinvip/app
笔横,當然其他項目import這個包時會出現(xiàn)錯誤竞滓。對于庫,也就是希望別人依賴的包吹缔,go.mod描述的和發(fā)布的路徑商佑,以及package名字都應該保持一致。 - 如果一個包沒有發(fā)布任何版本厢塘,則會取最新的commit和日期茶没,格式為v0.0.0-日期-commit號,比如
v0.0.0-20191028070444-45532e158b41
晚碾,參考Pseudo Versions抓半。版本號可以從v0.0.x
開始,比如v0.0.1
或者v0.0.3
或者v0.1.0
或者v1.0.1
之類格嘁,沒有強制要求必須要是1.0開始的發(fā)布版本琅关。 -
mod replace在子module無效,只在編譯的那個top level有效,也就是在最終生成binary的go.mod中定義才有效涣易,官方的說明是為了讓最終生成時控制依賴画机。例如想要把
github.com/pkg/errors
重寫為github.com/winlinvip/errors
這個包,正確做法參考分支replace_errors新症;若不在主模塊(top level)中replace參考replace_in_submodule步氏,只在子模塊中定義了replace但會被忽略;如果在主模塊replace會生效replace_errors徒爹,而且在主模塊依賴掉子募孕眩快依賴的模塊也生效replace_deps_of_submodule。不過在子穆⌒幔快中也能replace界阁,這個預感到會是個混淆的地方。有一個例子就是fork倉庫后修改后自己使用胖喳,這時候go.mod的package當然也變了泡躯,參考Migrating Go1.13 Errors,Go1.13的errors支持了Unwrap接口丽焊,這樣可以拿到root error较剃,而pkg/errors使用的則是Cause(err)函數(shù)來獲取root error,而提的PR沒有支持技健,pkg/errors不打算支持Go1.13的方式写穴,作者建議fork來解決,所以就可以使用go mod replace來將fork的url替換pkg/errors雌贱。 -
go get
并非將每個庫都更新后取最新的版本啊送,比如庫github.com/winlinvip/mod_minor_versions
有v1.0.1、v1.1.2兩個版本欣孤,目前依賴的是v1.1.2版本馋没,如果庫更新到了v1.2.3版本,立刻使用go get -u
并不會更新到v1.2.3导街,執(zhí)行go get -u github.com/winlinvip/mod_minor_versions
也一樣不會更新,除非顯式更新go get github.com/winlinvip/mod_minor_versions@v1.2.3
才會使用這個版本纤子,需要等一定時間后才會更新搬瑰。 - 對于大版本比如v2,必須用go.mod描述控硼,直接引用也可以比如
go get github.com/winlinvip/mod_major_error@v2.0.0
泽论,會提示v2.0.0+incompatible
,意思就是默認都是v0和v1卡乾,而直接打了v2.0.0的tag翼悴,雖然版本上匹配到了,但實際上是把v2當做v1在用,有可能會有不兼容的問題鹦赎〉危或者說,一般來說v2.0.0的這個tag古话,一定會有接口的變更(否則就不能叫v2了)雏吭,如果沒有用go.mod會把這個認為是v1,自然可能會有兼容問題了陪踩。 - 更新大版本時必須帶版本號比如
go get github.com/winlinvip/mod_major_releases/v2@v2.0.1
杖们,如果路徑中沒有這個v2則會報錯無法更新,比如go get github.com/winlinvip/mod_major_releases@v2.0.1
肩狂,錯誤消息是invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1
摘完,這個就是說mod_major_releases這個下面有g(shù)o.mod描述的版本是v0或v1,但后面指定的版本是@v2所以不匹配無法更新傻谁。 - 和上面的問題一樣孝治,如果在go.mod中,大版本路徑中沒有帶版本栅螟,比如
require github.com/winlinvip/mod_major_releases v2.0.3
荆秦,一樣會報錯module contains a go.mod file, so major version must be compatible: should be v0 or v1
,這個有點含糊因為包定義的go.mod是v2的力图,這個錯誤的意思是步绸,require的那個地方,要求的是v0或v1吃媒,而實際上版本是v2.0.3瓤介,這個和手動要求更新go get github.com/winlinvip/mod_major_releases@v2.0.1
是一回事。 - 注意三大崗位有cache赘那,比如mod_major_error@v5.0.0的go.mod描述有錯誤刑桑,應該是v5,而不是v3募舟。如果在打完tag后祠斧,獲取了這個版本
go get github.com/winlinvip/mod_major_error/v5
,會提示錯誤but does not contain package github.com/winlinvip/mod_major_error/v5
等錯誤拱礁,如果刪除這個tag后再推v5.0.0琢锋,還是一樣的錯誤,因為index和goproxy有緩存這個版本的信息呢灶。解決版本就是升一個版本v5.0.1吴超,直接獲取這個版本就可以,比如go get github.com/winlinvip/mod_major_error/v5@v5.0.1
鸯乃,這樣才沒有問題鲸阻。詳細參考Semantic versions and modules。 - 和上面一樣的問題,如果在版本沒有發(fā)布時鸟悴,就有g(shù)o get的請求陈辱,會造成版本發(fā)布后也無法獲取這個版本。比如
github.com/winlinvip/mod_major_error
沒有打版本v3.0.1遣臼,就請求go get github.com/winlinvip/mod_major_error/v3@v3.0.1
硼一,會提示沒有這個版本染坯。如果后面再打這個tag,就算有這個tag后,也會提示401找不到reading https://sum.golang.org/lookup/github.com/winlinvip/mod_major_error/v3@v3.0.1: 410 Gone
求厕。只能再升級個版本族阅,打個新的tag比如v3.0.2才能獲取到认臊。
總結(jié)起來說:
- GOPATH扳炬,自從默認為
$HOME/go
后,很好用蝙眶,依賴的包都緩存在這個公共的地方季希,只要項目不大,完全是很直接很好用的方案幽纷。一般情況下也夠用了式塌,估計GOPATH可能會被長期使用,畢竟習慣才是最可怕的友浸,習慣是活的最久的峰尝,習慣就成為了一種生活方式,用余老師的話說“文化是一種精神價值和生活方式收恢,最終體現(xiàn)了集體人格”武学。 - vendor,vendor緩存依賴在項目本地伦意,能解決很多問題了火窒,比GOPATH更好的是對于依賴可以定期更新,一般的項目中驮肉,對于依賴都是有需要了去更新熏矿,而不是每次編譯都去取最新的代碼。所以vendor還是非常實用的离钝,如果能保持比較克制票编,不要因為要用一個函數(shù)就要依賴一個包,結(jié)果這個包依賴了十個奈辰,這十個又依賴了百個栏妖。
- vgo/modules乱豆,代碼使用上沒有差異奖恰;在版本更新時比如明確需要導入v2的包,才會在導入url上有差異;代碼緩存上使用proxy來下載瑟啃,緩存在GOPATH的pkg中论泛,由于有版本信息所以不會有沖突;會更安全蛹屿,因為有sum在屁奏;會更靈活,因為有index和proxy在错负。
如何無縫遷移坟瓢?
現(xiàn)有GOPATH和vendor的項目,如何遷移到modules呢犹撒?官方的遷移指南Migrating to Go Modules折联,說明了項目會有三種狀態(tài):
- 完全新的還沒開始的項目。那么就按照上面的方式识颊,用modules就好了诚镰。
- 現(xiàn)有的項目,使用了其他依賴管理祥款,也就是vendor清笨,比如dep或glide等。go mod會將現(xiàn)有的格式轉(zhuǎn)換成modules刃跛,支持的格式參考這里抠艾。其實modules還是會繼續(xù)支持vendor,參考下面的詳細描述奠伪。
- 現(xiàn)有的項目跌帐,沒有使用任何依賴管理,也就是GOPATH绊率。注意go mod init的包路徑谨敛,需要和之前導出的一樣,特別是Go1.4支持的import comment滤否,可能和倉庫的路徑并不相同脸狸,比如倉庫在
https://go.googlesource.com/lint
,而包路徑是golang.org/x/lint藐俺。
Note: 特別注意如果是庫支持了v2及以上的版本炊甲,那么路徑中一定需要包含v2,比如
github.com/russross/blackfriday/v2
欲芹。而且需要更新引用了這個包的v2的庫卿啡,比較蛋疼,不過這種情況還好是不多的菱父。
咱們先看一個使用GOPATH的例子颈娜,我們新建一個測試包剑逃,先以GOPATH方式提供,參考github.com/winlinvip/mod_gopath官辽,依賴于github.com/pkg/errors蛹磺,rsc.io/quote和github.com/gorilla/websocket。
再看一個vendor的例子同仆,將這個GOPATH的項目萤捆,轉(zhuǎn)成vendor項目,參考github.com/winlinvip/mod_vendor俗批,安裝完dep后執(zhí)行dep init
就可以了俗或,可以查看依賴:
chengli.ycl$ dep status
PROJECT CONSTRAINT VERSION REVISION LATEST PKGS USED
github.com/gorilla/websocket ^1.4.1 v1.4.1 c3e18be v1.4.1 1
github.com/pkg/errors ^0.8.1 v0.8.1 ba968bf v0.8.1 1
golang.org/x/text v0.3.2 v0.3.2 342b2e1 v0.3.2 6
rsc.io/quote ^3.1.0 v3.1.0 0406d72 v3.1.0 1
rsc.io/sampler v1.99.99 v1.99.99 732a3c4 v1.99.99 1
接下來轉(zhuǎn)成modules包,先拷貝一份github.com/winlinvip/mod_gopath代碼(這里為了演示差別所以拷貝了一份岁忘,直接轉(zhuǎn)換也是可以的)蕴侣,變成github.com/winlinvip/mod_gopath_vgo,然后執(zhí)行命令go mod init github.com/winlinvip/mod_gopath_vgo && go test ./... && go mod tidy
臭觉,接著發(fā)布版本比如git add . && git commit -am "Migrate to vgo" && git tag v1.0.1 && git push origin v1.0.1
:
Mac:mod_gopath_vgo chengli.ycl$ cat go.mod
module github.com/winlinvip/mod_gopath_vgo
go 1.13
require (
github.com/gorilla/websocket v1.4.1
github.com/pkg/errors v0.8.1
rsc.io/quote v1.5.2
)
depd的vendor的項目也是一樣的昆雀,先拷貝一份github.com/winlinvip/mod_vendor成github.com/winlinvip/mod_vendor_vgo,執(zhí)行命令go mod init github.com/winlinvip/mod_vendor_vgo && go test ./... && go mod tidy
蝠筑,接著發(fā)布版本比如git add . && git commit -am "Migrate to vgo" && git tag v1.0.3 && git push origin v1.0.3
:
module github.com/winlinvip/mod_vendor_vgo
go 1.13
require (
github.com/gorilla/websocket v1.4.1
github.com/pkg/errors v0.8.1
golang.org/x/text v0.3.2 // indirect
rsc.io/quote v1.5.2
rsc.io/sampler v1.99.99 // indirect
)
這樣就可以在其他項目中引用它了:
package main
import (
"fmt"
"github.com/winlinvip/mod_gopath"
"github.com/winlinvip/mod_gopath/core"
"github.com/winlinvip/mod_vendor"
vcore "github.com/winlinvip/mod_vendor/core"
"github.com/winlinvip/mod_gopath_vgo"
core_vgo "github.com/winlinvip/mod_gopath_vgo/core"
"github.com/winlinvip/mod_vendor_vgo"
vcore_vgo "github.com/winlinvip/mod_vendor_vgo/core"
)
func main() {
fmt.Println("mod_gopath is", mod_gopath.Version(), core.Hello(), core.New("gopath"))
fmt.Println("mod_vendor is", mod_vendor.Version(), vcore.Hello(), vcore.New("vendor"))
fmt.Println("mod_gopath_vgo is", mod_gopath_vgo.Version(), core_vgo.Hello(), core_vgo.New("vgo(gopath)"))
fmt.Println("mod_vendor_vgo is", mod_vendor_vgo.Version(), vcore_vgo.Hello(), vcore_vgo.New("vgo(vendor)"))
}
Note: 對于私有項目狞膘,可能無法使用三大件來索引校驗,那么可以設置GOPRIVATE來禁用校驗什乙,參考Module configuration for non public modules挽封。
vgo with vendor
Vendor并非不能用,可以用modules同時用vendor臣镣,參考How do I use vendoring with modules? Is vendoring going away?辅愿,其實vendor并不會消亡,Go社區(qū)有過詳細的討論vgo & vendoring決定在modules中支持vendor忆某,有人覺得点待,把vendor作為modules的存儲目錄挺好的啊。在modules中開啟vendor有幾個步驟:
- 先轉(zhuǎn)成modules弃舒,參考前面的步驟癞埠,也可以新建一個modules例如
go mod init xxx
,然后把代碼寫好聋呢,就是一個標準的module苗踪,不過文件是存在$GOPATH/pkg
的,參考github.com/winlinvip/mod_vgo_with_vendor@v1.0.0削锰。 -
go mod vendor
通铲,這一步做的事情,就是將modules中的文件都放到vendor中來器贩。當然由于go.mod也存在颅夺,當然也知道這些文件的版本信息央串,也不會造成什么問題,只是新建了一個vendor目錄而已碗啄。在別人看起來這就是這正常的modules,和vendor一點影響都沒有稳摄。參考github.com/winlinvip/mod_vgo_with_vendor@v1.0.1稚字。 -
go build -mod=vendor
,修改mod這個參數(shù)厦酬,默認是會忽略這個vendor目錄了胆描,加上這個參數(shù)后就會從vendor目錄加載代碼(可以把$GOPATH/pkg
刪掉發(fā)現(xiàn)也不會下載代碼)。當然其他也可以加這個flag仗阅,比如go test -mod=vendor ./...
或者go run -mod=vendor .
昌讲。
調(diào)用這個包時,先使用modules把依賴下載下來减噪,比如go mod init private.me/app && go run t.go
:
package main
import (
"fmt"
"github.com/winlinvip/mod_vendor_vgo"
vcore_vgo "github.com/winlinvip/mod_vendor_vgo/core"
"github.com/winlinvip/mod_vgo_with_vendor"
vvgo_core "github.com/winlinvip/mod_vgo_with_vendor/core"
)
func main() {
fmt.Println("mod_vendor_vgo is", mod_vendor_vgo.Version(), vcore_vgo.Hello(), vcore_vgo.New("vgo(vendor)"))
fmt.Println("mod_vgo_with_vendor is", mod_vgo_with_vendor.Version(), vvgo_core.Hello(), vvgo_core.New("vgo with vendor"))
}
然后一樣的也要轉(zhuǎn)成vendor短绸,執(zhí)行命令go mod vendor && go run -mod=vendor t.go
。如果有新的依賴的包需要導入筹裕,則需要先使用modules方式導入一次醋闭,然后go mod vendor
拷貝到vendor。其實一句話來說朝卒,modules with vendor就是最后提交代碼時证逻,把依賴全部放到vendor下面的一種方式。
Note: IDE比如goland的設置里面抗斤,有個
Preferences /Go /Go Modules(vgo) /Vendoring mode
囚企,這樣會從項目的vendor目錄解析,而不是從全局的cache瑞眼。如果不需要導入新的包龙宏,可以默認開啟vendor方式,執(zhí)行命令go env -w GOFLAGS='-mod=vendor'
伤疙。
Concurrency&Control
并發(fā)是服務器的基本問題烦衣,并發(fā)控制當然也是基本問題,Go并不能避免這個問題掩浙,只是將這個問題更簡化花吟。
Links
由于簡書限制了文章字數(shù),只好分成不同章節(jié):
- Overview 為何Go有時候也叫Golang?為何要選擇Go作為服務器開發(fā)的語言厨姚?是沖動衅澈?還是騷動?Go的重要里程碑和事件谬墙,當年吹的那些牛逼今布,都實現(xiàn)了哪些经备?
- Could Not Recover 君可知,有什么panic是無法recover的部默?包括超過系統(tǒng)線程限制侵蒙,以及map的競爭寫。當然一般都能recover傅蹂,比如Slice越界纷闺、nil指針、除零份蝴、寫關(guān)閉的chan等犁功。
- Errors 為什么Go2的草稿3個有2個是關(guān)于錯誤處理的?好的錯誤處理應該怎么做婚夫?錯誤和異常機制的差別是什么浸卦?錯誤處理和日志如何配合?
- Logger 為什么標準庫的Logger是完全不夠用的案糙?怎么做日志切割和輪轉(zhuǎn)限嫌?怎么在混成一坨的服務器日志中找到某個連接的日志?甚至連接中的流的日志时捌?怎么做到簡潔又夠用萤皂?
- Interfaces 什么是面向?qū)ο蟮腟OLID原則?為何Go更符合SOLID匣椰?為何接口組合比繼承多態(tài)更具有正交性裆熙?Go類型系統(tǒng)如何做到looser, organic, decoupled, independent, and therefore scalable?一般軟件中如果出現(xiàn)數(shù)學禽笑,要么真的牛逼要么裝逼入录。正交性這個數(shù)學概念在Go中頻繁出現(xiàn),是神仙還是妖怪佳镜?為何接口設計要考慮正交性僚稿?
- Modules 如何避免依賴地獄(Dependency Hell)?小小的版本號為何會帶來大災難蟀伸?Go為什么推出了GOPATH蚀同、Vendor還要搞module和vgo?新建了16個倉庫做測試啊掏,碰到了9個坑蠢络,搞清楚了gopath和vendor如何遷移,以及vgo with vendor如何使用(畢竟生產(chǎn)環(huán)境不能每次都去外網(wǎng)下載)迟蜜。
- Concurrency & Control 服務器中的并發(fā)處理難在哪里刹孔?為什么說Go并發(fā)處理優(yōu)勢占領(lǐng)了云計算開發(fā)語言市場?什么是C10K娜睛、C10M問題髓霞?如何管理goroutine的取消卦睹、超時和關(guān)聯(lián)取消?為何Go1.7專門將context放到了標準庫方库?context如何使用结序,以及問題在哪里?
- Engineering Go在工程化上的優(yōu)勢是什么纵潦?為什么說Go是一門面向工程的語言徐鹤?覆蓋率要到多少比較合適?什么叫代碼可測性酪穿?為什么良好的庫必須先寫Example?
- Go2 Transition Go2會像Python3不兼容Python2那樣作嗎晴裹?C和C++的語言演進可以有什么不同的收獲被济?Go2怎么思考語言升級的問題?
- SRS & Others Go在流媒體服務器中的使用涧团。Go的GC靠譜嗎只磷?Twitter說相當?shù)目孔V,有圖有真相泌绣。為何Go的聲明語法是那樣钮追?C的又是怎樣?是拍的大腿阿迈,還是拍的腦袋元媚?