Go语言包管理简史

img{512x368}

包管理是Go一直被诟病做得不好的功能之一。先前版本(go 1.11之前)的主要缺点之一是go get是缺乏对依赖包版本的管理和对可复制构建(reproducible build)的支持。Go社区已经开发了一些包管理器和工具作为版本化包依赖的事实标准解决方案,如glidedep以及一些辅助工具等。

“我在生产构建中使用go get。” – 没有人这么说过。

Go语言的包管理实现可追溯到Google公司内的代码依赖管理(Google将内部所有源代码都存放在一个巨大的单体存储库中)。我们来分析一下在”Go module”之前Go语言的包管理工具都出了什么问题。

  • 依赖包的版本化
  • 依赖包的本地缓存(vendor)
  • GOPATH的必要性

依赖包的版本化

go get默认情况下不支持包版本控制。go软件包管理的第一版实现背后的想法是-不需要包版本控制,不需要第三方包存储库,您可以从当前分支中构建所有内容。

Go 1.11之前的版本中,添加依赖项意味着将该依赖项的源代码仓库克隆到$GOPATH下面。就是这样,没有版本的概念。版本始终指向克隆时刻的主分支。出现了另一个主要问题是,当不同的项目需要依赖包的不同版本时,Go包管理工具无法实现。

依赖包的本地缓存(vendor)

依赖包本地缓存通常是指相关依赖包与项目存储在同一位置。这通常意味着将您的依赖项源码也提交到源管理系统中,例如Git。

考虑这样一种情况- A使用依赖项B,而B使用了C版本在1.5版本中引入一个功能,这时B必须确保A在构建时使用的也是C 1.5或更高版本。在Go 1.5之前的版本中,没有一种机制可以在不重写导入路径的情况下将依赖包代码与命令绑定在一起。

GOPATH的必要性

GOPATH存在的主要原因有两个:

  1. 在Go中,import声明通过其完全限定的导入路径来引用包。GOPATH存在可以方便Go工具计算GOPATH/src内的任何目录所涉及软件包的绝对导入路径。
  2. 它是Go get命令存储包依赖项的位置。

这有什么问题?

  1. GOPATH 不允许开发人员像其他语言一样选择任意喜欢的目录签出项目的源代码。
  2. 此外,GOPATH不允许开发人员同时检出某个项目(或其依赖项)的多个副本。

Go Module介绍

Go 1.11引入了对Go模块(module)的初步支持。下面摘自Go Wiki:

一个模块是一组相关的Go包的集合,这个包集合被当做一个独立的单元进行统一版本管理。模块精确记录了依赖要求并支持创建可复制的构建。

Go模块带来了三个重要的内置功能:

  1. go.mod文件,它与package.json或Pipfile文件的功能类似。
  2. 机器生成的传递依赖项描述文件 – go.sum。
  3. 不再有GOPATH限制。模块可以位于任何路径中。
$ go help mod
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

    go mod <command> [arguments]

The commands are:

    download    download modules to local cache
    edit        edit go.mod from tools or scripts
    graph       print module requirement graph
    init        initialize new module in current directory
    tidy        add missing and remove unused modules
    vendor      make vendored copy of dependencies
    verify      verify dependencies have expected content
    why         explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

更多相关讨论在这里

迁移到Go Module

要使用Go模块,请更新Go到1.11及以上版本。由于不再需要GOPATH,因此可以通过以下两种方式之一激活模块支持(译注:下面的行为仅适用于Go 1.11~Go 1.12Go 1.13版本默认开启Go module,无论是否在GOPATH下,除非GO111MODULE=off):

  • 在GOPATH/src之外的目录中调用Go命令,并在当前目录中存在一个有效的go.mod文件。
  • 如果源码在GOPATH之下,Go模块将不起作用。要改变此行为,请设置环境变量GO111MODULE=on后再调用Go命令。

让我们通过以下简单的步骤开始迁移:

  • 由于GOPATH不再必要的了,将module移出GOPATH。

  • 在项目根目录中,创建初始模块定义 – go mod init github.com/username/repository。go mod还会自动转换现有的包管理器(如dep和Gopkg,glide以及其他六种)的依赖关系。这将创建一个名为go.mod的文件,该文件存储了模块名以及模块的依赖项及其版本。

$ cat go.mod
module github.com/deepsourcelabs/cli

go 1.12

