大家好,我是一只普通的煎鱼,周四晚上很有幸邀请到 goproxy.cn 的作者 @盛傲飞(@aofei) 到 Go 夜读给我们进行第 61 期 《Go Modules、Go Module Proxy 和 goproxy.cn》的技术分享。

本次 @盛傲飞 的夜读分享,是对 Go Modules 的一次很好的解读,比较贴近工程实践,我必然希望把这块的知识更多的分享给大家,因此有了今天本篇文章,同时大家也可以多关注 Go 夜读,每周会通过 zoom 在线直播的方式分享 Go 相关的技术话题,希望对大家有所帮助。

注:本文比较长,建议预留好时间。

前言

Go 1.11 推出的模块(Modules)为 Go 语言开发者打开了一扇新的大门,理想化的依赖管理解决方案使得 Go 语言朝着计算机编程史上的第一个依赖乌托邦(Deptopia)迈进。随着模块一起推出的还有模块代理协议(Module proxy protocol),通过这个协议我们可以实现 Go 模块代理(Go module proxy),也就是依赖镜像。

proxy.golang.org
goproxy.cn
goproxy.cn

目录

  • Go Modules 简介
  • 快速迁移项目至 Go Modules
  • 使用 Go Modules 时常遇见的坑
    • 坑 1: 判断项目是否启用了 Go Modules
    • 坑 2: 管理 Go 的环境变量
    • 坑 3: 迁移至 Go Modules
    • 坑 4: 拉取私有模块
    • 坑 5: 更新现有的模块
    • 坑 6: 主版本号
  • Go Module Proxy 简介
  • Goproxy 中国(goproxy.cn)

Go Modules 简介

Go modules (前身 vgo) 是 Go team (Russ Cox) 强推的一个理想化类语言级依赖管理解决方案,它是和 Go1.11 一同发布的,在 Go1.13 做了大量的优化和调整,目前已经变得比较不错,如果你想用 Go modules,但还停留在 1.11/1.12 版本的话,强烈建议升级。

三个关键字

强推

首先这并不是乱说的,因为 Go modules 确实是被强推出来的,如下:

  • 之前:大家都知道在 Go modules 之前还有一个叫 dep 的项目,它也是 Go 的一个官方的实验性项目,目的同样也是为了解决 Go 在依赖管理方面的短板。在 Russ Cox 还没有提出 Go modules 的时候,社区里面几乎所有的人都认为 dep 肯定就是未来 Go 官方的依赖管理解决方案了。
  • 后来:谁都没想到半路杀出个程咬金,Russ Cox 义无反顾地推出了 Go modules,这瞬间导致一石激起千层浪,让社区炸了锅。大家一致认为 Go team 实在是太霸道、太独裁了,连个招呼都不打一声。我记得当时有很多人在网上跟 Russ Cox 口水战,各种依赖管理解决方案的专家都冒出来发表意见,讨论范围甚至一度超出了 Go 语言的圈子触及到了其他语言的领域。

理想化

从他强制要求使用语义化版本控制这一点来说就很理想化了,如下:

  • Go modules 狠到如果你的 Tag 没有遵循语义化版本控制那么它就会忽略你的 Tag,然后根据你的 Commit 时间和哈希值再为你生成一个假定的符合语义化版本控制的版本号。
  • Go modules 还默认认为,只要你的主版本号不变,那这个模块版本肯定就不包含 Breaking changes,因为语义化版本控制就是这么规定的啊。是不是很理想化。

类语言级:

这个关键词其实是我自己瞎编的,我只是单纯地个人认为 Go modules 在设计上就像个语言级特性一样,比如如果你的主版本号发生变更,那么你的代码里的 import path 也得跟着变,它认为主版本号不同的两个模块版本是完全不同的两个模块。此外,Go moduels 在设计上跟 go 整个命令都结合得相当紧密,无处不在,所以我才说它是一个有点儿像语言级的特性,虽然不是太严谨。

推 Go Modules 的人是谁

那么在上文中提到的 Russ Cox 何许人也呢,很多人应该都知道他,他是 Go 这个项目目前代码提交量最多的人,甚至是第二名的两倍还要多。

