欢迎光临散文网 会员登陆 & 注册

万字长文解读 pkg.go.dev 的设计和实现

2023-07-11 08:46 作者:清澄秋爽  | 我要投稿

https://mp.weixin.qq.com/s/btX53JVCgfOfxDy2ynQa_A


文章较长,建议收藏,抽完整时间阅读。觉得不错帮转发下!

北京时间 2020 年 6 月 15 日 22 点左右,Go 官方发博文(https://blg.golang.org/pkgsite)宣布,pkg.go.dev 开源了。开源代码托管在 Google 自有仓库 https://go.googlesource.com/pkgsite,不过在 GitHub 上提供了镜像:https://github.com/golang/pkgsite。同时,对于该项目的任何 issue,通过 Go 主仓库进行管理,即 https://github.com/golang/go/labels/go.dev。

Go 作者们可能也没有想到,经过这么多年的发展,Go 被使用最广的竟然是 Web 开发,这可能得益于一开始 Go 就对 http 有很好的支持,也因此涌现出大量的 Web 框架,其中知名的有 Gin、Echo、Beego 等。

运营 Go 语言中文网和 Go 社区 8 年有余,发现广大 Gopher 们都苦于没有实战项目可以练手,很多新手学习完 Go 语法后,因为工作中没有用到,不知道该怎么进行项目实战或用 Go 做点什么。这期间也有很多人问我有无适合新手学习的开源项目。也是在去年初,我因此还创建了《Go项目实战》知识星球。现在,Go 官方开源了 pkg.go.dev 这个 Web 项目,爱学习的你,不应该只是看到了这个消息就完事了,应该做点什么,学点什么。

原计划,我是希望通过这个项目,让大家能够很好的学习 Go 是如何进行实际项目开发的。当我深入研究 pkg.go.dev 源码后,我失望了,无论是设计还是实现,水平都很一般。本想着放弃这一系列,但想想还是继续,一方面尝试指出问题,给出认为正确的做法;另一方面,毕竟是官方的项目,开源了相信它会变得更好。

本文包括的内容(并非大纲):

图片

项目架构

先上一张官方的架构图:

图片
architecture

包含了三个核心组件:

  • Frontend:这是一个面向用户的前端组件,处理用户请求,获取数据并展示给用户;

  • Worker:一个后台程序,负责将新模块的信息写入数据库。新模块的数据来自 Go Module Index[1],同时这些模块的内容是从 Go Module Mirror[2] 中下载的;

  • Database:数据库,用于存储站点上提供的所有信息。

图中其他的部分包括:Frontend 组件使用的 Redis 缓存、任务队列和 Scheduler。

Frontend 组件

这是一个简单的 HTTP 服务,它从数据库中获取数据,渲染模板,最后生成 HTML 页面。而搜索功能,是通过 Postgres 的全文搜索实现的,因此没有引入另外的搜索组件,比如:Solr、ElasticSearch。

目前前端做的事情比较简单:从 Postgres 数据库中获取 modules 和 packages 等数据,而 Redis 用于缓存这些数据。

细心的读者会发现这样的设计存在一个问题:更新不及时。前端数据依赖 Worker 组件写入 DB。我临时创建一个 Go 包,用于验证该问题:github.com/polaris1119/testpkg,在 godoc.org 上看到的信息如下:

图片

但 pkg.go.dev 上看到的却是:

图片

虽然这个 404 页面告诉你:如果你认为这是一个有效的包路径,可以通过这里的说明[3]尝试获取该包。那这个说明是什么?

因为 pkg.go.dev 的数据是从 proxy.golang.org 下载的。我们会定期监控 Go Module Index[4],以查找要添加到 pkg.go.dev 的新包。如果在 pkg.go.dev 上没有看到你要的包,则可以通过以下任一操作将其添加:

  • 向 proxy.golang.org 请求模块版本,可以请求模块代理协议(Module Proxy Protocol)[5]指定的任何端点,例如:https://proxy.golang.org/github.com/polaris1119/testpkg/@v/list;

  • 通过 go 命令下载相应的包。例如:GOPROXY=https://proxy.golang.org GO111MODULE=on go get      github.com/polaris1119/testpkg

可见,这个说明是告诉你怎么将包提交给 proxy.golang.org。即使这样做了,很可能 pkg.go.dev 上还是没有,因为 Frontend 只负责从 DB 获取数据。(几分钟后不出意外应该有了)

图片

然而这样的设计大家肯定接受不了,因此出现了几个这样的  issue:#36811[6], #37002[7], #37106[8] 等。为了应对这种情况,所以出现了架构图中的 Frontend Task Queue,用于获取数据库中还不存在的包并将包 master 分支的信息显示给用户,不过截止目前还未实现。不知道到时候以及会如何实现。

这里不得不吐槽了:

  • 这样的问题在设计之初就应该考虑。一个新的系统,不说比之前的 (godoc.org)好,至少不能差。godoc.org 在遇到包没有时会实时获取,而且还支持用户手动刷新;

  • 在访问某个不存在的包时,让用户按照说明操作下,以便 proxy.golang.org 上有这个包。这个过程完全可以程序自动做;不仅如此,还可以考虑自动触发 Worker 进行工作,拉取包数据;

Worker 组件

Worker 的主要工作是下载发现的新模块,进行处理,然后将信息写入数据库以供 Frontend 使用。它提取 README 文件,许可证文件(license)和文档,并将它们写入数据库。它还将与搜索相关的数据写入表(search_documents)。除了直接在模块 zip 中可用的搜索信息外,它还计算每个包的导入者数量。(imports_unique 表保存了每个包导入的其他包)

为了简化处理新模块的工作并利用上限速和重试功能,Worker 使用 Google Cloud Tasks[9] 队列来管理要处理的模块列表。当 Worker 程序在索引(index)中找到新模块时,它会将任务添加到队列中。队列以固定的最大速率将任务推送给 Worker。

文档提到:由于 Worker 必须是无状态 HTTP 服务器,因此无法运行后台任务。因此使用 Google Cloud Scheduler[10] 来定期执行任务。这些任务通常每分钟运行一次,它们是:

  • 轮询索引(index)以使新模块进入队列;

  • 对于暂时处理失败的模块,重新进入队列;

  • 更新每个包的导入者数量;

从架构图可以看到,Worker 从 index 获取新模块(默认是 index.golang.org),从 proxy 获取 module 的 zip 文件(默认是 proxy.golang.org),最后将信息、数据写入 postgres 数据库。

吐槽:

为什么 Worker 必须是无状态的 HTTP 服务,还无法运行后台任务?这里完全可以通过类似 https://github.com/robfig/cron 这样的库来处理定时任务。竟然设计成启动一个 HTTP 服务,由一个外部定时任务调度器(Google Cloud Scheduler)来调用它提供的接口。

数据库

数据库方面,主要看看表的设计,同时学习下是如何做迁移管理的。

该项目没有使用配置文件,而是通过环境变量来控制。比如 postgres 数据库相关配置信息通过如下环境变量控制:

  • GO_DISCOVERY_DATABASE_USER (default: postgres)

  • GO_DISCOVERY_DATABASE_PASSWORD (default: '')

  • GO_DISCOVERY_DATABASE_HOST (default: localhost)

  • GO_DISCOVERY_DATABASE_NAME (default: discovery-db)

这些配置信息在 internal/config/config.go 文件中。

表的设计是项目很重要的一个环节。为了看到该项目的表设计,安装好 postgres 后,执行如下脚本(类 Unix 系统)创建 discovery-db 数据库:

之后执行迁移操作。

数据库迁移

很多人可能对迁移不了解,这里简单介绍下。

这里说的数据库迁移,主要是指数据库 schema 的变更(当然也包括不同数据源往某个数据库迁移)。我们知道,代码的变更可以通过 Git 进行管理,通过 Git 可以很容易的实现代码的回滚。于是有人就想,代码变更时,很可能数据表的结构也变了,那有没有可能很方便的对数据库的变化进行回滚呢(升降版本)?于是有了 database migrate。就开源项目而言,对数据库自动升降级很有帮助。

然而数据库迁移并没有标准,依赖于具体的工具实现。迁移工具一般分为两种:1)独立的迁移软件,如 Liquibase[11];2)依附于具体语言的库,比如 Go 语言的 migrate[12],不过这个库也可以作为独立的软件使用。

