Back
Featured image of post minio源码分析

minio源码分析

针对minio的源码分析

1. minio的简介

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。摘自docs.min.io

2. 源码分析

minio 启动后就是一个 http 服务器,有了这个概念我们就可以从 init hook api 三个层面来解读

2.1. 环境介绍

go:
    version: 1.15.5
system:
    macOs Big Sur 
    version: 11.1
minio 
    hash: d8d25a308fc2cae134b3ad720694edf7a3feccf5 
    date: Dec 28, 2020 
    branch: master

2.2. 代码结构

抛去一些文档和其他辅助的文件,主要代码都是在根目录的 cmd pkg 两个文件夹中,main.go 是代码函数入口

├── CONTRIBUTING.md
├── CREDITS
├── Dockerfile
├── Dockerfile.cicd
├── Dockerfile.dev
├── Dockerfile.dev.browser
├── Dockerfile.mint
├── Dockerfile.release
├── LICENSE
├── Makefile
├── NOTICE
├── README.md
├── README_zh_CN.md
├── SECURITY.md
├── VULNERABILITY_REPORT.md
├── browser
├── buildscripts
├── cmd                         ---主流程代码
├── code_of_conduct.md
├── config
├── docker-buildx.sh
├── dockerscripts
├── docs
├── gateway
├── go.mod
├── go.sum
├── main.go                     ---主函数入口
├── minio
├── minio.iml
├── minio.spec
├── minio_server
├── mint
├── pkg                         ---依赖包
├── resources
├── ruleguard.rules.go
└── staticcheck.conf

2.3. 初始化

cmd入口结构

CMD入口
CMD入口

代码主进程是从根目录的main.go的主函数进入,然后分为 gatewayserver 两种模式,其中前者根据挂载的磁盘数量来决定是单机模式还是纠偏码模式。后者根据 gateway 后面的命令参数来决定是使用什么代理模式进行,目前支持 azure gcs hdfs nas s3

2.3.1. server初始化

server 模式下,无论是 fs 单磁盘模式,还是 Erasure 模式,都会经历以下步骤(包含在cmd/server-main.go 文件中)

1. 加入DNS的Cache的停止的hook
2. 注册系统关闭的信号量
3. 设置分配多少字节进行内存采样的阀植,关闭 mutex prof,关闭统计阻塞的event统计
4. 初始化全局console日志,并作为target加入
5. 处理命令行参数
6. 处理环境变量
7. 设置分布式节点名称
8. 处理所有的帮助信息
9. 初始化以下子系统
   1.  healState 
   2.  notification 
   3.  BucketMetadata 
   4.  BucketMonitor 
   5.  ConfigSys 
   6.  IAM 
   7.  Policy
   8.  Lifecycle
   9.  BucketSSEConfig
   10. BucketObjectLock
   11. BucketQuota
   12. BucketVersioning
   13. BucketTarget
10. https 启用后的证书检查
11. 升级检查
12. 根据操作系统进程的最大内存,fd,线程数设置
13. 配置路由
    1.  分布式模式下,注册以下路由
        1.  StorageREST
        2.  PeerREST
        3.  BootstrapREST
        4.  LockREST
    2. STS 相关路由
    3. ADMIN 相关路由
    4. HealthCheck 相关路由
    5. Metrics 相关路由
    6. Web 相关路由
    7. API 相关路由
14. 注册以下hook
	// 处理未初始化object 层的重定向
	setRedirectHandler,
	// 设置 请求头 x-amz-request-id 字段.
	addCustomHeaders,
	// 添加头部安全字段例如 Content-Security-Policy.
	addSecurityHeaders,
	// 转发path style 请求到真正的主机上
	setBucketForwardingHandler,
	// 验证请求
	setRequestValidityHandler,
	// 统计
	setHTTPStatsHandler,
	// 限制请求大小
	setRequestSizeLimitHandler,
	// 限制请求头大小
	setRequestHeaderSizeLimitHandler,
	// 添加 'crossdomain.xml' 策略来处理 legacy flash clients.
	setCrossDomainPolicy,
	// 重定向一些预定义的浏览器请求到静态路由上
	setBrowserRedirectHandler,
	// 如果请求是restricted buckets 则验证
	setReservedBucketHandler,
	// 为所有浏览器请求添加cache
	setBrowserCacheControlHandler,
	// 验证所有请求流量,以便有有效期的标头
	setTimeValidityHandler,
	// 验证所有的url,以便使客户端收到不受支持的url的报错
	setIgnoreResourcesHandler,
	// 验证授权
	setAuthHandler,
	// 一些针对ssl特殊的处理
	setSSETLSHandler,
	// 筛选http头,这些标记作为meta信息保留,仅供内部使用
	filterReservedMetadata,