Russ Cox 还是 Go 现在的掌舵人(大家应该知道之前 Go 的掌舵人是 Rob Pike,但是听说由于他本人不喜欢特朗普执政所以离开了美国,然后他岁数也挺大的了,所以也正在逐渐交权,不过现在还是在参与 Go 的发展)。

Russ Cox 的个人能力相当强,看问题的角度也很独特,这也就是为什么他刚一提出 Go modules 的概念就能引起那么大范围的响应。虽然是被强推的,但事实也证明当下的 Go modules 表现得确实很优秀,所以这表明一定程度上的 “独裁” 还是可以接受的,至少可以保证一个项目能更加专一地朝着一个方向发展。

总之,无论如何 Go modules 现在都成了 Go 语言的一个密不可分的组件。

GOPATH

Go modules 出现的目的之一就是为了解决 GOPATH 的问题,也就相当于是抛弃 GOPATH 了。

Opt-in

Go modules 还处于 Opt-in 阶段,就是你想用就用,不用就不用,不强制你。但是未来很有可能 Go2 就强制使用了。

"module" != "package"

有一点需要纠正,就是“模块”和“包”,也就是 “module” 和 “package” 这两个术语并不是等价的,是 “集合” 跟 “元素” 的关系,“模块” 包含 “包”,“包” 属于 “模块”,一个 “模块” 是零个、一个或多个 “包” 的集合。

Go Modules相关属性

go.mod

go.mod 是启用了 Go moduels 的项目所必须的最重要的文件,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头,目前有以下 5 个动词:

  • module:用于定义当前项目的模块路径。
  • go:用于设置预期的 Go 版本。
  • require:用于设置一个特定的模块版本。
  • exclude:用于从使用中排除一个特定的模块版本。
  • replace:用于将一个模块版本替换为另外一个模块版本。
go $version

go.sum

go.sum 是类似于比如 dep 的 Gopkg.lock 的一类文件,它详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。

我们可以看到一个模块路径可能有如下两种:

前者为 Go modules 打包整个模块包文件 zip 后再进行 hash 值,而后者为针对 go.mod 的 hash 值。他们两者,要不就是同时存在,要不就是只存在 go.mod hash。

那什么情况下会不存在 zip hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 zip hash,就会出现不存在 zip hash,只存在 go.mod hash 的情况。

GO111MODULE

这个环境变量主要是 Go modules 的开关,主要有以下参数:

  • auto:只在项目包含了 go.mod 文件时启用 Go modules,在 Go 1.13 中仍然是默认值,详见 :golang.org/issue/31857。
  • on:无脑启用 Go modules,推荐设置,未来版本中的默认值,让 GOPATH 从此成为历史。
  • off:禁用 Go modules。

GOPROXY

这个环境变量主要是用于设置 Go 模块代理,主要如下:

proxy.golang.orggoproxy.cn

刚刚在上面,我们可以发现值列表中有 “direct” ,它又有什么作用呢?

其实值列表中的 “direct” 为特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取 (比如 GitHub 等),当值列表中上一个 Go module proxy 返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源遇见 EOF 时终止并抛出类似 “invalid version: unknown revision...” 的错误

GOSUMDB

它的值是一个 Go checksum database,用于使 Go 在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经篡改,也可以是“off”即禁止 Go 在后续操作中校验模块版本

SUMDB_NAME+PUBLIC_KEYSUMDB_NAME+PUBLIC_KEY SUMDB_URLsum.golang.orggoproxy.cngoproxy.cnsum.golang.org

Go Checksum Database

Go checksum database 主要用于保护 Go 不会从任何源头拉到被篡改过的非法 Go 模块版本,其作用(左)和工作机制(右)如下图:

go helpmodule-auth

GONOPROXY/GONOSUMDB/GOPRIVATE

这三个环境变量都是用在当前项目依赖了私有模块,也就是依赖了由 GOPROXY 指定的 Go module proxy 或由 GOSUMDB 指定 Go checksum database 无法访问到的模块时的场景,他们具有如下特性:

  • 它们三个的值都是一个以英文逗号 “,” 分割的模块路径前缀,匹配规则同 path.Match。
  • 其中 GOPRIVATE 较为特殊,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是只是用 GOPRIVATE。
GOPRIVATE=*.corp.example.comcorp.example.comteam1.corp.example.comcorp.example.com

Global Caching

这个主要是针对 Go modules 的全局缓存数据说明,如下:

$GOPATH/pkg/mod$GOPATH/pkg/sum$GOCACHE/mod$GOCACHE/sum$GOPATHgo clean-modcache

另外在 Go1.11 之后 GOCACHE 已经不允许设置为 off 了,我想着这也是为了模块数据缓存移动位置做准备,因此大家应该尽快做好适配。

快速迁移项目至 Go Modules

go mod init

迁移后 go get 行为的改变

这里我们注意到有两点比较特别,分别是:

go mod vendor

使用 Go Modules 时常遇见的坑

坑 1: 判断项目是否启用了 Go Modules

坑 2: 管理 Go 的环境变量

go env-wos.UserConfigDirgo env-w

坑 3: 从 dep、glide 等迁移至 Go Modules

这里主要是指从旧有的依赖包管理工具(dep/glide 等)进行迁移时,因为 BUG 的原因会导致不经过 GOPROXY 的代理,解决方法有如下两个:

  • 手动创建一个 go.mod 文件,再执行 go mod tidy 进行补充。
  • 上代理,相当于不使用 GOPROXY 了。

坑 4:拉取私有模块

这里主要想涉及两块知识点,如下:

  • GOPROXY 是无权访问到任何人的私有模块的,所以你放心,安全性没问题。
  • GOPROXY 除了设置模块代理的地址以外,还需要增加 “direct” 特殊标识才可以成功拉取私有库。

坑 5:更新现有的模块

坑 6:主版本号

Go Module Proxy 简介

在这里再次强调了 Go Module Proxy 的作用(图左),以及其对应的协议交互流程(图右),有兴趣的小伙伴可以认真看一下。

Goproxy 中国

在这块主要介绍了 Goproxy 的一些实践操作以及 goproxy.cn 的一些 Q&A 和 近况,如下:

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 完全交给了七牛云,甚至连域名都过户了去。

近况

  • Goproxy 中国 (goproxy.cn) 是目前中国最可靠的 Go module proxy (真不是在自卖自夸)。
  • 为中国 Go 语言开发者量身打造,支持代理 GOSUMDB 的默认值,经过全球 CDN 加速,高可用,可 应用进公司复杂的开发环境中,亦可用作上游代理。
  • 由中国倍受信赖的云服务提供商七牛云无偿提供基础设施支持的开源的非营利性项目。
  • 目标是为中国乃至全世界的 Go 语言开发者提供一个免 费的、可靠的、持续在线的且经过 CDN 加速的 Go module proxy。
  • 域名已由七牛云进行了备案 (沪ICP备11037377号-56)。

情况

此处呈现的是存储大小,主要是针对模块包代码,而一般来讲代码并不会有多大,0-10MB,10-50MB 占最大头,也是能够理解,但是大于 100MB 的模块包代码就比较夸张了。

此时主要是展示了一下近期 goproxy.cn 的网络数据情况,我相信未来是会越来越高的,值得期待。

Q&A

Q:如何解决 Go 1.13 在从 GitLab 拉取模块版本时遇到的,Go 错误地按照非期望值的路径寻找目标模块版本结果致使最终目标模块拉取失败的问题?

goget

Q:使用 Go modules 时可以同时依赖同一个模块的不同的两个或者多个小版本(修订版本号不同)吗?

example.com/foobar@v1.2.3example.com/foobar/v2@v2.3.4v0v1v1.0.0v1.0.1v1.0.1go.modv1.0.1
v1.0.0
go.sum
go.sumgo.modgo.modgo.modgo.mod
go.modreplace
replace
example.com/b~/bgo.modreplace example.com/b=>~/bimport"example.com/b"import"~/b"
excludereplace
go.modreplace c=>~/some/path/creplacec~/some/path/c

总结

在 Go1.13 发布后,接触 Go modules 和 Go module proxy 的人越来越多,经常在各种群看到各种小伙伴在咨询,包括我自己也贡献了好几枚 “坑”,傲飞的这一次 《Go Modules、Go Module Proxy 和 goproxy.cn》的技术分享,非常的有实践意义。如果后续大家还有什么建议或问题,欢迎随时来讨论。

最后,感谢在 goproxy.cn 背后的人们(@七牛云 和 @盛傲飞)对中国 Go 语言社区的无私贡献和奉献。