每一次 schema 的变更,有时包括初始化数据,通常会记录在一个单独的脚本文件中。通常每一次数据库变更,应该生成一个对应的文件。我们通过 migrate 这个库具体学习下迁移的操作。

migrate 学习

该项目是 Go 语言实现的数据库迁移工具,支持 CLI 方式使用,也支持作为库导入使用。Migrate 从源读取迁移,并将迁移以正确的顺序应用于数据库。

目前该工具支持如下数据库:

  • PostgreSQL[13]

  • Redshift[14]

  • Ql[15]

  • Cassandra[16]

  • SQLite[17] (todo #165[18])

  • MySQL/ MariaDB[19]

  • Neo4j[20]

  • MongoDB[21]

  • CrateDB[22] (todo #170[23])

  • Shell[24] (todo #171[25])

  • Google Cloud Spanner[26]

  • CockroachDB[27]

  • ClickHouse[28]

  • Firebird[29]

  • MS SQL Server[30]

迁移来源支持如下几种:

  • Filesystem[31] - 从文件系统读取

  • Go-Bindata[32] - 从内嵌的二进制数据读取

  • Github[33] - 从远程 GitHub 仓库读取

  • Github Enterprise[34] - 从远程 Github 企业仓库读取

  • Gitlab[35] - 从远程 Gitlab 仓库读取

  • AWS S3[36] - 从 Amazon Web Services S3 读取

  • Google Cloud Storage[37] - 从 Google Cloud Platform Storage 读取

先安装,以 MacOS 为例:

为了方便演示,以上文创建的 https://github.com/polaris1119/testpkg 为例,clone 下来后,在 testpkg 目录下执行如下命令,创建一个迁移:

成功后会在 migrations 目录下生成两个文件:

这里有两个基本概念需要清楚:up 和 down,上面两个文件中有包含。

  • up 表示升级到当前版本;

  • down 表示回退到上一版本;

所以,我们在上面两个文件中填上如下内容:

  • up 文件的内容是创建表 gopher;

  • down 文件的内容是删除表 gopher;

之后就可以进行迁移操作了(这里假定你本地已经装上 postgres,密码是 123456,同时创建了 testpkg 数据库):

这时会发现数据库中多了两个表:gopher 和 schema_migrations。其中 schema_migrations 的内容如下:

versiondirty1FALSE

如果这时再执行如下命令进行“回滚”:

为了安全,会如下提示:

选择 y,成功后再看看数据库的变化,发现 gopher 表不见了,schema_migrations 表的内容也清空了,因为上次是版本 1 ,这次回退了。

为了进一步了解细节,我们再创建一个迁移,增加一个表:

会生成两个文件,现在有 4 个文件了:

注意到没?文件名前缀自动变为了 00002。同样,我们在新生成的两个文件中填上如下内容:

之后执行迁移命令:

输出:

查看数据库,发现 gopher 和 article 表都有了,schema_migrations 表中 version 字段值是 2,符合预期。

明白迁移是怎么回事了吗?现在回过头来看看 pkgsite 的迁移和数据表。

##pkgsite 的数据表

pkgsite 提供了几个脚本,方便使用。比如上文提到的创建数据库的脚本。创建迁移和执行迁移的脚本分别是:devtools/create_migration.sh 和 devtools/migrate_db.sh。我们是研究 pkgsite,自然是执行迁移:(默认 migrate_db.sh 认为 postgres 数据库账号的密码是空,因为我本地设置了密码是 123456,因此需要在 migrate_db.sh 中加上密码)

输出:

一共 21 个版本。这时在 discovery-db 数据库中生成了一系列表。我们拿其中一个的表:packages ,看看它的设计:

我又要吐槽了:

  • 借用火丁笔记老王的评价:这是应届生设计的吧

  • 表名一般建议使用单数形式;

  • 字符串类型竟然全部是 text,类型选择很随意,没有任何讲究;

  • 主键竟然是三个 text 类型的联合;

  • 。。。

总体看,设计者应该没有经历过大项目,或没有参与过因为量大而遇到性能问题的项目。

设计数据表的建议

这里给出当初在 360 时,公司 DBA 对创建数据表的一些建议或要求:

  • 表名、列名长度不超过 16

  • 每个字段都必须有 NOT NULL DEFAULT '', 如果是 int 则 default 0

  • 主键必须为 int/bigint 类型

  • 如果是 int 类型,且不会存负数,则标记为 UNSIGNED INT

  • 如果 int 类型是 10 以下的几个可枚举值,则使用 TINYINT 类型

  • varchar 长度小于 3000

  • text 字段个数不超过 3 个

  • 每个字段增加 COMMENT 注释,说明字段用途

  • 索引不能有重复

  • 索引个数不能大于 5 个(包括主键)

  • 索引字段必须为 not null,并且有 default 值

  • 请不要使用 MySQL 保留字

以上虽然是针对 MySQL 的,但基本上 Postgres 也是适用的。

代码组织

看看 pkgsite 项目的目录结构:

  • cmd:该目录几乎是 Go 圈约定俗成的,是 Go 官方以及开源界推荐的方式,用于存放 main.main。它包含 4 个子目录,也就是项目可以生成 4 个可执行文件:

    1. fronted:对应前文介绍的 Frontend 组件;

    2. woker:对应前文介绍的 Worker 组件;

    3. prober:探测器,定期探测 frontend,并导出 frontend 的度量指标,以便监控报警和性能追踪;

    4. teeproxy:用于处理 godoc.org 上的链接跳转到 pkg.go.dev;

  • internal:除了 cmd 中部分的 Go 代码,其他代码全部在该目录下。目前开源项目习惯包含一个 pkg 包,将一些通用的代码放在该包下。我们知道 internal 包,对其他项目是不可见的。所以,该项目任何包,其他项目都没法直接使用。(上面省略了很多子包)

其他目录说明见上面对应的注释。

关于 main.main 放在哪的问题

上面说,main.main 放在 cmd 目录下几乎是约定俗成的,但针对这个问题还是需要进一步说明一下,毕竟这不是标准或规范。

那关于 main.main,即包含 main 包 和 main 函数的文件(一般是 main.go)放在哪里,目前一般有两种做法:

1)放在项目根目录下。这样放有一个好处,那就是可以方便的通过 go get 进行安装。比如 github.com/polaris1119/golangclub ,按这样的方式安装:

成功后在 $GOBIN(未设置时取 $GOPATH[0]/bin )目录下会找到 golangclub 可执行文件。但如果你的项目不止一个可执行文件,也就是会存在多个 main.go,这种方式显然没法满足需求。

目前有一些开源项目是这么做的,比如 cobra 生成的框架也是采用的这种方式。

2)创建一个 cmd 目录,专门放置 main.main,有些项目可能会直接将 main.go 放在 cmd 下,但这又回到了上面的方式,而且还没上面的方式方便。一般建议项目存在多个可执行文件时,在 cmd 下创建对应的目录。因为 pkgsite 存在多个可执行文件,因此采用了这种方式。像知名的 Kubernetes 也是采用的这种方式。对于这种方式,通过 go get 可以这样安装:

这样会将项目所有的可执行文件都生成,你也可以指定生成某一个:

本地搭建 pkgsite

为了更好了解这个项目,我们尝试在本地搭建一个。这里假定你在本地安装好了 postgres,同时设置了 postgres 账号的密码为 123456,并执行了上文提到的迁移程序,创建好了需要的数据表。

本地搭建可以基于 Docker,本文采用非 Docker 的方式。

Frontend

最简单的启动 Frontend 的方式是直接执行如下命令:

发现报错,提示 postgres 需要密码。

我们先不处理这个问题,看看 frontend 程序有无 flag 可以设置。

输出如下:

不一一解释,英文说明很清楚。这里着重看 -direct_proxy-proxy_url,通过 direct_proxy 似乎可以绕开 postgres,但由于默认的 proxy(proxy.golang.org)国内不可访问,因此同时设置一个 proxy:

输出如下:

打开浏览器访问 localhost:8080:

图片

一切似乎很顺利。打开某个库,第一次稍微慢点,之后很快,从控制台输出的日志也能看到。

但访问标准库,如 database/sql,却报错:(同时页面显示 500)

很显然,在去获取 Go 仓库信息时,因为 go.googlesource.com 在国内无法访问导致错误。然而我科学上网后并没有解决问题:

我们先不细究这个问题。用上数据库试试。

现在解决数据库密码的问题。从启动时输出的 config 可以猜到,数据库信息在 config 中。前文提到,pkgsite 并没有用到配置文件,所有的配置通过环境变量来设置。

在 internal/config/config.go 文件中找到了这行代码:

因此这样启动 Frontend:

这回成功了,访问 localhost:8080 也正常显示了。然而查看包都返回 404,包括标准库和第三方包。

图片

前文讲解过,pkgsite 就是这么设计的,数据库现在是空的,需要通过 Worker 往里面填充数据。

Worker

同样的采用最简单的方式运行:

  • 注意上面 postgres 密码的问题

启动后,发现监听 8000 端口:

看看长什么样:

图片

顶部的一些链接,都是 google cloud 的,本地没有意义。

根据前面讲解,Worker 数据最终是从 Module Index 和 Module Proxy 来的,而且 Worker 不会自己触发,必须外界调用它提供的接口。上面界面中可以看到,通过点击上面的几个按钮可以执行拉取:

  • Enqueue From Module Index:从 Module Index 入队列以便处理;

  • Requeue Failed Versions:对失败的版本重入队列;

  • Reprocess Versions:重新处理;

  • Populate Standard Library:填充标准库数据;

很惨的是,Enqueue From Module Index 和 Populate Standard Library 都失败。

图片

吐槽一句:在国内做技术真难!

除了这种方式,还有一种手动获取某个版本的办法,比如在浏览器请求 http://localhost:8000/fetch/github.com/gin-gonic/gin/@v/v1.6.3,发现页面报 500,终端出现如下错误:

这是通过 proxy.golang.org 获取包模块信息。针对 Frontend,我们可以修改 proxy,Worker 可以修改吗?

可惜的是,Worker 并没有提供相关 flag 可以修改,但在 config.go 中发现了环境变量:GO_MODULE_PROXY_URL 和 GO_MODULE_INDEX_URL,默认分别是:https://proxy.golang.org 和 https://index.golang.org/index。proxy 我们可以设置为国内的 https://goproxy.cn,index 没有国内的可用。但对于手动获取某一个版本,设置 proxy 即可。通过如下方式重启 Worker:

再次访问 http://localhost:8000/fetch/github.com/gin-gonic/gin/@v/v1.6.3,显示成功:

回过头看看 http://localhost:8080/github.com/gin-gonic/gin,发现正常显示了,数据库中也有相应的数据了。但这种方式一次只能搞定一个包的一个版本。

福利:国内访问 pkg.go.dev 比较慢,有时甚至无法访问,因此我搭建了一个镜像:https://pkg.golangclub.com。

源码研究

通过上面的分析我们知道,Frontend 和 Worker 都是 HTTP 服务,因此先分析下这块是怎么实现的。

基于 net/http 构建

pkgsite 没有使用 Gin、Echo 之类的 Web 框架,而是基于 net/http 构建。Frontend 和 Worker 在这块的代码类似,这里以 Frontend 的代码为例。先看看 main.main 的代码(去除了部分认为不重要的代码),加上了注释。

先附一张调用图,再看代码:

图片
  • 这里用到了 OpenCensus 进行统计跟踪,这块内容本文不展开,有机会后面专门讲解

上面代码我们从后往前看:http.ListenAndServe 的第二个参数是一个 http.Handler,因此 mw(router) 必然返回的是一个 http.Handler。通常我们基于 net/http 创建 HTTP 服务,是这么做的:

之所以第二个参数传递 nil,是因为我们使用 http 包默认的 Handler。而该项目传递了 Handler,说明没有使用默认的。通常为了方便扩展,会实现自己的 Handler,这里主要是为了集成 OpenCensus。

我们看看 pkgsite 项目中间件定义:

  • 中间件是一个接收 http.Handler 类型参数,同时返回 http.Handler 类型的函数;

  • Chain 将多个中间件串起来并返回一个新的中间件;

  • h = middlewares[len(middlewares)-1-i](h "len(middlewares)-1-i") 做到了 Chain(m1, m2)(handler) = m1(m2(handler)),做到了后进先出;

看一个具体的中间件 Quota:

  • pkg.go.dev 采用了基于 IP 的方式限流;

  • 中间件可以在执行 h.ServeHTTP 方法前后加上一些逻辑,也可以在其之后加上一些逻辑;

  • 通过 http.HandleFunc 转换一个函数为 http.Handler;

  • 如果中间件比较复杂,可以自定义类型,实现 http.Handler,即实现其 ServeHTTP 方法,如 RequestLog 中间件;

接着看看 Router,根据要求,它应该是一个 http.Handler。

http.Handler 是一个接口,Router 应该实现它,但它却内嵌了一个 http.Handler?原来通过这种方式,Router 不需要显示实现 http.Handler 接口的方法 ServeHTTP,因为内嵌,默认就实现了 http.Handler 接口。

所以关键在于实例化 Router:

原来通过 http.NewServeMux() 得到一个实现了 http.Handler 接口的实例,然后赋值给 Router 的内嵌字段 http.Handler (忽略 ochttp,它属于 OpenCensus)。

最后是 server.Install(router.Handle, cacheClient),安装具体的路由,其实就是调用 router.Handle 方法。

最简代码实现

为了你更好的理解,这里用尽可能少的代码实现上面的主要功能:

代码放在 https://github.com/polaris1119/testpkg,go run main.go 运行后,打开浏览器访问 localhost:2020,终端有如下输出:

符合预期。

具体库如何获取数据

问:在 pkg.go.dev 首页上的 Popular Packages 和 Featured Packages 是如何实现的?

图片

一看代码,我惊住了:竟然是 HTML 中写死的~

pkgsite 如何区分标准库和第三方库的?具体库的信息都是由 handle("/", detailHandler) 处理的,具体是 internal/frontend/details.go 文件中的 serveDetails 方法:

进一步查看 stdlib.Contains 函数:

第三方库和标准库的区别就是包路径中是否包含句点(.)。然而获取他们信息时,读取的库是一样的。

具体看看请求 github.com/gin-gonic/gin 这个包都进行了什么处理?

图片

继续吐槽:

  • 默认访问某个包时,tab 必然为空,竟然做了前面一大堆事情才判断 tab,进行重定向。这里不应该要么提前重定向,要么给 tab 默认值,不进行重定向吗?

  • 包的组织比较随意,也没有使用 MVC 模式,代码层次、可读性较差;

有一个好点的设计值得提一下:

  • DataSource 接口。支持从 proxy 和 postgres 获取数据。

Worker 核心逻辑

Frontend 页面展示,相对逻辑较简单,基本是查库、展示。而 Worker 涉及到写库,本应该也较简单,但这里涉及到 Module 的一些知识,我们借此了解下。

上面介绍了,Worker 会循环从 Module Index 发现新模块并写入队列,然后从 Module Proxy 获取模块的具体内容。

  • index.golang.org:一个索引,用于为 proxy.golang.org 提供可用的新模块版本的摘要。可以在 https://index.golang.org/index 上查看摘要。摘要是以新行分隔的 JSON 形式提供,包括了模块路径(Path),模块版本(Version)以及 proxy.golang.org 首次缓存它的时间(Timestamp)。该列表按时间升序排序。支持两个可选参数:

    1. since:返回列表中模块版本的最早允许时间戳(RFC3339 格式)。默认是 index 开始的时间,例如 https://index.golang.org/index?since=2019-04-10T19:08:52.997264Z

    2. limit:返回列表的最大长度。默认值 = 2000,最大值 = 2000,例如 https://index.golang.org/index?limit=10

返回的内容示例:

我们看看 pkgsite 是怎么从 index.golang.org 获取数据的。

因为 Worker 也是一个 HTTP 服务,main.main 中的代码和 Frontend 类似,我们直接找到拉取 index 的地方:

这是一个由 Cloud Scheduler 调用的端点(endpoint),在 Worker 的后台界面的 “Enqueue From Module Index” 也是调用的这个接口。处理逻辑是:

  • 从 module_version_states 表中根据 index_timestamp 字段降序,拿到最大的一个时间戳,作为 since 参数的值;

  • 使用 since 和 limit 参数请求 index.golang.org/index,获取新模块;

  • 将获取的数据写入 module_version_states 表;

  • 将获取的数据放入队列,方便获取包的具体信息;

本地运行时(非 GAE 上),队列使用的内存队列(满足定义的 Queue 接口),具体使用的是 Channel,默认容量是 1000:

因此在入队列后,process 这个处理程序会进行处理:

这里获取具体包信息时,开启一个 goroutine 进行处理,为了避免 goroutine 数量不可控,Worker 提供了一个 flag,用于控制 goroutine 的数量,默认是 10,这里 q.sem 就是用于控制 goroutine 数量的。

之后就是通过 Module Proxy 获取包信息并进行处理。这块涉及模块代理协议[38]的内容,有兴趣的可以自己阅读源码了解,同时参考一个开源模块代理实现:https://github.com/goproxy/goproxy.cn。

标准库是如何获取的

讲到这里,还有一个问题没解决,那就是标准库,它并不会走上面 Worker 提到的处理逻辑。

这用于获取标准库版本,需要手动执行,而且 Go 每发布一个版本,应该执行一次。请求这个接口后,会通过 git 获取 Go 仓库信息,这个仓库不是 GitHub 上的,而是 https://go.googlesource.com/go,上面提到了,国内访问不了,不过可以改代码换成 https://github.com/golang/go。

标准库具体的处理逻辑:

  • 通过 https://github.com/go-git/go-git 这个 Git 操作库,获取 Go 目前所有的版本信息;

  • 针对每一个版本,循环交由队列处理,这里和第三方库是一样的;

  • 之后的处理流程和第三库类似,只是获取数据时的来源不同;

注意:由于目前 Go 版本较多,这个过程会比较耗时,甚至会有失败的情况。

依然吐槽

除了以上还有其他接口需要手动执行,如构建搜索文档等。细究代码后,再一次发现 Worker 设计的不合理。明明是一个后台任务处理器,非得依赖第三方定时任务来触发,有些还必须手动触发。

总结

整个项目的分析就差不多结束了。虽然 internal 下面的包一堆,毕竟业务不复杂。如果你对其中的细节感兴趣,可以跟着代码仔细研究。可以借助 Goland 这样的 IDE 进行调试,跟踪关键接口是如何执行的,这样有利于快速了解项目的整体结构。

文章较长,希望看到这里你能有所收获!没有开打赏,给个转发或在看或留言就是对我最大的支持!没有关注的关注下!

图片

参考资料

[1]

Go Module Index: https://index.golang.org

[2]

Go Module Mirror: https://proxy.golang.org

[3]

说明: https://go.dev/about#adding-a-package

[4]

Go Module Index: https://index.golang.org/index

[5]

模块代理协议(Module Proxy Protocol): https://docs.studygolang.com/cmd/go/#hdr-Module_proxy_protocol

[6]

#36811: https://github.com/golang/go/issues/36811

[7]

#37002: https://github.com/golang/go/issues/37002

[8]

#37106: https://github.com/golang/go/issues/37106

[9]

Google Cloud Tasks: https://cloud.google.com/tasks

[10]

Google Cloud Scheduler: https://cloud.google.com/scheduler

[11]

Liquibase: https://www.liquibase.org/

[12]

migrate: https://github.com/golang-migrate/migrate

[13]

PostgreSQL: https://github.com/golang-migrate/migrate/blob/master/database/postgres

[14]

Redshift: https://github.com/golang-migrate/migrate/blob/master/database/redshift

[15]

Ql: https://github.com/golang-migrate/migrate/blob/master/database/ql

[16]

Cassandra: https://github.com/golang-migrate/migrate/blob/master/database/cassandra

[17]

SQLite: https://github.com/golang-migrate/migrate/blob/master/database/sqlite3

[18]

todo #165: https://github.com/mattes/migrate/issues/165

[19]

MySQL/ MariaDB: https://github.com/golang-migrate/migrate/blob/master/database/mysql

[20]

Neo4j: https://github.com/golang-migrate/migrate/blob/master/database/neo4j

[21]

MongoDB: https://github.com/golang-migrate/migrate/blob/master/database/mongodb

[22]

CrateDB: https://github.com/golang-migrate/migrate/blob/master/database/crate

[23]

todo #170: https://github.com/mattes/migrate/issues/170

[24]

Shell: https://github.com/golang-migrate/migrate/blob/master/database/shell

[25]

todo #171: https://github.com/mattes/migrate/issues/171

[26]

Google Cloud Spanner: https://github.com/golang-migrate/migrate/blob/master/database/spanner

[27]

CockroachDB: https://github.com/golang-migrate/migrate/blob/master/database/cockroachdb

[28]

ClickHouse: https://github.com/golang-migrate/migrate/blob/master/database/clickhouse

[29]

Firebird: https://github.com/golang-migrate/migrate/blob/master/database/firebird

[30]

MS SQL Server: https://github.com/golang-migrate/migrate/blob/master/database/sqlserver

[31]

Filesystem: https://github.com/golang-migrate/migrate/blob/master/source/file

[32]

Go-Bindata: https://github.com/golang-migrate/migrate/blob/master/source/go_bindata

[33]

Github: https://github.com/golang-migrate/migrate/blob/master/source/github

[34]

Github Enterprise: https://github.com/golang-migrate/migrate/blob/master/source/github_ee

[35]

Gitlab: https://github.com/golang-migrate/migrate/blob/master/source/gitlab

[36]

AWS S3: https://github.com/golang-migrate/migrate/blob/master/source/aws_s3

[37]

Google Cloud Storage: https://github.com/golang-migrate/migrate/blob/master/source/google_cloud_storage

[38]

模块代理协议: https://docs.studygolang.com/cmd/go/#hdr-Module_proxy_protocol


万字长文解读 pkg.go.dev 的设计和实现的评论 (共 条)

分享到微博请遵守国家法律