当前位置:   article > 正文

从源代码级别看懂MinIO对象存储网关的实现

从源代码级别看懂MinIO对象存储网关的实现

我在 《MinIO对象存储的网关架构设计》一文中介绍了MinIO的网关架构设计,它的整体架构如下图所示:

MinIO对象存储的网关架构设计

从架构图可以很清楚的看到MinIO网关的分层结构,那么这一篇我就从MinIO网关的具体代码分析它是如何实现的。

网关启动

MinIO内部已经实现了GCS、S3、NAS等几个网关,支持的网关列表如下:

  1.   nas    Network-attached storage (NAS)
  2.   azure  Microsoft Azure Blob Storage
  3.   s3     Amazon Simple Storage Service (S3)
  4.   hdfs   Hadoop Distributed File System (HDFS)
  5.   gcs    Google Cloud Storage

假如要启动一个NAS网关,可以使用如下命令:

minio gateway nas PATH

以上命令中的PATH是一个NAS挂载点路径,当然你也可以使用本地路径。假如我的NAS挂载点路径为/tmp/nas/ ,那么我通过如下命令就可以启动一个NAS网关。

 minio gateway nas /tmp/nas/

小提示:因为MinIO需要用户名和密码,所以在启动网关之前一定要设置,通过如下命令即可:
export MINIO_ACCESS_KEY=accesskey
export MINIO_SECRET_KEY=secretkey

其实就是设置MINIO_ACCESS_KEY和MINIO_SECRET_KEY环境变量,可以改成自己想要的用户名和密码。

启动后你可以像使用MinIO Server一样使用网关。

网关启动代码分析

MinIO的命令行启动只有2个命令,一个是server、一个是gateway,分别用于启动服务和网关,而整个MinIO的启动是从minio/main.go文件(假设存放MinIO源代码的根目录是minio)开始的。

minio/main.go

  1. package main
  2. import (
  3.    "os"
  4.    minio "github.com/minio/minio/cmd"
  5.    // Import gateway
  6.    _ "github.com/minio/minio/cmd/gateway"
  7. )
  8. func main() {
  9.    minio.Main(os.Args)
  10. }

整个源代码文件比较简单,主要是把命令行输入的参数交给minio.Main这个函数处理。这里需要注意的是github.com/minio/minio/cmd/gateway包的导入,它会调用github.com/minio/minio/cmd/gateway包的init函数,初始化支持的网关,后面会详细讲到。
继续跟踪minio.Main函数,它的代码实现在minio/cmd/main.go文件中,核心代码如下:

minio/cmd/main.go

  1. func newApp(name string) *cli.App {
  2.    // Collection of minio commands currently supported are.
  3.    commands := []cli.Command{}
  4.    //省略无关代码
  5.    // registerCommand registers a cli command.
  6.    registerCommand := func(command cli.Command) {
  7.       commands = append(commands, command)
  8.    }
  9.     //省略无关代码
  10.    // Register all commands.
  11.    registerCommand(serverCmd)
  12.    registerCommand(gatewayCmd)//注册的网关命令。
  13.    app := cli.NewApp()
  14.    app.Name = name
  15.    //省略无关代码
  16.    app.Commands = commands
  17.    app.CustomAppHelpTemplate = minioHelpTemplate
  18.    //省略无关代码
  19.    return app
  20. }
  21. // Main main for minio server.
  22. func Main(args []string) {
  23.    // Set the minio app name.
  24.    appName := filepath.Base(args[0])
  25.    // Run the app - exit on error.
  26.    if err := newApp(appName).Run(args); err != nil {
  27.       os.Exit(1)
  28.    }
  29. }