15. 注册最外层hook
    1.  criticalErrorHandler(处理panic)
    2.  corsHandler(处理CORS)
16. 如果是纠偏码模式
    1.  验证配置
17. 初始化 Object 层
18. 设置 deploment 的id
19. 如果是纠偏码模式
    1.  初始化自动 Heal
    2.  初始化后台 Replication
    3.  初始化后台 Transition
20. 初始化 DataCrawler
21. 初始化server
22. 如果启用缓存,初始化缓存层
23. 打印启动信息
24. 验证认证信息是否是默认认证信息,如果是,提示修改

2.3.2. gateway初始化

gateway 模式下,无论后面代理的是哪种类型的存储(azure gcs hdfs nas s3),都会经历以下步骤(包含在cmd/gateway-main.go 文件中),从下面的步骤可以看出,和 server 模式区别不是很大

1. 加入DNS的Cache的停止的hook
2. 初始化全局console日志,并作为target加入
3. 处理命令行参数
4. 处理环境变量
5. 检查ip端口是否可用
6. 根据操作系统进程的最大内存,fd,线程数设置
7.  配置路由
    1. ADMIN 相关路由
    2. HealthCheck 相关路由
    3. Metrics 相关路由
    4. Web 相关路由
    5. API 相关路由
8.  注册以下hook
	// 处理未初始化object 层的重定向
	setRedirectHandler,
	// 设置 请求头 x-amz-request-id 字段.
	addCustomHeaders,
	// 添加头部安全字段例如 Content-Security-Policy.
	addSecurityHeaders,
	// 转发path style 请求到真正的主机上
	setBucketForwardingHandler,
	// 验证请求
	setRequestValidityHandler,
	// 统计
	setHTTPStatsHandler,
	// 限制请求大小
	setRequestSizeLimitHandler,
	// 限制请求头大小
	setRequestHeaderSizeLimitHandler,
	// 添加 'crossdomain.xml' 策略来处理 legacy flash clients.
	setCrossDomainPolicy,
	// 重定向一些预定义的浏览器请求到静态路由上
	setBrowserRedirectHandler,
	// 如果请求是restricted buckets 则验证
	setReservedBucketHandler,
	// 为所有浏览器请求添加cache
	setBrowserCacheControlHandler,
	// 验证所有请求流量,以便有有效期的标头
	setTimeValidityHandler,
	// 验证所有的url,以便使客户端收到不受支持的url的报错
	setIgnoreResourcesHandler,
	// 验证授权
	setAuthHandler,
	// 一些针对ssl特殊的处理
	setSSETLSHandler,
	// 筛选http头,这些标记作为meta信息保留,仅供内部使用
	filterReservedMetadata,
9. 注册最外层hook
    1.  criticalErrorHandler(处理panic)
    2.  corsHandler(处理CORS)
10. 初始化以下子系统
   1.  healState 
   2.  notification 
   3.  BucketMetadata 
   4.  BucketMonitor 
   5.  ConfigSys 
   6.  IAM 
   7.  Policy
   8.  Lifecycle
   9.  BucketSSEConfig
   10. BucketObjectLock
   11. BucketQuota
   12. BucketVersioning
   13. BucketTarget
11. 如果开启了 IAMOps ,则初始化
12. 如果启用缓存,初始化缓存层
13. 验证object层是否支持 encryption compression
14. 打印启动信息

2.4. 子系统介绍

无论是 server 模式还是gateway模式都包含了以下子系统,然后各个api接口在自己的逻辑代码中插入响应的逻辑。

2.4.1. globalAllHealState(cmd/admin-heal-ops.go)

所有等待修复bucket的状态机,在内存中保存,主要是针对客户端请求的,状态分为以下几种

  1. healNotStartedStatus
  2. healRunningStatus
  3. healStoppedStatus
  4. healFinishedStatus

