「Golang进阶之路」基础篇
[toc]
写在前面
本文将重点介绍Go一些实用的一些基础知识,当然不是指Go语言基础知识,将介绍:Go包管理、工作模式等
包管理
先抛出一个问题:Go的依赖管理有哪些?
我们知道目前Go的主流的管理是Go Modules, 下图是主要的发展史:
graph LR
%% 定义节点样式
classDef default fill:#f5f5f5,stroke:#999,stroke-width:1px,radius:50% classDef red color:red classDef blue color:blue
%% 创建节点 - 水平排列
A[GOPATH < Go1.5]:::blue B[Go Vendor >= Go1.5]:::blue C[Go Modules >= Go1.11]:::red
%% 连接箭头(灰色)
A--->B B--->C
%% 连接线样式
linkStyle 0,1 stroke:#999,stroke-width:1px
我们直接看Go Modules
Go Modules
go1.11发布Go Modules,Go Modules的功能默认是关闭的,直到go1.13才开启。所以在go1.11和go1.12版本中我们需要通过go提供的变量GO111MODULE来开启Go Modules,这跟我们上面介绍的GO15VENDOREXPERIMENT环境变量类似。GO111MODULE也属于Go的段性变量,在某个功能发布之初的几个版本是需要手动开启的,等稳定下来被开发者接受后会在后续的版本中默认开启。
GO111MODULE 这个环境变量有三个值:
- on 开启Go modules
- off 关闭Go modules
- auto 当项目路径在 $GOPATH 目录外部时, 设置为 GO111MODULE = on;当项目路径位于 GOPATH 内部时,即使存在 go.mod文件, 也会设置 GO111MODULE = off
我们可以使用go env
查看:
> go env
GO111MODULE='on'
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/iceymoss/.cache/go-build'
GOENV='/home/iceymoss/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/iceymoss/go/pkg/mod'
GONOPROXY='gitlab.iceymoss.com'
GONOSUMDB='gitlab.iceymoss.com'
GOOS='linux'
GOPATH='/home/iceymoss/go'
GOPRIVATE='gitlab.iceymoss.com'
GOPROXY='https://goproxy.cn,direct'
GOROOT='/usr/lib/go-1.22'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/lib/go-1.22/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.22.2'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-L/home/'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build327796706=/tmp/go-build -gno-record-gcc-switches'
开启Go Modules:
export GO111MODULE=on (unix环境)
set GO111MODULE=on (windows环境)
或者使用go env 指令进行设置
go env -w GO111MODULE=on
需要注意的是,使用go env如果出现下面的错误,则说明在操作系统的环境变量中已经配置这个变量,go env指令无法覆盖操作系统级别的环境变量。
warning: go env -w GO111MODULE=... does not override conflicting OS environment variable
通常这些环境变量可能会在下面这些文件中设置:
/etc/profile ,/.profile,/etc/bashrc,/.bashrc,~/.bash_profile
需要注意的是,仅仅设置 GO111MODULE=on并不能代表项目就使用了Go modules的方式来管理包,还应该使用go mod init将项目初始化成一个Go modules工程。这个命令需要在项目的根目录下执行,执行成功后会在当前目录下生成go.mod的文件。
下面我们就来看看Go modules的具体用法:
环境配置
注意:我们我们的Go版本最低要求Go1.11, 我这里使用的是:
> go version
go version go1.22.2 linux/amd64
设置代理
我们可以需要设置一下加速代理,这里使用阿里云的代理:
export GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com/goproxy/,direct set GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com/goproxy/,direct```
或者
go env -w GOPROXY=goproxy.cn/,mirrors.aliyun...
#### 初始化工程
使用命令:
```go
mkdir go-mod-demo
go mod init go-mod-demo
按照一下步骤:
> mkdir go-mod-demo
~/icey/demo/go-project ......................................................................... 11:50:05> cd go-mod-demo
~/icey/demo/go-project/go-mod-demo ............................................................. 11:50:07> ls
~/icey/demo/go-project/go-mod-demo ............................................................. 11:50:08> go mod init go-mod-demo
go: creating new go.mod: module go-mod-demo
~/icey/demo/go-project/go-mod-demo ............................................................. 11:50:58> ls
go.mod
~/icey/demo/go-project/go-mod-demo ............................................................. 11:51:01> cat go.mod
module go-mod-demo
go 1.22.2
这样我们就创建了一个Go Modules依赖管理方式的项目
接着我们来导入一个依赖,执行以下:
go get github.com/gin-gonic/gin
然后我们在项目根目录下创建一个main.go入口程序:
package main
import (
"net/http"
"github.com/gin-gonic/gin" // 引入依赖
)
//handle方法
func Pong(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ "name": "ice_moss", "age": 18, "school": "家里蹲大学",
})}
func main() {
//初始化一个gin的server对象
//Default实例化对象具有日志和返回状态功能
r := gin.Default() //注册路由,并编写处理方法
r.GET("/ping", Pong) //监听端口:默认端口listen and serve on 0.0.0.0:8080
r.Run(":8083")}
然后执行:go run main.go
:
> go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.Pong (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8083
我们再来看一下mod文件:
module go-mod-demo
go 1.22.2
require (
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.10.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect)
并且他还生成了一个go.sum文件
可以看到他多了非常多的依赖了,当然如果我们刚clone了一下项目,我们可以在go.mod同级目录下执行:
go mod tidy
该命令会根据mod中的依赖情况去拉取对应的内容
这里关于go mod的几个指令我们需要掌握:
1. go mod init
用来初始化一个新的go modules工程, 并创建go.mod文件
2. go mod tidy
用来解决工程中包的依赖关系,如果是缺少的包会下载并将依赖包的信息维护到go.mod和go.sum文件中;如果是项目中没有用到的依赖包则会将依赖包的信息从go.mod和go.sum文件中移除。
3. go mod download
下载依赖包到本地缓存。如果go.mod和go.sum文件中已经包含依赖包的信息,而依赖包还没下载到本地就可以执行这个指令。
4. go mod vendor
这个指令是为了兼容Go Vendor模式,在Go Modules发布之前,Go Vendor使用比较普遍,所以go mod也支持将依赖包通过go mod vendor指令复制到项目的vendor目录中。
最常用的指令就是上面4个,还有一些其他指令我们可以通过go help mod来查看相关的帮助文档,主要包含go mod graph,go mod why和go mod verify等查看和校验依赖的指令。go mod edit 用来编辑 go.mod文件的指令。平时很少使用,了解即可。
接着我们需要了解Go modules中两个重要的文件:go.mod和go.sum
go.mod文件
上面我们执行go mod init go-mod-demo之后,在当前目录下生成了一个go.mod的文件。每个 Go 模块都由一个 go.mod 文件定义,这个文件描述模块的属性,包括它对其他模块和 Go 版本的依赖性
// module声明工程名
module go-mod-demo
// 记录当前环境的go版本
go 1.22.2
require (
// 直接依赖包(即代码中import的包)
gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.6.0 gorm.io/gorm v1.25.12 // 后面有indirect的表示项目的间接依赖(直接依赖包的依赖)
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.10.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect)
对于一个标准的go.mod文件而言,必须有module定义。如果项目只用了go内置的库,则不会有require的定义。go.mod文件还有include,exclude和replace的定义, 将在后面会详细介绍。
这个文件中我们需要注意的是:
1. module命名一般使用git仓库的路径,比如上面gin的包,它的仓库地址是:github.com/gin-gonic/gin ,则模块名就是 github.com/gin-gonic/gin,其中 github.com是git仓库的域名,gin-gonic/gin是代码在仓库中的路径。这样定义模块名才能让工程可以通过远程去下载。内部的私有仓库也是这种命名规则,使用git仓库的域名+工程路径的方式命名。
2. 第二行的go 1.22.2的版本号在官方文档中指的是当前工程所依赖的最低的go版本。但实际上它只是获取了你当前环境的go版本,只要代码兼容,上面的代码在低版本的go环境中也是可以运行的。所以这个版本定义其实不是必须的,去掉也不会报错。
3. require指的是导入的非go内置的库,或者非工程代码内部定义的包。后面有indirect注释的代表间接依赖包,没有的代表直接依赖包。需要注意的是只有当间接依赖包版本不包含go.mod文件时候才会记录到go.mod文件中。 require后面首先跟的是包的路径,然后是版本号。版本号可以是远程库中存在的版本也可以是go mod生成的伪版本号,上面的间接依赖就是一个伪版本号。
什么情况下会产生伪版本呢?
当源代码中 import
指向的模块不存在于 go.mod 文件中时,Go 命令行工具会自动搜索这个模块,并将最新版本(最后一个 tag 且非预发布的稳定版本)添加到 go.mod 文件中。如果这个包没有打tag,则使用伪版本,专门用于标记没有 tag 的提交。比如上面的v0.0.0-20151119151921-a422bbe96644,它其实是由3部分组成的:前面部分为语义化版本号,用于标记版本;中间部分为 UTC 的提交时间,用于比较两个伪版本以其确定先后顺序;后面部分是 commit 哈希的前缀,用于标记该版本位于哪个 commit。
go.sum文件
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
go.sum文件是我们触发项目依赖校验时生成,比如我们执行了go build,go run,go get,go mod tidy等指令。 它详细罗列了当前项目直接或间接依赖的所有模块的版本
go.sum文件每行由模块导入路径、模块的特定版本和预期哈希组成并通过空格分隔。
<module> <version>[/go.mod] <hash>
一般情况下,每个依赖包版本会包含两条记录,
第一条记录为该依赖包所有文件的哈希值,
第二条记录仅表示该依赖包版本中go.mod文件的哈希值,
如果该依赖包版本没有go.mod文件,一般只有第一条记录。比如上面的gin包,第一行的版本v1.10.1后面的hash值表示该依赖包版本整体的哈希值,
而v1.10.1/go.mod后面的hash值表示该依赖包版本中go.mod文件的hash值。
go.sum是怎么做包校验的呢?
当我们拿到某项目的源代码并尝试在本地构建,go命令会从本地缓存中查找所有go.mod中记录的依赖包,并计算本地依赖包的哈希值,然后与go.sum中的记录进行对比,即检测本地缓存中使用的依赖包版本是否满足项目go.sum文件的期望。如果校验失败,则说明本地缓存目录中依赖包版本的哈希值和项目中go.sum中记录的哈希值不一致,这时候构建就会被拒绝。 这样就可以确保相同的依赖包在任何环境下使用的依赖包的源码都是一样的。
依赖包版本中任何一个文件(包括go.mod)改动,都会改变其整体哈希值,此处再额外记录依赖包版本的go.mod文件主要用于计算依赖树时不必下载完整的依赖包版本,只根据go.mod即可计算依赖树。
每条记录中的哈希值前均有一个表示哈希算法的h1:,表示后面的哈希值是由算法SHA-256计算出来的。
模块版本的 SHA-256 哈希值用来验证当前缓存的模块,以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。在每次缺少模块时,如果缓存中不存在,则需要下载并计算其哈希添加到 go.sum 中;如果缓存中存在,则需要匹配 go.sum中已有的记录。
需要注意的是每次缺少模块时,即依赖包不在go.sum
文件中(即新增的依赖包),且是一个公网可下载的包,Go 命令会去 Go 校验数据库(如默认配置的 sum.golang.org 或中国大陆官方校验和数据库 sum.golang.google.cn )查询并获取该模块的校验和,如果下载的模块代码与校验和不匹配,则报告不匹配相关信息并退出,如果匹配,则将校验和写入 go.sum
文件中。
有三种情况不会对依赖包做哈希校验:
1. 配置了 GOPRIVATE时,GOPRIVATE匹配到的包将不会做checksum校验,GOPRIVATE变量主要是用来设置内部的包不使用GOPROXY配置的代理,因为内部一般都不会上传到公网。
2. 使用go mod vendor命令可以将依赖的包全部下载到当前工程根目录下的vendor目录下,可供离线编译,打包到vendor目录中的包也不会再做校验。
3. GOSUMDB设置为off时,所有的包都不会做校验
需要注意的是同一个模块版本的数据只缓存一份,所有其它模块共享使用。如果你希望清理所有已缓存的模块版本数据,可以执行 go clean -modcache
命令。
为什么go.sum文件中记录的依赖包版本数量会比go.mod文件中要多呢?
这是因为go.mod记录的是直接依赖的依赖包版本,只在依赖包版本不包含go.mod文件时候才会记录间接依赖包版本
而go.sum则是要记录构建用到的所有依赖包版本。
如果不希望进行依赖包检验,也可以关闭,将GOSUMDB设置为off即可:
将环境变量添加到/etc/profile
export GOSUMDB=off (unix环境)
#或者使用:bash
go env -w GOSUMDB=off
需要注意的是,GOPATH模式下和Go Modules模式下依赖包存储的路径是不一样的。
GOPATH
模式下,依赖包存储在$GOPATH/src
,该目录下只保存特定依赖包的一个版本,而且通过go get下载的包是完整的仓库。
而在GOMODULE
模式下,依赖包存储在$GOPATH/pkg/mod
,该目录中可以存储特定依赖包的多个版本,每个版本占用一个目录,目录中只包含依赖包文件,不包含.git
目录。
除了上面介绍的这些Go包管理工具外,我们在构建Go项目时也需要遵循一些原则和规范性的建议:
1. 一个目录名下只能有一个package,否则编译器会报错。
2. 建议一个package名的内容放在一个目录下面,便于项目管理。
3. 建议目录名和package名相同,便于项目管理。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: