Go进阶-依赖管理详解 | 青训营笔记

109 阅读6分钟

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

依赖指各种开发包,在开发过程中需要站在巨人肩膀上,利用已经封装好的、经过验证的开发组件或工具来提升自己的研发效率,专注业务逻辑,而其他的框架,目标、driver、以及collection等一系列依赖都会通过sdk(软件开发工具包)方式引入。这时对依赖包的管理就显得尤为重要。

而Go的依赖管理主要经历了3个阶段,它的演进路线是:

graph LR
GOPATH --> Go_Vender --> Go_Module

整个演进围绕两个问题:不同项目依赖版本不同以及控制依赖库的版本。通过了解演进路线,我们能更深的理解GO现在使用的依赖管理:Go Module。

GOPATH:

在多人开发时,如果每个人有一套自己的目录结构,读取配置文件位置不统一,输出二进制运行文件也不统一,这样会导致开发标准不统一,影响开发效率。

GOPATH是一个环境变量,它使用绝对路径提供项目的工作目录(工作区)。go get它的目录有以下结构:

1675086629(1).png

查看环境变量:windows cmd下go env。
GOPATH在windows下默认路径为:C:\Users\用户名\go

  • GOPATH依赖管理的实现逻辑:
    项目所有的依赖都是依赖GOPATH路径下的src文件下的代码,把所有源代码都放到src下
  • 弊端:
    1. package的多版本控制,无法实现多个项目依赖同一个库的不同版本。(因为同一个库的不同版本代码实现是不一样的,而GOPATH依赖管理是直接从一个绝对路径import库,这就会导致所有的项目只能依赖同一份代码)。
    2. 没法在GOPATH工作区以外的地方写代码。(在GOPATH之外创建项目并使用外部依赖时,运行时Go会提醒我们找不到导入包。)

Go Vendor:

  • 实现逻辑:项目目录下增加vendor文件夹,所有依赖包都放一份副本在vendor文件夹;通过为每个项目引入一份依赖副本,解决了多个项目依赖同一个库的不同版本问题。

  • 依赖寻址方式: 先在项目目录下的vendor文件夹找,找不到则在GOPATH的绝对路径下的src文件夹找。

  • 弊端:

    1. 无法很好解决依赖包的版本变动问题。
    2. 单个项目依赖冲突。(项目A依赖包B和包C,包B和包C依赖包D的不同版本)
      归根结底的问题是:还是依赖源码,不能标识版本。

Go Module (Go Mod)

Go Modules是Go语言官方推出的依赖管理系统,从Go 1.11开始实验性引入,Go 1.16默认开启,解决了之前依赖管理系统存在的诸如无法依赖同一个库的多个版本等问题。其终极目标为:定义版本规则和管理项目的依赖关系。

实现逻辑: Go Module通过go.mod文件管理依赖包的版本,通过go get/go mod指令工具管理依赖包。解决了Go Vender出现的问题,可以管理依赖的版本。

依赖管理三要素:

  1. 配置文件,描述依赖 go.mod

是一个特殊的文件,它指定仓库的规范名称,go通过这个名称解析导入包的位置。文件中规范名称表示的新实体称为Module(模块),

  • go.mod文件内容例子: 1675427030(1).png module:定义当前项目的模块路径。
    go:来设置预期的go版本。
    require:设置特定的模块版本,每个依赖单元用模块路径+版本唯一表示。
    indirect注释:表示该单元依赖是间接依赖。
    incompatible后缀:表示没有go.mod文件且主版本2+的依赖。(为了兼容GoModule规则提出之前就已有的2+版本依赖。) 模块路径/vN后缀:有go.mod文件且主版本2+的依赖。

  • 版本规则: GOPATH和GOVendor都是源码副本方式依赖,没有版本规则概念,而GoMod为了方便管理依赖则定义了自己的版本规则,分为语义化版本和基于commit伪版本。

    • 语义化版本规则
      [MAJOR].[MINOR].[PATCH] eg:V1.3.0, V2.3.0

      MAJOR: MAJOR不同则表示接口不兼容,区分模块。同一个库该版本也可能不同。
      MINOR:新增函数或功能,向后兼容。
      PATCH:修复bug。

    • 基于commit伪版本
      [基本版本前缀]-[时间戳]-[校验码]
      eg: v0.0.0-20220401081311-c38fb59326b7
      每次提交commit后Go都会生成一个伪版本号。

      基础版本前缀:和语义化版本的一样
      时间戳:提交commit的时间
      校验码:包含12位的哈希前缀

  • 依赖图
    Go进行版本选择的算法会选择能让程序运行的最低的兼容版本。(v1.4包含v1.3所有) 1675431415(1).png

  1. 中心仓库管理依赖库 Proxy

依赖分发是从哪里下载,如何下载依赖库的问题。

  • 依赖分发-回源
    直接使用github,对于go.mod中定义的依赖直接从对应仓库中下载指定版本依赖,从而完成依赖分发。

    github是常见代码托管系统平台,GoModules系统中定义的依赖最终可以对应到多版本代码管理系统中某个项目的特定提交or版本。

    存在问题:

    1. 无法保证构建稳定性和可用性。(软件作者增删改软件版本,导致找不到版本或者版本变更)
    2. 增加第三方代码托管平台压力
  • 依赖分发-Proxy
    Go Proxy是一个服务站点,通过缓存源站中的软件内容,来解决稳定性,可用性以及减轻三方代码托管平台压力问题。
    使用Go Proxy之后,构建时会直接从Go Proxy站点拉取依赖。

  • 依赖分发-变量 GOPROXY
    Go Proxy的使用:通过GOPROXY环境变量控制使用。

    GOPROXY是一个Go Proxy站点的URL列表,使用direct表示源站。eg:GOPROXY= "UPL1,URL2,dorect",会安装列表顺序挨个网页找我们需要的依赖,找到了之后就会缓存到proxy站点中。

  1. 本地工具 go get/mod
  • go get用于下载依赖包
    go get example.org/pkg @update \\ 默认下载
    go get example.org/pkg @none \\ 删除依赖
    go get example.org/pkg @V1.1.2 \\ 指定语义版本
    go get example.org/pkg @23dfdd5 \\ 指定commit版本
    go get example.org/pkg @master \\ 指定分支最新commit
  • go mod用于管理包
    go mod init \\ 初始化创建go.mod文件
    go mod download \\ 下载模块到本地缓存
    go mod tidy \\ 增加需要依赖删除不需要
    尽量提交之前执行下go tidy减少构建时无效依赖包的拉取。

参考

[1] Go语言GOPATH是什么 - 海布里Simple - 博客园 (cnblogs.com)
[2] Go语言环境配置与go mod - 简书 (jianshu.com)
[3] Go语言的依赖管理 | 青训营笔记 - 掘金 (juejin.cn)
[4] Go 语言进阶——依赖管理| 青训营笔记 - 掘金 (juejin.cn)
[5] Go 语言进阶与依赖管理 - 掘金 (juejin.cn)