不一样的go语言-athens源码概览

前言

  上一篇文章介绍了athens私服的安装以及vgo download protocol的简要介绍。本文着重介绍go proxy sever的实现原理以及athens是如何实现的。

go get原理

  当GOPROXY没有设置的时候,通过-x参数,可以看到go get获取module的详细过程。

[eventer@localhost]# go get -x github.com/gin-gonic/gin@v1.3.0

  对于git来说,go依赖于git命令,通过git命令的组合获取module库的元数据及各版本源码包。而其中的第一步在于向源码仓库获取module的元数据。

[eventer@localhost]# curl -sSL 'https://swtch.com/testmod?go-get=1'
<!DOCTYPE html>
<meta name="go-import" content="swtch.com/testmod mod https://swtch.com/testmodproxy">
Nothing to see here.

  go cli根据返回的元数据从指定的地址获取module,说白了就是在本地执行git的各个命令,跟大家平时从源码库拿代码的过程差不多一样。

  当GOPROXY被设置的时候,按照《Defining Go Modules》一文中关于proxy server的定义,情况发生了一些变化,而这也正是athens所要实现的内容。

athens概述&流程

  按照vgo download protocol中的定义,go proxy server是一个高效、可用、安全,且遵循module格式标准、下载协议、本地缓存以及支持按需下载的代理服务。显然,这是一个构件系统的定义,而athens也正是朝着这个目标实现的。

  但由于go get与go mod命令的设定及其主动获取这些特征,使得其与java阵营的nexus、jfrog不同。java的库是由开发者主动deploy到公有仓库或私有仓库中,程序构建的时候再根据pom或gradle配置文件的声明从仓库获取指定的package。而go则省略了第一步,直接在构建的时候由go get或go mod根据go.mod文件的声明从源码库中获取module。因而这就意味着athens首先必须实现从当前流行的源码库中获取公开、私有的module,比如github、gitlab、bitbuckt;又要考虑如何从私有的源码库中获取module。

  所以显而易见,athens需要实现的功能列表如下:

功能项 性质 功能说明
下载协议 必需 4个必需接口,2个可选接口
本地存储 必需 module存储方式,本地磁盘or内存。athens都支持, 可配置
云端存储 增强 module存储方式,支持gcp、minio、mongo、s3、AzureBlob,可配置
公仓用户认证 必需 github token
私仓用户认证 必需 SVN、Bazaar、Bitbucket、github、gitlab
版本控制 增强 提供方案控制哪些module的哪些版本使用代理、是否可用等
日志跟踪 增强 使用opencensus实现
并发控制 增强 多个并发请求同一个module,处理第一个请求,后续请求等待并获取结果
健康检测 增强 实现服务状态接口
管理功能 增加 实现查询module若干接口

  那么按照预想,go get指令的流程如下:

image

  而再次获取同一个版本的module时,流程如下:

image

  通过代理取包的过程其实也很简单。athens按照约定,提供了4个或6个接口供go get指令使用。当GOPROXY被设置时,go get切换至新流程,如下(goproxy.io是proxy server):

  1. https://goproxy.io/github.com/gin-gonic/gin/@v/list
  2. https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.info
  3. https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.mod
  4. https://goproxy.io/github.com/gin-gonic/gin/@v/v1.3.0.zip

  这组协议对应至本地文件系统的一组目录$GOPATH/pkg/mod/cache/download,这里保存了对应上述4个接口的文件,这4个文件内容可以到这个目录下自行查看,它们的格式即是协议描述的内容。

接口实现

  athens本身是一个web服务,采用gorilla框架实现。main.go位于cmd/proxy包下,关键代码读取配置文件,然后根据配置文件参数初始化程序。

//读入配置文件
conf, err := config.Load(*configFile)
if err != nil {
    log.Fatalf("could not load config file: %v", err)
}

//根据配置初始化程序
handler, err := actions.App(conf)
if err != nil {
    log.Fatal(err)
}

  在app.go文件中,配置了storage、github token、NETRCPath、HGRCPath、log、FilterFile、路由注册。auth.go中的代码将NETRCPath、HGRCPath声明的文件内容转写到当前用户home目录下的预定位置;而关键的路由注册,则由下面的代码完成,调用的是app_proxy.go中的addProxyRoutes方法。

if err := addProxyRoutes(
    proxyRouter,
    store,
    lggr,
    conf,
); err != nil {
    err = fmt.Errorf("error adding proxy routes (%s)", err)
    return nil, err
}

  addProxyRoutes方法注册了的路由如下:

路由 说明
/ 首页
/healthz 健康检测
/readyz -
/version athens版本
/catalog 所有module列表

  在这之后,定义了GoGetFetter用于处理module的下载、upstream vcs监听器、并发控制器、vgo download protocol协议实现。

handlerOpts := &download.HandlerOpts{Protocol: dp, Logger: l}
//RegisterHanlders方法注册了list、version.info、version.mod、version.zip这4个接口的路由
download.RegisterHandlers(r, handlerOpts)

  athens包说明

用途 描述
pkg/config 配置文件对应实体 存取配置文件参数
pkg/download/ vgo download protocol协议实现 核心入口
pkg/errors errors统一定义及堆栈跟踪 良好的错误设计,可以快速定位到出错的包、方法
pkg/log logrus日志框架集成 -
pkg/middleware 中间件 module缓存、日志、请求验证、module获取策略
pkg/module module获取策略、仓库源码获取、zip获取实现 -
pkg/observ 日志及统计数据输出 datadog
pkg/paths module path解析工具 -
pkg/stash module获取与存储包装类及module并发请求控制 -
pkg/storage 存储实现 包括内存、本地磁盘、数据库(mongodb)、文件系统(afero抽象文件系统)、云存储(s3,gcp,minio)等

  module获取策略

定义 说明
module.Exclude 排除 忽略对指定包的请求
module.Include - 不走代理,按常规模式获取包,本地私仓使用此配置
module.Direct - 走代理

  获取module流程图

获取module流程图


  在athens的实现中,各个包之间的调用关系如下:

包调用关系


  GET baseURL/module/@v/list时序图

GET baseURL/module/@v/list时序图


  GET baseURL/module/@v/version.info时序图

GET baseURL/module/@v/version.info时序图


baseURL/module/@v/version.mod与baseURL/module/@v/version.zip的过程与baseURL/module/@v/version.info一致,只是调用不同的实现而已。

欢迎关注个人公众号

不一样的go语言

posted @ 2019-05-03 16:12 eventer 阅读(...) 评论(...) 编辑 收藏