2.4.2. globalBackgroundHealState(cmd/admin-heal-ops.go)

所有为自动化恢复bucket的状态机,在内存中保存,状态分为以下几种

  1. healNotStartedStatus
  2. healRunningStatus
  3. healStoppedStatus
  4. healFinishedStatus

2.4.3. globalNotificationSys(cmd/notification.go)

初始化: 在bucket通知可以被支持的情况下,载入所有的 `EndpointServer` ,然后绑定 `bucket` , `ObjectLayer` 
使用: 在各个api尾部调用 `send` 接口,然后根据 `eventName` 和 `objectName` 拉出所有的通知target,然后发送通知
其他: 提供了一个接口 `GetBandwidthReports` 用来获取一组bucket的带宽,通过调用各个客户端的 `/bandwidth` 接口获取

2.4.4. globalBucketMetadataSys(cmd/bucket-metadata-sys.go)

初始化: 在bucket通知可以被支持的情况下,绑定 `bucket` , `ObjectLayer`
使用: 所有关于bucket的meta信息都保存在 `/buckets/{bucketName}/.metadata.bin` 的object中,meta信息内容可以查看以下结构体,调用 `getObject` 接口获取对应bucket的meta信息
其他: 载入bucket信息的时候是并发载入的,每次100个,接口为 `concurrentLoad`
type BucketMetadata struct {
	Name                        string
	Created                     time.Time
	LockEnabled                 bool // legacy not used anymore.
	PolicyConfigJSON            []byte
	NotificationConfigXML       []byte
	LifecycleConfigXML          []byte
	ObjectLockConfigXML         []byte
	VersioningConfigXML         []byte
	EncryptionConfigXML         []byte
	TaggingConfigXML            []byte
	QuotaConfigJSON             []byte
	ReplicationConfigXML        []byte
	BucketTargetsConfigJSON     []byte
	BucketTargetsConfigMetaJSON []byte

	// Unexported fields. Must be updated atomically.
	policyConfig           *policy.Policy
	notificationConfig     *event.Config
	lifecycleConfig        *lifecycle.Lifecycle
	objectLockConfig       *objectlock.Config
	versioningConfig       *versioning.Versioning
	sseConfig              *bucketsse.BucketSSEConfig
	taggingConfig          *tags.Tags
	quotaConfig            *madmin.BucketQuota
	replicationConfig      *replication.Config
	bucketTargetConfig     *madmin.BucketTargets
	bucketTargetConfigMeta map[string]string
}

2.4.5. globalBucketMonitor(cmd/config.go)

初始化: 传入一个 `chan`,初始化对象,无外部其他依赖。
使用: 通过包装包含 `throttleBandwidth` 接口的reader `NewMonitoredReader`,从而在执行 `replicateObject` 时进行统计,并通过 `GetReport` ` 接口获取

2.4.6. globalConfigSys

初始化: 绑定 `ObjectLayer`,然后按照下面优先级进行合并

	1. 命令行指定的配置文件位置 `{config_location}/minioConfigFile`
	2. home目录下 `${HOME}/.minio/config.json`
	3. 数据盘目录下 `<export_path>/.minio.sys/config/config.json`
	4. 读取object `/.minio.sys/config/config.json`
   
使用: 通过增删改接口查获取配置

2.4.7. globalIAMSys(cmd/iam.go)

初始化: 绑定 `ObjectLayer`,先拿到 `/.minio.sys/config/iam.lock`的锁,然后查看 `globalEtcdClient`是否为空, 如果不为空再进行合并
使用: 通过 `IAMSys` 提供的接口进行查询 `user` `group` `policy` 信息,然后再各个api接口中判断认证和权限
其他: 
    1. 通过 `IAMStorageAPI` 这个interface,来抽象IAM相关信息的存储,目前实现有以下两种
      	
		  1. `IAMEtcdStore`(cmd/iam-etcd-store.go)
          2. `IAMObjectStore`(cmd/IAMObjectStore.go)
   
   	2. iam结构信息如下,从图中看出
   
      	1. user和group是多对多的关系
      	2. user 和 group 都可以拥有policy

IAM
IAM

2.4.8. globalPolicySys

初始化: 构造空结构体
使用: 通过查询bucket的config信息来获取对应的配置

2.4.9. globalLifecycleSys

初始化: 构造空结构体
使用: 通过查询bucket的config信息来获取对应的配置

2.4.10. globalBucketSSEConfigSys

初始化: 构造空结构体
使用: 通过查询bucket的config信息来获取对应的配置

2.4.11. globalBucketObjectLockSys

初始化: 构造空结构体
使用: 通过查询object的meta信息来获取对应配置

2.4.12. globalBucketQuotaSys

初始化: 构造空结构体
使用: 通过查询bucket的config信息来获取对应的配置

2.4.13. globalBucketVersioningSys

初始化: 构造空结构体
使用: 通过查询bucket的config信息来获取对应的配置

2.4.14. globalBucketTargetSys

初始化: 构造空结构体
使用: 通过查询bucket的config信息来获取对应的配置

2.5. hook 介绍

纵观 minio 所有源码,hook部分是一大亮点,考虑了很多,分别从以下几个方面进行了考虑

  1. 系统层面 setMaxResources(这个不属于hook)
  2. 代码层面 setReservedBucketHandler setBrowserCacheControlHandler criticalErrorHandler
  3. web层面
    • setRedirectHandler
    • addCustomHeaders
    • setBucketForwardingHandler
    • setBrowserRedirectHandler
  4. 安全层面
    • 请求域名
      • setCrossDomainPolicy
    • 请求头
      • corsHandler
      • addSecurityHeaders
      • setRequestValidityHandler
      • setRequestHeaderSizeLimitHandler
      • setSSETLSHandler
    • 请求体
      • setRequestSizeLimitHandler
    • url
      • setIgnoreResourcesHandler
      • setAuthHandler
    • 自定义
      • filterReservedMetadata
  5. 审计层面
    • setHTTPStatsHandler

2.6. api层介绍

api层调用层级结构如下图,从图中我们可以看出,

  1. 无论是 gateway 还是 server 模式都是通过实现 ObjectAPI 这个interface来进行服务
  2. objectAPIHandlers 这一层面,主要是做了一些检查,实际针对内容处理是放在ObjectAPI 这个interface的实现层,以 putObject 为例,做了以下内容
    1. 检查 http 头字段
    2. 验证签名
    3. bucket容量检查
    4. 验证md5
      API
      API

2.7. 对象放入和获取流程(仅仅针对分布式模式)

以下分析以 server 模式下, ObjectAPI 的纠偏码模式实现来进行分析

2.7.1. PutObject(cmd/erasure-sets.go)

  1. 根据 object 的名称hash值获取磁盘Set集合。
  2. (cmd/erasure-object.go) 调用 putObject 接口
  3. 根据设置的 StorageClass 和磁盘set磁盘总数算出奇偶校验驱动器所使用驱动器数量和数据盘所使用驱动器数量
  4. 根据算出来的奇偶校验驱动器驱动器数量排序,把set集合中的驱动器分好
  5. 根据算出的奇偶校验块和数据盘块初始化 erasure 对象,并根据磁盘和数据包初始化好各个驱动器的 BitrotWriter,注意是先写到临时目录(.minio.sys/tmp/{uniqueID}/{DataDir}/{partName})
  6. 每次读取一定量的数据,进行并发写。实际最后是调用到了 StorageRESTClient.AppendFile 接口
  7. 全部数据写完后,调用 renameData 接口移动文件夹到真正的文件路径,实际最后是调用到了 StorageRESTClient.RenameData 接口
  8. 删除临时文件

备注:newErasureServerPools->waitForFormatErasure->connectLoadInitFormats->initStorageDisksWithErrors->newStorageAPI->newStorageRESTClient 来进行初始化 StorageApi

2.7.2. GetObject(erasure-sets.go)

此接口步骤基本上和PutObject相同,只不过接口换成了 StorageRESTClient.ReadFile,多了一个heal的步骤

2.8. 补充资料

Erasure-Code原理介绍

3. 最后感悟

梳理下来,minio的源码优秀的地方在于以下三点

  1. 大量使用interface做抽象支持不同实现
  2. 对存储服务考虑的比较周全
  3. 对于S3的HTTP规范理解比较深刻