require (
    github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e
    github.com/getsentry/raven-go v0.2.0
    github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9
)
  • 运行go build会创建一个go.sum文件,其中包含特定模块版本的内容的预期校验和。这是为了确保这些模块将来的下载内容与第一次下载是相同的。请注意,go.sum不是锁文件。
$ cat go.sum
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e h1:9574pc8MX6rF/QyO14SPHhM5KKIOo9fkb/1ifuYMTKU=
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9 h1:dIsTcVF0w9viTLHXUEkDI7cXITMe+M/MRRM2MwisVow=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

关于版本控制的注意事项:为了保持向后兼容性,如果模块的版本为v2或更高版本,则模板的主版本必须以/vN的形式被包含在go.mod文件中使用的模块路径的末尾。比如:module github.com/username/repository/v2

日常命令

列出依赖项

go list -m all 列出当前模块及其所有依赖项。

$ go list -m all
github.com/deepsourcelabs/cli
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e
github.com/getsentry/raven-go v0.2.0
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9

在go list输出中,当前模块(也称为主模块)始终是第一行,其后是路径排序所有依赖模块。

列出软件包的可用版本

go list -m -versions github.com/username/repository 列出软件包的可用版本。

$ go list -m -versions github.com/getsentry/raven-go
github.com/getsentry/raven-go v0.1.0 v0.1.1 v0.1.2 v0.2.0

添加依赖

添加依赖项是隐式的。在代码中导入依赖项后,运行go build或go test命令将获取模块的最新版本并将其添加到go.mod文件中。如果要显式添加依赖项,请运行go get github.com/username/repository。

依赖项的升级/降级

go get github.com/username/repository@vx.x.x下载并设置依赖项和更新go.mod文件的特定版本。

$ go get github.com/getsentry/raven-go@v0.1.2
go: finding github.com/getsentry/raven-go v0.1.2
go: downloading github.com/getsentry/raven-go v0.1.2
go: extracting github.com/getsentry/raven-go v0.1.2

$ cat go.mod
module github.com/deepsourcelabs/marvin-go

go 1.12

require (
    github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e
    github.com/getsentry/raven-go v0.1.2
    github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9
)

$ cat go.sum
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e h1:9574pc8MX6rF/QyO14SPHhM5KKIOo9fkb/1ifuYMTKU=
github.com/certifi/gocertifi v0.0.0-20190410005359-59a85de7f35e/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/getsentry/raven-go v0.1.2 h1:4V0z512S5mZXiBvmW2RbuZBSIY1sEdMNsPjpx2zwtSE=
github.com/getsentry/raven-go v0.1.2/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9 h1:dIsTcVF0w9viTLHXUEkDI7cXITMe+M/MRRM2MwisVow=
github.com/pkg/errors v0.0.0-20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

vendor依赖项

使用模块时,go命令将完全忽略vendor目录。为了向后兼容旧版Go,或确保将用于构建的所有文件一起存储在单个文件树中,请运行go mod vendor。

这将在主模块的根目录中创建一个vendor目录,并将依赖模块中的所有软件包存储在该目录中。

注意:要使用主模块的顶级vendor目录进行构建,请运行’go build -mod=vendor’。

删除未使用的依赖项

go mod tidy将删除未使用的依赖项并更新go.mod文件。

常见问题解答

  1. GOPATH不再需要了?
    是,永别了GOPATH。

  2. 默认情况下拉取哪个版本?
    go.mod文件和go命令通常将语义版本用作描述模块版本的标准形式,以便可以比较版本以确定哪个版本应早于或晚于其他版本。v1.2.3通过在基础源存储库中标记(tag)修订来引入类似的模块版本。未标记(untag)的修订版可以使用“伪版本”之类的来引用:v0.0.0-yyyymmddhhmmss-abcdefabcdef,其中时间是UTC的提交时间,最后的后缀是提交哈希的前缀。

  3. go.sum应该被检入到版本库中吗?
    是。

鉴于本人近期较忙,又不希望让博客长草,近一段时间会挑选翻译一些笔者认为比较优秀的外文文章分享给大家。

本文翻译自《Package management in Go – brief overview of package management in Go — pre and post Go modules》


我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网上线了,感谢小伙伴们学习支持!

我爱发短信:企业级短信平台定制开发专家 https://51smspush.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

我的联系方式:

微博:https://weibo.com/bigwhite20xx
微信公众号:iamtonybai
博客:tonybai.com
github: https://github.com/bigwhite

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

Go语言回顾:从Go 1.0到Go 1.13

