十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專業(yè)推廣+無(wú)憂售后,網(wǎng)站問(wèn)題一站解決
前言

成都創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),濱江企業(yè)網(wǎng)站建設(shè),濱江品牌網(wǎng)站建設(shè),網(wǎng)站定制,濱江網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,濱江網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
在上文中,我們介紹了gopath的含義、功能、優(yōu)劣、以及如何通過(guò)GOPATH來(lái)組織項(xiàng)目
在本文中,我們將介紹go module的原理和用法以試圖能夠回答下面的幾個(gè)問(wèn)題
go module 是什么?
go module為什么需要?
go module的基本使用方法是什么?
go module如何管理版本與依賴?
go module如何解決依賴的沖突問(wèn)題?
go module 環(huán)境變量的配置與使用方式?
如何搭建私有 module鏡像?
在go module之前,有一些問(wèn)題長(zhǎng)期困擾go語(yǔ)言的開發(fā)人員
能否將go工程代碼脫離gopath之外
能否處理版本依賴問(wèn)題并且自動(dòng)選擇最兼容的依賴版本
能否使用go工具本地管理依賴項(xiàng),自定義依賴項(xiàng)
go1.11開始支持,go1.13全面支持的go modules 正是為了解決上面的問(wèn)題誕生的,下面我們?cè)敿?xì)介紹go module企圖解決的問(wèn)題
在介紹gopath時(shí),我們介紹了如果導(dǎo)入為
import "github.com/gobuffalo/buffalo"
實(shí)際引用的是$GOPATH/src/github.com/gobuffalo/buffalo 文件中的代碼。
也就是說(shuō),在gopath中 ,導(dǎo)入路徑與項(xiàng)目在文件系統(tǒng)中的目錄結(jié)構(gòu)和名稱必須是匹配的。
那么能否import 路徑為github.com/gobuffalo/buffalo,但是項(xiàng)目實(shí)際的路徑卻是在另一個(gè)任意的文件目錄中?(例如/users/gobuffalo/buffalo).答案是肯定的,go module 通過(guò)在一個(gè)特殊的叫做go.mod的文件中指定模塊名來(lái)解決這一問(wèn)題。
## go.mod
01 module github.com/gobuffalo/buffalo
02
...
06
在go.mod文件的第一行指定了模塊名,模塊名表示開發(fā)人員可以用此來(lái)引用當(dāng)前代碼倉(cāng)庫(kù)中任何package的路徑名,以此來(lái)替代$gopath的路徑。從而,代碼倉(cāng)庫(kù)在任何位置都已經(jīng)沒有關(guān)系,因?yàn)镚o工具可以使用模塊文件的位置和模塊名來(lái)解析代碼倉(cāng)庫(kù)中的任何內(nèi)部import。
對(duì)于任何版本控制(VCS)工具,我們都能在任何代碼提交點(diǎn)打上"tag"標(biāo)記,如下所示:
使用VCS工具,開發(fā)人員可以通過(guò)引用特定標(biāo)簽將軟件包的任何特定版本克隆到本地。
當(dāng)我們引用一個(gè)第三方包時(shí),可能并不總是希望應(yīng)用項(xiàng)目最新的代碼,而是某一個(gè)特定與當(dāng)前項(xiàng)目兼容的代碼。對(duì)于某一個(gè)項(xiàng)目來(lái)說(shuō),可能并沒有意識(shí)到有人在使用他們的代碼,或者某種原因進(jìn)行了巨大的不兼容更新。
我們希望能夠指明需要使用的第三方包的版本,并且go工具能夠方便下載、管理
更棘手的是,一個(gè)第三方包A可能引用了其他的第三方包B,因此還必須把第三方包A的全部依賴下載
如何查找并把所有的依賴包下載下來(lái)?
某一個(gè)包下載失敗應(yīng)該怎么辦?
所有項(xiàng)目之間如何進(jìn)行依賴的傳導(dǎo)?
如何選擇一個(gè)最兼容的包?
如何解決包的沖突?
如果希望在項(xiàng)目中同時(shí)引用第三方包的二個(gè)不同版本,需要如何處理?
因此,只通過(guò)gopath維護(hù)單一的master包的方式是遠(yuǎn)遠(yuǎn)不夠的,因?yàn)橐蕾嚢淖钚麓a不一定與項(xiàng)目兼容。盡管go社區(qū)已經(jīng)針對(duì)以上問(wèn)題提供了一些解決方案(例如dep,godep,glide等)但是go官方的go moudle提供了一種集成解決方案,通過(guò)在文件中維護(hù)直接和間接依賴項(xiàng)的版本列表來(lái)解決這一問(wèn)題。通過(guò)將一個(gè)特定版本的依賴項(xiàng)看做是捆綁的不可變的依賴項(xiàng),就叫做一個(gè)模塊(moudle)
為了加快構(gòu)建程序的速度并快速切換、獲取項(xiàng)目中依賴項(xiàng)的更新,Go維護(hù)了下載到本地計(jì)算機(jī)上的所有模塊的緩存,緩存目前默認(rèn)位于$GOPATH/pkg目錄中。有g(shù)o的提議希望能夠自定義緩存的位置。
所在位置看上去如下所示:
go/
├── bin
├── pkg
├── darwin_amd64
└── mod
└── src
在mod目錄下,我們能夠看到模塊名路徑中的第一部分用作了模塊緩存中的頂級(jí)文件夾
~/go/pkg/mod ? ls -l jackson@192
drwxr-xr-x 6 jackson staff 192 1 15 20:50 cache
drwxr-xr-x 7 jackson staff 224 2 20 17:50 cloud.google.com
drwxr-xr-x 3 jackson staff 96 2 18 12:03 git.apache.org
drwxr-xr-x 327 jackson staff 10464 2 28 00:02 github.com
drwxr-xr-x 8 jackson staff 256 2 20 17:27 gitlab.followme.com
drwxr-xr-x 6 jackson staff 192 2 19 22:05 go.etcd.io
...
當(dāng)我們打開一個(gè)實(shí)際的模塊,例如github.com/nats-io,我們會(huì)看到與nats庫(kù)有關(guān)許多模塊
~/go/pkg/mod ? ls -l github.com/nats-io jackson@192
total 0
dr-x------ 24 jackson staff 768 1 17 10:27 gnatsd@v1.4.1
dr-x------ 15 jackson staff 480 2 17 22:22 go-nats-streaming@v0.4.0
dr-x------ 26 jackson staff 832 2 19 22:05 go-nats@v1.7.0
dr-x------ 26 jackson staff 832 1 17 10:27 go-nats@v1.7.2
...
為了擁有一個(gè)干凈的工作環(huán)境,我們可以用如下代碼清空緩存區(qū)。但是請(qǐng)注意,在正常的工作流程中,是不需要執(zhí)行如下代碼的。
$ go clean -modcache
我們從GOPATH外開始一個(gè)新的項(xiàng)目講解,新建一個(gè)新建夾以及一個(gè)main文件
$ cd $HOME
$ mkdir mathlib
$ cd mathlib jackson@192
$ touch main.go
接著在當(dāng)前目錄中,執(zhí)行如下指令初始化moudle。
~/mathlib ? go mod init github.com/dreamerjackson/mathlib
go mod init指令的功能很簡(jiǎn)單,自動(dòng)生成一個(gè)go.mod文件 后面緊跟的路徑即是自定義的模塊名。習(xí)慣上以托管代碼倉(cāng)庫(kù)的URL為模塊名(代碼將會(huì)放置在https://github.com/dreamerjackson/mathlib下)
go.mod文件 位于項(xiàng)目的根目錄下,內(nèi)容如下所示,第一行即為模塊名。
module github.com/ardanlabs/service
#### 引入第三方模塊
go 1.13
接下來(lái)我們將書寫初始化的代碼片段
package main
import "github.com/dreamerjackson/mydiv"
func main(){
}
我們?cè)诖a片段中導(dǎo)入了為了講解go moudle而特地的引入的packagegithub.com/dreamerjackson/mydiv,其進(jìn)行簡(jiǎn)單的除法操作,同時(shí)又引入了另一個(gè)包github.com/pkg/errors。其代碼如下圖所示:
如下圖所示,在goland中我們可以看到導(dǎo)入的package 是紅色的,因?yàn)榇藭r(shí)在go module的緩存并不能找到此package。
為了能夠?qū)⒋藀ackage下載到本地,我們可以使用go mod tidy指令
$ go mod tidy
go: finding github.com/dreamerjackson/mydiv latest
go: downloading github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161
go: extracting github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161
同時(shí)我們?cè)?code>go.mod中能夠看到新增加了一行用于表示我們引用的依賴關(guān)系
module github.com/dreamerjackson/mathlib
go 1.13
require github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161
注意在這里間接的依賴(即github.com/dreamerjackson/mydiv 依賴的github.com/pkg/errors)并沒有也沒有必要在go.mod文件展示出來(lái),而是出現(xiàn)在了一個(gè)自動(dòng)生成的新的文件go.sum中.
## go.sum
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161 h2:QR1fJ05yjzJ0qv1gcUS+gAe5Q3UU5Y0le6TIb2pcJpQ=
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161/go.mod h2:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/pkg/errors v0.9.1 h2:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h2:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
接著就可以愉快的調(diào)用我們的代碼了
package main
import (
"fmt"
"github.com/dreamerjackson/mydiv"
)
func main(){
res,_ :=mydiv.Div(4,2)
fmt.Println(res)
}
運(yùn)行go run 命令后,即會(huì)為我們輸出除法結(jié)果2
假設(shè)我們依賴的第三方包出現(xiàn)了更新怎么辦?如果將依賴代碼更新到最新的版本呢?
有多種方式可以實(shí)現(xiàn)依賴模塊的更新,在go.mod文件中修改版本號(hào)為:
require github.com/dreamerjackson/mydiv latest
或者
require github.com/dreamerjackson/mydiv master
獲取復(fù)制commitId 到最后
require github.com/dreamerjackson/mydiv c9a7ffa8112626ba6c85619d7fd98122dd49f850
還有一種辦法是在終端當(dāng)前項(xiàng)目中,運(yùn)行go get
go get github.com/dreamerjackson/mydiv
上述幾種方式在保存文件后,再次運(yùn)行go mod tidy即會(huì)進(jìn)行更新
此時(shí)如果我們?cè)俅未蜷_go.sum文件會(huì)發(fā)現(xiàn),go.sum中不僅僅存儲(chǔ)了直接和間接的依賴,還會(huì)存儲(chǔ)過(guò)去的版本信息。
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161 h2:QR1fJ05yjzJ0qv1gcUS+gAe5Q3UU5Y0le6TIb2pcJpQ=
github.com/dreamerjackson/mydiv v0.0.0-20200305082807-fdd187670161/go.mod h2:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/dreamerjackson/mydiv v0.0.0-20200305090126-c9a7ffa81126/go.mod h2:h70Xf3RkhKSNbUF8W3htLNJskYJSITf6AdEGK22QksQ=
github.com/pkg/errors v0.9.1 h2:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h2:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
當(dāng)我們不想在使用此第三方包時(shí),可以直接在代碼中刪除無(wú)用的代碼,接著執(zhí)行
$ go mod tidy
會(huì)發(fā)現(xiàn)go.mod 與go.sum 一切又都空空如也~
每個(gè)依賴管理解決方案都必須解決選擇依賴版本的問(wèn)題,當(dāng)今存在的許多版本選擇算法都試圖識(shí)別任何依賴的“最新最大”版本。如果您認(rèn)為語(yǔ)義版本控制被正確應(yīng)用并且將遵守約定,那么這是有道理的。在這些情況下,依賴項(xiàng)的“最新最大”版本應(yīng)該是最穩(wěn)定和安全的版本,并且應(yīng)與較早版本具有向后兼容性。
Go決定采用其他方法,Russ Cox花費(fèi)了大量時(shí)間和精力撰寫和談?wù)?Go團(tuán)隊(duì)的版本選擇方法,即最小版本選擇(Minimal Version Selection,MVS)。從本質(zhì)上講,Go團(tuán)隊(duì)相信MVS可以為Go程序提供最佳的機(jī)會(huì),以實(shí)現(xiàn)兼容性和可重復(fù)性。我建議閱讀這篇文章,以了解Go團(tuán)隊(duì)為什么相信這一點(diǎn)。
go最小版本選擇指的是選擇項(xiàng)目中最合適的最小版本。并不是說(shuō)MVS不能選擇最新的版本,而是如果項(xiàng)目中任何依賴不需要最新的版本,則不需要它。
舉一個(gè)簡(jiǎn)單的例子,假設(shè)現(xiàn)在項(xiàng)目github.com/dreamerjackson/mydiv的最新版本為v1.0.2,可通過(guò)下面指令查看所有
> go list -m -versions github.com/dreamerjackson/mydiv
github.com/dreamerjackson/mydiv v1.0.0 v1.0.1 v1.0.2 v1.0.3
假設(shè)現(xiàn)在有兩個(gè)模塊A、B,都依賴模塊D。其中
A -> D v1.0.1,
B -> D v1.0.2
如果我們的當(dāng)前項(xiàng)目只依賴A,這個(gè)時(shí)候go module會(huì)如何選擇呢?像dep這樣的依賴工具將選擇v1.0.3,即最新的語(yǔ)義版本控制。但是在go module中,最小版本選擇原理將遵循A項(xiàng)目聲明的版本,即v1.0.1
如果隨后當(dāng)前項(xiàng)目又引入了模塊B的新代碼怎么辦?將模塊B導(dǎo)入項(xiàng)目后,Go會(huì)將項(xiàng)目的模塊D版本從v1.0.1升到v1.0.2。為模塊D的所有依賴項(xiàng)(模塊A和B)選擇模塊D的“最小”版本,該版本當(dāng)前處于所需版本集(v1.0.1和v.1.0.2)中
最后,如果刪除剛剛為模塊B添加的代碼,會(huì)發(fā)生什么?Go會(huì)將項(xiàng)目鎖定到模塊D的版本v1.0.2中。降級(jí)到版本v1.0.1將是一個(gè)更大的更改,而Go知道版本v1.0.1可以正常運(yùn)行并且穩(wěn)定,因此版本v1.0.2仍然是“最新版本”。
為了驗(yàn)證最小版本選擇原理,作者嘔心瀝血設(shè)計(jì)了一個(gè)簡(jiǎn)單的示例。
以項(xiàng)目github.com/dreamerjackson/mydiv為例,讀者可以將其看做上節(jié)中的模塊D,其v1.0.1與v1.0.2版本的代碼如下,只是簡(jiǎn)單的改變了錯(cuò)誤返回的字符串。
## v1.0.1
package mydiv
import "github.com/pkg/errors"
func Div(a int,b int) (int,error){
if b==0{
return 0,errors.Errorf("new error b can't = 0")
}
return a/b,nil
}
## v1.0.2
package mydiv
import "github.com/pkg/errors"
func Div(a int,b int) (int,error){
if b==0{
return 0,errors.Errorf("new error b can't = 0")
}
return a/b,nil
}
接著模塊B即github.com/dreamerjackson/minidiv 引用了模塊D即github.com/dreamerjackson/mydiv v1.0.1版本
## 模塊B
package div
import (
"github.com/dreamerjackson/mydiv"
)
func Div(a int,b int) (int,error){
return mydiv.Div(a,b)
}
最后當(dāng)前的項(xiàng)目,我們將其稱為模塊Now直接依賴了模塊D v1.0.2,同時(shí)依賴了模塊B
當(dāng)前代碼如下:
package main
import (
"fmt"
div "github.com/dreamerjackson/minidiv"
"github.com/dreamerjackson/mydiv"
)
func main(){
_,err1:= mydiv.Div(4,0)
_,err2 := div.Div(4,0)
fmt.Println(err1,err2)
}
當(dāng)前的依賴關(guān)系如下:
當(dāng)前模塊 --> 模塊D v1.0.2
當(dāng)前模塊 --> 模塊B --> 模塊D v1.0.1
因此我們將驗(yàn)證,是否和我們所料,當(dāng)前項(xiàng)目選擇了模塊D v1.0.2 呢?
驗(yàn)證方式有兩種:第一種為直接運(yùn)行,查看項(xiàng)目采用了哪一個(gè)版本的代碼
$ go run main.go
v1.0.2 b can't = 0 v1.0.2 b can't = 0
如上所示,輸出的結(jié)果全部是我們?cè)谀KD v1.0.2中定義的代碼!
第二種方式是使用go list指令
~/mathlib ? go list -m all | grep mydiv
github.com/dreamerjackson/mydiv v1.0.2
我們還可以通過(guò)使用go mod mhy z指令,查看在哪里引用了包github.com/dreamerjackson/mydiv
~/mathlib ? go mod why github.com/dreamerjackson/mydiv
# github.com/dreamerjackson/mydiv
github.com/dreamerjackson/mathlib
github.com/dreamerjackson/mydiv
我們可以使用go list -m -u all 指令查看直接和間接模塊的當(dāng)前和最新版本
~/mathlib ? go list -m -u all | column -t jackson@192
go: finding github.com/dreamerjackson/minidiv latest
github.com/dreamerjackson/mathlib
github.com/dreamerjackson/minidiv v0.0.0-20200305104752-fcd15cf402bb
github.com/dreamerjackson/mydiv v1.0.2 [v1.0.3]
github.com/pkg/errors v0.9.1
如上所示,我們可以看到github.com/dreamerjackson/mydiv的當(dāng)前版本為v1.0.2,但是最新的版本為v1.0.3
獲取直接和間接模塊可以使用go get指令。其中有不少的參數(shù)。
下面命令以最小版本原則更新所有的直接和間接模塊
go get -t -d -v ./...
-t 考慮構(gòu)建測(cè)試所需的模塊
-d 下載每個(gè)模塊的源代碼
-v 提供詳細(xì)輸出
./… 在整個(gè)源代碼樹中執(zhí)行這些操作,并且僅更新所需的依賴項(xiàng)
注意,除非你了解項(xiàng)目的所有細(xì)節(jié),否則慎用全部的最大最新版本的更新
如果go get中使用-u參數(shù)會(huì)用最大最新版本原則更新所有的直接和間接模塊
~/mathlib ? go get -u -t -d -v ./... jackson@192
go: finding github.com/dreamerjackson/minidiv latest
go: downloading github.com/dreamerjackson/mydiv v1.0.3
go: extracting github.com/dreamerjackson/mydiv v1.0.3
接著我們可以再次查看當(dāng)前引用的版本,我們會(huì)發(fā)現(xiàn)模塊github.com/dreamerjackson/mydiv已經(jīng)強(qiáng)制更新到了最新的v1.0.3
~/mathlib ? go list -m all | grep mydiv jackson@192
github.com/dreamerjackson/mydiv v1.0.3
如果您不滿意所選的模塊和版本,則始終可以通過(guò)刪除go.mod go.sum模塊文件并再次運(yùn)行g(shù)o mod tidy來(lái)重置。當(dāng)項(xiàng)目還不太成熟時(shí)這是一種選擇。
$ rm go.*
$ go mod init
$ go mod tidy
Go模塊引入了一種新的導(dǎo)入路徑語(yǔ)法,即語(yǔ)義導(dǎo)入版本控制。每個(gè)語(yǔ)義版本均采用vMAJOR.MINOR.PATCH的形式。
MAJOR 主版本號(hào),如果有大的版本更新,導(dǎo)致 API 和之前版本不兼容。我們遇到的就是這個(gè)問(wèn)題。
MINOR 次版本號(hào),當(dāng)你做了向下兼容的新 feature。
PATCH 修訂版本號(hào),當(dāng)你做了向下兼容的修復(fù) bug fix。
v 所有版本號(hào)都是 v 開頭。
如果兩個(gè)版本具有相同的主編號(hào),則預(yù)期更高版本(如果您愿意,更大版本)將與較早版本(較小版本)向后兼容。但是,如果兩個(gè)版本的主要編號(hào)不同,則它們之間沒有預(yù)期的兼容性關(guān)系。
因此我們?cè)谏厦娴膶?shí)例中可以看到,go預(yù)料到v1.0.3與v1.0.1是兼容的,因?yàn)樗麄冇邢嗤闹靼姹咎?hào)1。但是一般我們將版本升級(jí)到了v2.0.0,即被認(rèn)為是出現(xiàn)了重大的更新。
如上圖實(shí)例顯示了go對(duì)于版本更新的處理。my/thing/v2標(biāo)識(shí)特定模塊的語(yǔ)義主版本2。版本1是my/thing,模塊路徑中沒有明確的版本。但是,當(dāng)您引入主要版本2或更大版本時(shí),必須在模塊名稱后添加版本,以區(qū)別于版本1和其他主要版本,因此版本2為my/thing/v2,版本3為my/thing/v3,依此類推。
假設(shè)模塊A引入了模塊B和模塊C,模塊B引入了模塊Dv1.0.0,模塊C引入了模塊Dv2.0.0。則看起來(lái)就像是
A --> 模塊B --> 模塊D v1.0.0
A --> 模塊C --> 模塊D v2.0.0
由于v1 和v2 模塊的路徑不相同,因此他們之間會(huì)是互不干擾的兩個(gè)模塊。
下面我們用實(shí)例來(lái)驗(yàn)證
首先我們給mydiv打一個(gè)v2.0.0的tag,其代碼如下,簡(jiǎn)單修改了錯(cuò)誤文字v2.0.0 b can't = 0
package mydiv
import "github.com/pkg/errors"
func Div(a int,b int) (int,error){
if b==0{
return 0,errors.Errorf("v2.0.0 b can't = 0")
}
return a/b,nil
}
同時(shí)需要修改v2模塊路徑名為:
module github.com/dreamerjackson/mydiv/v2
接著在mathlib中,代碼如下:
package main
import (
"fmt"
div "github.com/dreamerjackson/minidiv"
mydiv "github.com/dreamerjackson/mydiv/v2"
)
func main(){
_,err1:= mydiv.Div(4,0)
_,err2 := div.Div(4,0)
fmt.Println(err1,err2)
}
現(xiàn)在的依賴路徑可以表示為為:
mathlib --> 直接引用mydiv v2
mathlib --> 直接引用minidiv --> 間接引用mydiv v1
當(dāng)我們運(yùn)行代碼之后,會(huì)發(fā)現(xiàn)兩段代碼是共存的
v2.0.0 b can't = 0 ::v1.0.1 b can't = 0
接著執(zhí)行go list,模塊共存,驗(yàn)證成功~
~/mathlib(master*) ? go list -m all | grep mydiv
github.com/dreamerjackson/mydiv v1.0.1
github.com/dreamerjackson/mydiv/v2 v2.0.1
模塊鏡像于2019年八月推出,是go官方1.13版本的默認(rèn)系統(tǒng)。模塊鏡像是一個(gè)代理服務(wù)器,以幫助加快構(gòu)建本地應(yīng)用程序所需的模塊的獲取。代理服務(wù)器實(shí)現(xiàn)了基于REST的API,并根據(jù)Go工具的需求進(jìn)行了設(shè)計(jì)。
模塊鏡像將會(huì)緩存已請(qǐng)求的模塊及其特定版本,從而可以更快地檢索將來(lái)的請(qǐng)求。一旦代碼被獲取并緩存在模塊鏡像中,就可以將其快速提供給世界各地的用戶。
checksum數(shù)據(jù)庫(kù)也于2019八月推出,是可以用來(lái)防止模塊完整性、有效性的手段。它驗(yàn)證特定版本的任何給定模塊代碼的正確性,而不管何人何時(shí)何地以及是如何獲取的。Google擁有唯一的校驗(yàn)和數(shù)據(jù)庫(kù),但是可以通過(guò)私有模塊鏡像對(duì)其進(jìn)行緩存。
有幾個(gè)環(huán)境變量可以控制與模塊鏡像和checksum數(shù)據(jù)庫(kù)有關(guān)的行為
GOPROXY:一組指向模塊鏡像的URL,用于獲取模塊。如果您希望Go工具僅直接從VCS地址獲取模塊,則可以將其設(shè)置為direct。如果將此設(shè)置為off,則將不會(huì)下載模塊
GOSUMDB:用于驗(yàn)證給定模塊/版本的代碼的checksum數(shù)據(jù)庫(kù)地址。此地址用于形成一個(gè)適當(dāng)?shù)腢RL,該URL告訴Go工具在哪里執(zhí)行這些checksum校驗(yàn)和查找。該URL可以指向Google擁有的checksum數(shù)據(jù)庫(kù),也可以指向支持對(duì)checksum數(shù)據(jù)庫(kù)進(jìn)行緩存或代理的本地模塊鏡像。如果您不希望Go工具驗(yàn)證添加到go.sum文件中的給定模塊/版本的哈希碼,也可以將其設(shè)置為off,僅在將任何新module添加到go.sum文件之時(shí),才查詢checksum數(shù)據(jù)庫(kù)
GONOPROXY:一組基于URL的模塊路徑,這些模塊不會(huì)使用模塊鏡像來(lái)獲取,而應(yīng)直接在VCS地址上獲取。
GONOSUMDB:一組基于URL的模塊路徑,這些模塊的哈希碼不會(huì)在checksum數(shù)據(jù)庫(kù)中查找。
GOPRIVATE:一個(gè)方便變量,用于將GONOPROXY和GONOSUMDB設(shè)置為相同的默認(rèn)值
我們可以通過(guò)go env 來(lái)查看到這些默認(rèn)值
$ go env
GONOPROXY=""
GONOSUMDB=""
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOSUMDB="sum.golang.org"
這些默認(rèn)值告訴Go工具使用Google模塊鏡像和Google checksum數(shù)據(jù)庫(kù)。如果這些Google服務(wù)可以訪問(wèn)您所需的所有代碼,則建議使用此配置。如果Google模塊鏡像恰好以410(消失)或404(未找到)響應(yīng),則使用direct(這是GOPROXY配置的一部分)將允許Go工具更改路線并直接獲取模塊/版本VCS位置。
例如,如果我們需要訪問(wèn)所有代理服務(wù)器,例如需要權(quán)限的位于gitlab 等地的代碼,我們可以使用export GOPRIVATE=gitlab.XXX.com,gitlab.XXX-XX.com,XXX.io 多個(gè)域名用逗號(hào)分隔。
Athens是一個(gè)私有模塊鏡像,可以用于搭建私有模塊鏡像。使用私有模塊鏡像的一個(gè)原因是允許緩存公共模塊鏡像無(wú)法訪問(wèn)的私有模塊。Athens項(xiàng)目提供了一個(gè)在Docker Hub上發(fā)布的Docker容器,因此不需要特殊的安裝。
docker run -p '3000:3000' gomods/athens:latest
接下來(lái),啟動(dòng)一個(gè)新的終端會(huì)話以運(yùn)行Athens,為大家演示其用法。啟動(dòng)Athens服務(wù)并通過(guò)額外的參數(shù)調(diào)試日志(請(qǐng)確保系統(tǒng)已經(jīng)安裝并啟動(dòng)了docker)并有科學(xué)*上網(wǎng)的環(huán)境
$ docker run -p '3000:3000' -e ATHENS_LOG_LEVEL=debug -e GO_ENV=development gomods/athens:latest
INFO[7:11AM]: Exporter not specified. Traces won't be exported
2020-03-06 07:11:30.671249 I | Starting application at port :3000
接著我們修改GOPROXY參數(shù),指向本地3000端口,初始化我們之前的項(xiàng)目,再次執(zhí)行go mod tidy
$ export GOPROXY="http://localhost:3000,direct"
$ rm go.*
$ go mod init github.com/dreamerjackson/mathlib
$ go mod tidy
在Athens日志中即可查看對(duì)應(yīng)信息
INFO[7:39AM]: incoming request http-method=GET http-path=/github.com/dreamerjackson/mydiv/@v/list http-status=200
INFO[7:39AM]: incoming request http-method=GET http-path=/github.com/dreamerjackson/minidiv/@v/list http-status=200
INFO[7:39AM]: incoming request http-method=GET http-path=/github.com/dreamerjackson/minidiv/@latest http-status=200
詳細(xì)信息,查看參考資料中Athens的官方網(wǎng)站
提供脫離gopath管理go代碼的優(yōu)勢(shì)
提供了代碼捆綁、版本控制、依賴管理的功能
供全球開發(fā)人員使用、構(gòu)建,下載,授權(quán)、驗(yàn)證,獲取,緩存和重用模塊(可以通過(guò)搭建自己的代理服務(wù)器來(lái)實(shí)現(xiàn)這些功能)
可以驗(yàn)證模塊(對(duì)于任何給定的版本)始終包含完全相同的代碼,而不管它被構(gòu)建了多少次,從何處獲取以及由誰(shuí)獲取