以上源代码我省略了很多和网关无关的,便于分析查看。从以上源代码可以清晰的看到MinIO是通过registerCommand函数注册了server和gateway这两个命令:registerCommand(serverCmd)registerCommand(gatewayCmd)。这样当你在终端输入minio回车的时候就可以看到server和gateway这两个命令的提示。

  1. ➜  minio                      
  2. NAME:
  3.   minio - High Performance Object Storage
  4. USAGE:
  5.   minio [FLAGS] COMMAND [ARGS...]
  6. COMMANDS:
  7.   server   start object storage server
  8.   gateway  start object storage gateway

到了这里,相信你已经清楚网关整体的启动流程了,那么如何启动一个具体的网关呢,比如NAS,这就要具体分析刚刚源代码中注册的gatewayCmd命令了。

gatewayCmd命令分析

gatewayCmd是一个定义在cmd包中的全局变量,它的源代码在minio/cmd/gateway-main.go文件中:

minio/cmd/gateway-main.go

  1. var (
  2.    gatewayCmd = cli.Command{
  3.       Name:            "gateway",
  4.       Usage:           "start object storage gateway",
  5.       Flags:           append(ServerFlags, GlobalFlags...),
  6.       HideHelpCommand: true,
  7.    }
  8. )

gatewayCmd是一个比较顶层的命令,它下面还有很多子命令,比如nas、gcs等,一个子命令代表一个网关,那么这些子命令是如何注册作为gatewayCmd的子命令的呢?我以比较简单的NAS网关为例分析nas子命令的注册逻辑。

NAS网关子命令分析

还记得「网关启动代码分析」小节中留的github.com/minio/minio/cmd/gateway包导入使用init函数初始化的提示吧?现在就详细分析下,看MinIO是如何通过包的init函数实现nas子命令注册的。

minio/cmd/gateway/gateway.go

  1. package gateway
  2. import (
  3.    // Import all gateways please keep the order
  4.    // NAS
  5.    _ "github.com/minio/minio/cmd/gateway/nas"
  6.    // Azure
  7.    _ "github.com/minio/minio/cmd/gateway/azure"
  8.    // S3
  9.    _ "github.com/minio/minio/cmd/gateway/s3"
  10.    // HDFS
  11.    _ "github.com/minio/minio/cmd/gateway/hdfs"
  12.    // GCS (use only if you must, GCS already supports S3 API)
  13.    _ "github.com/minio/minio/cmd/gateway/gcs"
  14.    // gateway functionality is frozen, no new gateways are being implemented
  15.    // or considered for upstream inclusion at this point in time. if needed
  16.    // please keep a fork of the project.
  17. )

github.com/minio/minio/cmd/gateway包的源代码实现如上所示,没有啥东西,还是一堆包的导入,这次的每个包就代表一个网关的具体实现,比如NAS网关的实现在github.com/minio/minio/cmd/gateway/nas包中,它的命令注册源代码如下:


minio/cmd/gateway/nas/gateway-nas.go

  1. func init() {
  2.    const nasGatewayTemplate = `NAME:`//省略很多字符串
  3.    minio.RegisterGatewayCommand(cli.Command{
  4.       Name:               minio.NASBackendGateway,
  5.       Usage:              "Network-attached storage (NAS)",
  6.       Action:             nasGatewayMain,
  7.       CustomHelpTemplate: nasGatewayTemplate,
  8.       HideHelpCommand:    true,
  9.    })
  10. }

看到关键了吧?就是这个RegisterGatewayCommand函数,通过它把nas这个子命令注册给gateway这个父命令。再看下RegisterGatewayCommand函数的具体实现就可以证明了:


minio/cmd/gateway-main.go

  1. // RegisterGatewayCommand registers a new command for gateway.
  2. func RegisterGatewayCommand(cmd cli.Command) error {
  3.    cmd.Flags = append(append(cmd.Flags, ServerFlags...), GlobalFlags...)
  4.    gatewayCmd.Subcommands = append(gatewayCmd.Subcommands, cmd)
  5.    return nil
  6. }