Go 1.13版本在2019.9.3正式发布!国外的Gopher Vincent Blanchon发表了一篇文章《Go: Retrospective》(科学上网阅读),对Go从1.0版本到1.13版本做了简要的回顾,这里是那篇文章的译文。

img{512x368}

对于每一位Go开发者来说,Go语言的演化历程是必须要知道的事情。了解这些横跨年份发布的大版本的主要变化将有助于Gopher理解Go语言的发展理念以及该语言每个版本的优势与不足。更多关于特定版本的变更细节,可以参考每个版本对应的Changelog

Go 1.0 – 2012.3月

伴随着Go语言的第一个版本,Go的缔造者还发布了一份兼容性文档。该文档保证未来的Go版本将保持向后兼容性(backward-compatible),即始终兼容已有的代码,保证已有代码在Go新版本下编译和运行的正确性。

Go 1.0版本还包含了go tool pprof命令,这是一个Google pprof C++ profiler的变体。Go 1.0还提供了go vet命令(之前的go tool vet),用于报告Go package中可能的错误。

Go 1.1 – 2013.5月

该版本主要专注于语言改善和性能提升(编译器、垃圾回收、map、goroutine调度)。这里是一个改善后的效果示意图:

img{512x368}
图来自https://dave.cheney.net/2013/05/21/go-11-performance-improvements

这个版本同时还嵌入了一个竞态探测器(race detector),这个工具对于Go这种原生并发的语言是十分必要的。在《Race Detector with ThreadSanitizer”》一文中,你可以找到有关race detector的更多详细信息。

在这个版本中的一个重点变动是Goroutine调度器被重写了,重写后的调度器性能大幅提升。

重写后的Go调度器的设计如下图:

img{512x368}
图来自 https://rakyll.org/scheduler/

M对应的是操作系统的线程。P表示一个处理器(P的数量不能超过GOMAXPROCS),每个P拥有一个本地goroutine队列。在1.1版本之前,P这个抽象并不存在。所有goroutine的调度通过全局互斥锁进行全局级别的管理。这次改进实现了”work-stealing”算法,允许某个P从其他P的队列中”偷goroutine”:

img{512x368}
图来自 https://rakyll.org/scheduler/

更多关于Go调度器调度原理以及”work-stealing”算法的信息,可以查看Jaana B. Dogan的文章《Go’s work-stealing scheduler》

Go 1.2 – 2013.12

在该版本中,Go test命令开始支持代码测试覆盖率统计了,并且通过go提供的新子命令: go tool cover可以查看代码测试覆盖率统计信息:

img{512x368}
图来自 https://blog.golang.org/cover

它还能提供代码覆盖信息:

img{512x368}
图来自 https://blog.golang.org/cover

Go 1.3 – 2014.6

该版本包含了栈管理的一个重要改进。在该版本中,栈内存分配采用连续段(contiguous segment)的分配模式以提升内存分配效率。这将为下一个版本将栈size降到2KB奠定基础。之前的分割栈分配方式(segment stack)存在频繁分配/释放栈段导致栈内存分配性能不稳定(较低)的问题,引入新机制后,分配稳定性和性能都有较大改善。

这里是一个json包的例子,图中显示json包对栈size的敏感度:

img{512x368}
图来自 contiguous stack

使用连续段的栈内存分配管理模式解决了一些程序性能低下的问题。下面是html/template包的性能对stack size的敏感度图:

img{512x368}

