Go開發(fā)關(guān)鍵技術(shù)指南:Modules

Modules

先把最重要的說了裸准,關(guān)于modules的最新詳細信息可以執(zhí)行命令go help modules或者查這個長長的手冊Go Modules料身,另外modules弄清楚后很好用遷移成本低错英。

Go Module的好處,可以參考Demo

  1. 代碼不用必須放GOPATH,可以放在任何目錄诈胜,終于不用做軟鏈了。
  2. Module依然可以用vendor冯事,如果不需要更新依賴焦匈,可以不必從遠程下載依賴代碼,同樣不必放GOPATH昵仅。
  3. 如果在一個倉庫可以直接引用缓熟,會自動識別模塊內(nèi)部的package,同樣不用鏈接到GOPATH岩饼。

Go最初是使用GOPATH存放依賴的包(項目和代碼)荚虚,這個GOPATH是公共的目錄,如果依賴的庫的版本不同就杯具了籍茧。2016年也就是7年后才支持vendor規(guī)范版述,就是將依賴本地化了,每個項目都使用自己的vendor文件夾寞冯,但這樣也解決不了沖突的問題(具體看下面的分析)渴析,相反導致各種包管理項目天下混戰(zhàn),參考pkg management tools吮龄。2017年也就是8年后俭茧,官方的vendor包管理器dep才確定方案,看起來命中注定的TheOne終于塵埃落定漓帚。不料2018年也就是9年后母债,又提出比較完整的方案versioningvgo,這年Go1.11支持了Modules尝抖,2019年Go1.12和Go1.13改進了不少Modules內(nèi)容毡们,Go官方文檔推出一系列的Part 1 — Using Go ModulesPart 2 — Migrating To Go ModulesPart 3 — Publishing Go Modules昧辽,終于應該大概齊能明白衙熔,這次真的確定和肯定了,Go Modules是最終方案搅荞。

為什么要搞出GOPATH红氯、Vendor和GoModules這么多技術(shù)方案框咙?本質(zhì)上是為了創(chuàng)造就業(yè)崗位,一次創(chuàng)造了index痢甘、proxysum三個官網(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的勿她,可以想到袄秩,這會造成幾個問題:

  1. 每次都要從網(wǎng)絡下載依賴,可能對于美國這個問題不存在逢并,但是對于中國之剧,要從GITHUB上下載很大的項目,是個很麻煩的問題砍聊,還沒有斷點續(xù)傳背稼。
  2. 如果兩個項目,依賴了GOPATH了項目玻蝌,如果一個更新會導致另外一個項目出現(xiàn)問題蟹肘。比如新的項目下載了最新的依賴庫词疼,可能會導致其他項目出問題。
  3. 無法獨立管理版本號和升級帘腹,獨立依賴不同的包的版本贰盗。比如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:

image.png

如果公共庫package(這里是OAuth)的導入路徑一樣(比如是github.com/google/oauth)簿寂,但是做了非兼容性變更漾抬,發(fā)布了OAuth-r1和OAuth-r2,其中一個云服務商更新了自己的依賴常遂,另外一個沒有更新纳令,就會造成沖突,他們依賴的版本不同:

image.png

在Go中無論怎么修改都無法支持這種情況克胳,除非在package的路徑中加入版本語義進去平绩,也就是在路徑上帶上版本信息(這就是Go Modules了),這和優(yōu)雅沒有關(guān)系漠另,這實際上是最好的使用體驗:

image.png

另外做法就是改變包路徑捏雌,這要求包提供者要每個版本都要使用一個特殊的名字,但使用者也不能分辨這些名字代表的含義笆搓,自然也不知道如何選擇哪個版本性湿。

先看看Go Modules創(chuàng)造的三大就業(yè)崗位,index負責索引满败、proxy負責代理緩存和sum負責簽名校驗肤频,它們之間的關(guān)系在Big Picture中有描述∷隳可見go-get會先從index獲取指定package的索引宵荒,然后從proxy下載數(shù)據(jù),最后從sum來獲取校驗信息:

image.png

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宗侦,主要做的事情:

  1. 新建v2的分支,git checkout -b v2忆蚀,比如https://github.com/winlinvip/mod_major_releases/tree/v2矾利。
  2. 修改go.mod的描述,路徑必須帶v2蜓谋,比如module github.com/winlinvip/mod_major_releases/v2梦皮。
  3. 提交后打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/quotegithub.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_vendorgithub.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有幾個步驟:

  1. 先轉(zhuǎn)成modules弃舒,參考前面的步驟癞埠,也可以新建一個modules例如go mod init xxx,然后把代碼寫好聋呢,就是一個標準的module苗踪,不過文件是存在$GOPATH/pkg的,參考github.com/winlinvip/mod_vgo_with_vendor@v1.0.0削锰。
  2. go mod vendor通铲,這一步做的事情,就是將modules中的文件都放到vendor中來器贩。當然由于go.mod也存在颅夺,當然也知道這些文件的版本信息央串,也不會造成什么問題,只是新建了一個vendor目錄而已碗啄。在別人看起來這就是這正常的modules,和vendor一點影響都沒有稳摄。參考github.com/winlinvip/mod_vgo_with_vendor@v1.0.1稚字。
  3. 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的又是怎樣?是拍的大腿阿迈,還是拍的腦袋元媚?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者苗沧。
  • 序言:七十年代末刊棕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子待逞,更是在濱河造成了極大的恐慌甥角,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件识樱,死亡現(xiàn)場離奇詭異嗤无,居然都是意外死亡,警方通過查閱死者的電腦和手機怜庸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門当犯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人割疾,你說我怎么就攤上這事灶壶。” “怎么了杈曲?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵驰凛,是天一觀的道長胸懈。 經(jīng)常有香客問我,道長恰响,這世上最難降的妖魔是什么趣钱? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮胚宦,結(jié)果婚禮上首有,老公的妹妹穿的比我還像新娘。我一直安慰自己枢劝,他們只是感情好井联,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著您旁,像睡著了一般烙常。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹤盒,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天蚕脏,我揣著相機與錄音,去河邊找鬼侦锯。 笑死驼鞭,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尺碰。 我是一名探鬼主播挣棕,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亲桥!你這毒婦竟也來了穴张?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤两曼,失蹤者是張志新(化名)和其女友劉穎皂甘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悼凑,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡偿枕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了户辫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渐夸。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖渔欢,靈堂內(nèi)的尸體忽然破棺而出墓塌,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布苫幢,位于F島的核電站访诱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏韩肝。R本人自食惡果不足惜触菜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哀峻。 院中可真熱鬧涡相,春花似錦、人聲如沸剩蟀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽育特。三九已至丙号,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間且预,已是汗流浹背槽袄。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工烙无, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锋谐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓截酷,卻偏偏與公主長得像涮拗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子迂苛,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容