看以上RegisterGatewayCommand函数 的源代码,把传入的命令作为gatewayCmd的子命令完成注册,而gatewayCmd就是我刚刚讲过的cmd包中的全局变量。

NAS网关启动分析

好了,nas子命令也注册好了,那么如果在终端中输入如下命令,程序会怎么运行呢?

 minio gateway nas /tmp/nas/

这就需要分析刚刚注册的nas子命令了,我再把注册nas子命令这段代码粘贴出来便于分析。


minio/cmd/gateway/nas/gateway-nas.go

  1. func init() {
  2.    const nasGatewayTemplate = `NAME:`//省略很多字符串
  3.    minio.RegisterGatewayCommand(cli.Command{
  4.       Name:               minio.NASBackendGateway,
  5.       Usage:              "Network-attached storage (NAS)",
  6.       Action:             nasGatewayMain,
  7.       CustomHelpTemplate: nasGatewayTemplate,
  8.       HideHelpCommand:    true,
  9.    })
  10. }

仔细看如上源代码,看到cli.Command这个结构体的Action字段了吗?他就是一个命令在执行时运行的函数,也就是命令的处理逻辑都在这个函数中,对应nas子命令就是nasGatewayMain这个函数,现在只需要分析nasGatewayMain函数的源代码实现即可分析NAS网关的启动逻辑。


minio/cmd/gateway/nas/gateway-nas.go

  1. // Handler for 'minio gateway nas' command line.
  2. func nasGatewayMain(ctx *cli.Context) {
  3.    // Validate gateway arguments.
  4.    if !ctx.Args().Present() || ctx.Args().First() == "help" {
  5.       cli.ShowCommandHelpAndExit(ctx, minio.NASBackendGateway, 1)
  6.    }
  7.    minio.StartGateway(ctx, &NAS{ctx.Args().First()})
  8. }

以上源代码的核心在于StartGateway函数,它可以根据参数启动相应的网关。

minio/cmd/gateway-main.go

  1. // StartGateway - handler for 'minio gateway <name>'.
  2. func StartGateway(ctx *cli.Context, gw Gateway) {
  3.   //为了分析过程,我先省略这个函数的逻辑
  4. }

StartGateway函数有两个参数,一个ctx是命令行的信息,比如你在终端输入的命令、参数以及Flag等,第二个Gateway就是你要启动哪个网关,这是一个接口:

minio/cmd/gateway-interface.go

  1. // Gateway represents a gateway backend.
  2. type Gateway interface {
  3.    // Name returns the unique name of the gateway.
  4.    Name() string
  5.    // NewGatewayLayer returns a new  ObjectLayer.
  6.    NewGatewayLayer(creds auth.Credentials) (ObjectLayer, error)
  7.    // Returns true if gateway is ready for production.
  8.    Production() bool
  9. }

这个Gateway接口里关键的方法就是NewGatewayLayer,通过它可以获得一个ObjectLayer来操作不同网关的文件或者对象数据(参考我画的网关架构图ObjectLayer那一层)。

NAS网关实现Gateway接口

Gateway是一个通用的网关操作接口,所以每个网关都有自己的实现,NAS网关也不例外,它的实现就是NAS这个结构体。

minio/cmd/gateway/nas/gateway-nas.go

  1. // NAS implements Gateway.
  2. type NAS struct {
  3.    path string
  4. }
  5. // Name implements Gateway interface.
  6. func (g *NAS) Name() string {
  7.    return minio.NASBackendGateway
  8. }
  9. // NewGatewayLayer returns nas gatewaylayer.
  10. func (g *NAS) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error) {
  11.    var err error
  12.    newObject, err := minio.NewFSObjectLayer(g.path)
  13.    if err != nil {
  14.       return nil, err
  15.    }
  16.    return &nasObjects{newObject}, nil
  17. }
  18. // Production - nas gateway is production ready.
  19. func (g *NAS) Production() bool {
  20.    return true
  21. }