更多信息可参见[《How Does the Goroutine Stack Size Evolve?”》(https://medium.com/@blanchon.vincent/go-how-does-the-goroutine-stack-size-evolve-447fc02085e5)]。

这个版本还发布了sync.Pool。这个组件允许我们后面重用结构体,减少内存分配的次数。它也将成为Go生态圈中许多性能提升的源头,比如:标准库中的encoding/json、net/http或是Go社区中的zap等。

关于sync.Pool的更多信息,可以参考文章《Understand the Design of Sync.Pool》

Go开发组在该版本中对channel进行了优化改善,使其性能获得提升。下面是channel在Go 1.2和Go 1.3版本中的基准测试数据对比:

img{512x368}

Go 1.4 – 2014.12

在该版本中,Go提供了对Android的官方支持。使用golang.org/x/mobile包,gopher们可以使用Go编写简单的Android应用。

同时,之前版本中大量用C语言和汇编语言实现的运行时已经被翻译为Go,一个更为精确的垃圾回收器让堆内存分配减少了10~30%。

和版本自身无关的是,Go工程在本次发布后已经从Mercurial迁移到Git,从Google code迁移到github。

Go还发布了go generate命令,该命令可以通过扫码代码中的//go:generate指示器来生成代码,可以帮助Gopher简化代码生成工作。

更多关于这方面的信息可以参考Go blog和这篇文章《Generating code》

Go 1.5 – 2015.8

这个新版本推迟了两个月发布,目的是适应Go新的开发发布周期:每年二月和八月进行发布:

img{512x368}
图来自:https://github.com/golang/go/wiki/Go-Release-Cycle

在该版本中,垃圾回收器全面重构。由于引入并发回收器,回收阶段带来的延迟大幅减少。下面是来自一个生产环境服务器上的延迟数据,我们看到延迟从300ms降到了30ms:

img{512x368}
图片来自 https://blog.golang.org/ismmkeynote

这个版本还发布go tool trace命令,通过该命令我们可以实现执行器的跟踪(trace)。这些跟踪是在test执行、运行时生成的,跟踪信息可以通过浏览器呈现:

img{512x368}
图片来自原始Go Execution Tracer文档

Go 1.6 – 2016.2

这个版本的最显著变化是当使用HTTPS时,将默认支持HTTP/2。

垃圾回收器的延迟在该版本中进一步降低:

img{512x368}
图片来自https://blog.golang.org/ismmkeynote

Go 1.7 – 2016.8

这个版本发布了context包。该包用于处理timeout和取消任务。

更多关于context包的信息,可参考文章:《Context and Cancellation by Propagation》

编译器工具链的性能得到了较大幅度优化,编译速度更快,二进制文件size更小,有些时候幅度可达20~30%。

Go 1.8 – 2017.2

垃圾回收器的延迟在该版本中进一步改善,延迟时间已经全面降到毫秒级别以下:

img{512x368}
图片来自https://blog.golang.org/ismmkeynote

对延迟的优化还将继续。接下来版本的目标是将延迟降到100微秒左右。

这个版本还大幅提升了defer的性能:

img{512x368}
图片来自 https://medium.com/@blanchon.vincent/go-how-does-defer-statement-work-1a9492689b6e

更多关于defer的信息,可以参考文章How Does Defer statement Work?

Go 1.9 – 2017.8

该版本引入了alias语法。

type byte = uint8

这里byte是unit8的一个alias。

sync包增加了Map类型,该类型支持并发访问(原生map类型不支持)。

关于map的更多信息,参考文章“Concurrency Access with Maps”

Go 1.10 – 2018.2

在该版本中,test包引入了一个新的缓存机制,所有通过测试的结果都将被缓存下来。当test没有变化时,重复执行test会节省大量运行test的时间。

first run:
ok      /go/src/retro 0.027s
second run:
ok      /go/src/retro (cached)

go build命令也维护了一个已构建的包的缓存以加速构建性能。

该版本中垃圾回收器并没有显著性能提升。但是Go team为垃圾回收定义了一个新的SLO(Service-Level Objective):

img{512x368}
图片来自https://blog.golang.org/ismmkeynote

Go 1.11 – 2018.8

Go 1.11引入了一个重要的新功能:Go modules。Go module的引入是为了应对过去几年官方调查问卷结果中Go社区反馈的几个主要挑战:

img{512x368}
图片来自 https://blog.golang.org/survey2018-results

另外一个重要功能是一个试验功能:支持WebAssembly。允许开发人员将Go源码编译成一个兼容四个主流浏览器的二进制格式文件。

Go 1.12 – 2019.2

该版本中,go vet基于analysis包进行了重写,使得go vet更为灵活并支持Go开发人员编写自己的checker。

更多关于analyzer的信息可以参考文章《How to Build Your Own Analyzer》

Go 1.13 – 2019.8

在该版本中,sync.Pool得到了改善:当垃圾回收时,pool中对象不会被完全清理掉。它引入了一个cache,用于在两次GC之前清理pool中未使用的对象实例。

逃逸分析(escape analysis)被重新实现了,在该版本中,Go得意更少地在堆上分配内存了。下面是新旧逃逸分析的基准测试对比:

img{512x368}
图片来自 https://github.com/golang/go/issues/23109


我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网上线了,感谢小伙伴们学习支持!

我爱发短信:企业级短信平台定制开发专家 https://51smspush.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

我的联系方式:

微博:https://weibo.com/bigwhite20xx
微信公众号:iamtonybai
博客:tonybai.com
github: https://github.com/bigwhite

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! 图片广告位1 图片广告位2 图片广告位3 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats