零基础学习Go的Day11| 青训营笔记

101 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天

1.复习已学知识

  • 复习并发编程的基本知识
  • 复习Goroutine
  • 复习WaitGroup
  • 复习Lock
  • 复习Channel

2.观看Go语言进阶与依赖管理

  • Go 依赖管理

为什么需要依赖管理

要想了解依赖管理,首先我们必须知道为什么需要它。考虑这么一个情况:你的代码不可能只引用自系统库和语言标准库,很大概率的,你要使用来自网络的,第三方的,其他开发者的依赖库,例如某些 ORM 框架,HTTP 框架等。这些依赖库来自不同站点的不同存储库,拥有不同的版本;这些依赖库本身也拥有许多依赖...... 这些事情总能使得每一位程序员焦头烂额。为了解决依赖的管理问题,Go 引入了依赖管理。

演进过程

依赖管理并不是 Go 的独创,早在 Go 之前,就有许多不同语言的依赖管理工具了,例如 Java 的 Maven/Gradle,.NET 的 NuGet,Python 的pip,Rust 的 Cargo,JavaScript(NodeJS)的 npm/yarn/pnpm。对于 Go 的依赖管理来说,经历了 GOPATH,Go Vender,Go Module 三部分的演进。

GOPATH

最初,Go 直接将依赖库源码扔进 GOPATHsrc 文件夹以作为项目依赖。

GOPATH 是一个环境变量,指向一个目录,作为项目的编译产出目录和依赖目录。这是一个公共环境变量,也就意味着,所有项目都依赖于同一个 GOPATH,这就会导致这样的问题:如果项目 A 依赖于依赖库 Lib 的版本 1,而项目 B 依赖于同一个依赖库的版本 2,由于 GOPATH 并没有任何版本管理措施,就会导致编译出错。

Go Vendor

于是,Go 引入了 Go Vendor,通过在项目目录下新建 vendor 文件夹,并存放依赖库文件副本的方式,使得不同项目可以依赖不同的依赖库版本,解决了版本冲突的问题。

值得一提的是,如果无法在 vendor 文件夹中找到项目所需的依赖文件,那么 Go 会尝试回到 GOPATH 查找。

Go Vendor 的引入看似解决了版本问题,但是实际上依然造成了问题:如果项目 A 引入了 项目 B 和项目 C 作为依赖库,而后两者又共同依赖了项目 D 的不同版本,那么由于 B,C,D 作为项目 A 的依赖依然被同时存在同一个 vendor 文件夹中,依旧导致了依赖冲突。

Go Module

因此经过了六道轮回,终于,Go 采用了和 JavaScript(NodeJS)的 npm 类似的包管理方案 —— 这就是 Go Module。

Go Module 通过项目路径中的 go.mod 文件(类似于 npm 的 pakcage.json 声明所需依赖的名称和版本范围),然后,通过 go.sum 文件记录项目实际使用的依赖和版本(类似于 npm 的 package-lock.json)。

我们没有必要像 Java 的 Maven/Gradle 那样手动编辑配置文件指定依赖,Go 为我们提供了 go getgo mod 两条指令来方便的添加和移除项目中的依赖。

go.mod 文件详解

一个合法的 go.mod 文件可能长这样:

module example/project/app
​
go 1.16
​
require (
    example/lib1 v1.0.2
    example/lib2 v1.0.0 // indirect
    example/lib3 v0.1.0-20190725025543-5a5fe074e612
    example/lib4 0.0.0-20180306012644-bacd9c7efldd // indirect
    example/lib5/v3 v3.0.2
    example/lib6 v3.2.0+incompatible
)

首先,module example/project/app 标识了依赖管理的基本单元;

然后,go 1.16 指定了 Go 原生库(标准库)的版本,此处我们指定版本为 1.16

require` 内则指定了单元依赖,格式是 `[Module Path] [Version/Pseudo-version]

对于一个依赖项,我们首先指定其名称(路径),然后指定所需的版本。版本号应当按照语义化版本(MAJOR.MINOR.PATCH)的格式填入,例如 v1.0.2

或者,我们可以填入一个基于 commit 的伪版本,代表我们需要来自某个 commit 的依赖库版本,它的格式是 vx.0.0-yyyymmddhhmmss-abcdefgh1234,其中 yyyymmddhhmmss 是提交 Commit 的时间,而 abcdefgh1234 则是该 commit 的哈希值。

有的依赖可能会使用 // indirect 注释标识,这意味着该依赖并非由项目直接引入,而是透过其他依赖间接引入(例如 A 项目引入了 B 依赖,B 依赖依赖于依赖 C,那么依赖 C 就是 项目 A 的间接依赖)

有的依赖可能会在版本末尾添加 +incompatible 标识,这是为了兼容非语义化版本所致。

依赖分发

如上所述,我们的依赖可能来自世界各地的不同依赖库,他们可能来自不同的代码托管网站,例如 GitHub,GitLab,这些代码托管网站可能无法接受来自数以百万计的 Go 开发者的依赖拉取请求。为了解决这个问题,Go 引入了 Proxy 系统,通过指定 Proxy 服务器,优先从 Proxy 服务器拉取依赖,这不仅减轻了源站负担,也保证了依赖的可用性,避免依赖库开发者删库跑路。

可以通过指定 GOPROXY 环境变量的方式指定 Proxy 服务器,其值为一个由逗号分隔的网址列表,例如 "https://proxy1.cn, https://proxy2.cn, direct"。当需要拉取依赖时,Go 便会按顺序从依赖服务器拉取代码,如果找不到指定的依赖,那么就前往下一个依赖服务器拉取,直到前往源站(即direct)拉取代码。

对于中国大陆的朋友们,不妨可以试试 https://goproxy.cn/https://goproxy.io 这两个 Proxy 服务器,可以大幅加速依赖拉取速度。

go get 指令

go get 是一个命令行指令,可用于添加和移除依赖,在项目目录执行它以为项目配置依赖:

基本语法:go get example.org/pkg????

其中,example.org/pkg 是所需依赖的仓库地址,????可以取以下值:

  • @update,缺省值(不填写时使用的值),使用最新版本
  • @none,删除项目中的此依赖
  • @v1.1.2,使用此版本的依赖
  • @23dfdd5,使用特定的 commit
  • @master,使用指定分支的最新 commit

go mod 指令

go mod 是一个命令行指令,可用于初始化项目和管理依赖,在项目目录执行它以为项目配置依赖:

  • go mod init,初始化项目,这将创建 go.mod 文件,类似于 npm 的 npm init
  • go mod download,下载模块到本地缓存
  • go mod tidy,添加需要的依赖,删除不需要的依赖(有点类似于 apt autoremove