从以上NAS结构体实现Gateway接口的源代码可以看到,NAS本质上对于对象的操作使用的是MinIO自带的单点模式下的文件对象操作结构体FSObjects,这是很合理的,因为NAS操作的就是一个文件夹路径,这是MinIO的单点模式是一样的。

小提示:MinIO server启动有两种模式,一个是单点模式,一种是纠删码模式,其中单点模式就是只传了一个endpoint给minio,使用的是文件系统的操作方式,更详细的可以研究FSObjects的源代码实现。

最终网关启动

好了,NAS网关的具体实现也有了,生成的ObjectLayer也有了,那么就剩下最终网关的启动了,又回到了刚刚的nasGatewayMain函数,这部分函数的代码对所有网关都是一样的,因为它是针对Gateway接口的编程。

minio/cmd/gateway-main.go

  1. // StartGateway - handler for 'minio gateway <name>'.
  2. func StartGateway(ctx *cli.Context, gw Gateway) {
  3.     //省略无关很多紧要的代码
  4.    // Handle common command args.
  5.    handleCommonCmdArgs(ctx)
  6.    // Handle gateway specific env
  7.    gatewayHandleEnvVars()
  8.    // Set when gateway is enabled
  9.    globalIsGateway = true
  10.    // Initialize router. `SkipClean(true)` stops gorilla/mux from
  11.    // normalizing URL path minio/minio#3256
  12.    // avoid URL path encoding minio/minio#8950
  13.    router := mux.NewRouter().SkipClean(true).UseEncodedPath()
  14.    // operations such as profiling, server info etc.
  15.    registerAdminRouter(router, enableConfigOps, enableIAMOps)
  16.    // Add API router.
  17.    registerAPIRouter(router)
  18.    // Use all the middlewares
  19.    router.Use(registerMiddlewares)
  20.    newObject, err := gw.NewGatewayLayer(globalActiveCred)
  21.    newObject = NewGatewayLayerWithLocker(newObject)
  22.    // Once endpoints are finalized, initialize the new object api in safe mode.
  23.    globalObjLayerMutex.Lock()
  24.    globalObjectAPI = newObject //关键代码,使用网关接口生成的ObjectLayer
  25.    globalObjLayerMutex.Unlock()
  26.    handleSignals()
  27. }

以上是nasGatewayMain函数的源代码,我已经省略了很多无关紧要的代码,便于分析网关的启动。nasGatewayMain函数整体的代码逻辑和启动一个MinIO server很像,只不过全局的处理对象存储的globalObjectAPI换成了网关返回的ObjectLayer,这样通过API接口对对象的操作才会转换为底层真实网关的操作。
该函数其他的代码主要注册路由,比如前台的S3兼容API以及后台的Admin管理API等,并且通过globalIsGateway = true把这次启动标记为是作为网关启动的,便于MinIO内部其他代码逻辑的处理。

小结

这篇文章我主要以比较容易的NAS网关为例分析整个网关的启动,其他网关大同小异,主要是对于接口Gateway的具体代码实现不一样,其他都是一样的,所以当我们为MinIO新增一个网关时,只需要实现Gateway接口,然后通过init函数注册一个该网关的子命令给gatewayCmd即可,非常简单,这些都是得益于MinIO对网关架构的优秀设计。

更多精彩推荐

MinIO对象存储的网关架构设计

对象存储和 CDN 实现分析揭秘

聊聊越来越火的对象存储

Golang Gin 实战(十四)| 文件托管、代理百度网站、自实现API网关

Golang Gin 实战(十三)| 中间件详解看这一篇就够了

Golang Gin 实战(十二)| ProtoBuf 使用和源码分析原理实现

本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org或者网站 https://www.flysnow.org/ ,第一时间看后续精彩文章。觉得好的话,请顺手点个赞吧。

扫码关注
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/思考机器3/article/detail/62614
推荐阅读
相关标签
  

闽ICP备14008679号