Skip to content
forked from qinguoyi/osproxy

对象存储分布式代理(object storage distrbuted proxy),支持Docker一键部署

License

Notifications You must be signed in to change notification settings

JIeJaitt/osproxy

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

osproxy

osproxy是一个使用Go语言开发的对象存储分布式代理(object-storage-distributed-proxy),可以作为文件存储微服务,文件会在服务中转处理后再对接到对象存储,包括但不限于以下功能:

  • 分布式uid及秒传,支持相同文件不同命名
  • 分片读写,大文件上传,merge接口不用等待数据合并,分片上传完直接下载
  • 异步任务,易扩展的event-handler,支持分片合并及其他文件处理任务
  • 统一封装,降低业务接入复杂度,业务侧只需要存储文件uid
  • 代理下载,不直接暴露底层存储厂商及格式
  • 支持集群部署,proxy模块处理不同机器的分片转发
  • 支持Local/MinIO/腾讯COS/阿里OSS等对象存储,易于扩展
  • 支持Docker一键部署

本项目仅用作学习交流,如果你正在学习Go语言,并且该项目给你的学习带来了一些帮助,欢迎star,欢迎交流。

目录

架构 功能 API 技术栈 更新日志 本地 单机 集群 扩展 如何接入 庖丁解牛 grpc实现 讨论群

架构

img.png

功能

img1.png

API文档

img2.png

技术栈

  • Golang
  • Gin
  • GORM
  • Redis
  • MySQL/PostgreSQL
  • MinIO
  • Swagger
  • Zap
  • Nginx
  • Docker

更新日志

  • 新增本地存储

本地调试

注意: 请提前准备好golang和docker环境;服务启动会自动创建表,但不会创建库,需要自己创建库.

注意:本地调试,不使用nginx做反向代理.

基础服务

  • 启动pg数据库
docker run -d --restart=always -p 5432:5432 --name=postgresql -v `pwd`/pg:/var/lib/postgresql/data -e POSTGRES_PASSWORD=123456 postgres
  • 启动redis
docker run -d --restart=always --name myredis -p 6379:6379 redis --requirepass "123456"
  • 启动minio
# 9000是api端口,9090是控制台端口
docker run -p 9000:9000 -p 9001:9001 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minioadmin" -e "MINIO_SECRET_KEY=minioadmin" -v `pwd`/minio/data:/data -v `pwd`/minio/config:/root/.minio minio/minio server /data --console-address ":9001"

配置修改

  • 修改配置文件
app:
  env: prod
  port: 8888                # 服务端口
  app_name: osproxy         # 服务名称
  app_url: http://127.0.0.1

log:
  level: info               # 日志等级
  root_dir: ./storage/logs  # 日志根目录
  filename: app.log         # 日志文件名称
  format: json              # 写入格式 可选json
  show_line: true           # 是否显示调用行
  max_backups: 3            # 旧文件的最大个数
  max_size: 500             # 日志文件最大大小(MB)
  max_age: 28               # 旧文件的最大保留天数
  compress: true            # 是否压缩
  enable_file: false        # 是否启用日志文件

database:
  - db_name: default
    driver: postgres                # 数据库驱动
    host: 127.0.0.1                 # 服务地址
    port: 5432                      # 端口号
    database: postgres              # 数据库名称
    username: postgres              # 用户名
    password: 123456                # 密码
    charset: utf8mb4                # 编码格式
    max_idle_conns: 10              # 空闲连接池中连接的最大数量
    max_open_conns: 100             # 打开数据库连接的最大数量
    log_mode: info                  # 日志级别
    enable_lg_log: false            # 是否开启自定义日志
    enable_file_log_writer: false   # 是否打印到日志文件
    log_filename: sql.log           # 日志文件名称

redis:
  host: 127.0.0.1        # 服务地址
  port: 6379             # 服务端口
  db: 0                  # 库选择
  password: 123456       # 密码

minio:
  endpoint: 127.0.0.1:9000      # 服务地址
  access_key_id: minioadmin     # 用户名
  secret_access_key: minioadmin # 密码
  use_ssl: false                # 密码
  enabled: true                 # 是否启用

cos:
  appid: 12***63195                              # 唯一标识
  region: ap-nanjing                             # 地域
  secret_id: AKIDc4GCrG*******p9GApIZdU9V        # 用户名
  secret_key: IMDoC1uYw*******1pNTRFpBJOJzSm     # 密码
  enabled: false                                 # 是否启用

oss:
  endpoint: oss-cn-beijing.aliyuncs.com          # 服务地址
  access_key_id: LTAI5t*******X1tHK              # 用户名
  access_key_secret: bfCt*******HudARdfwfvaoS    # 密码
  enabled: false                                 # 是否启用
  
local:
  enabled: false                                 # 是否启用

服务启动

# 设置goproxy
Linux: go env -w GOPROXY=https://goproxy.cn,direct
Windows: $env:GOPROXY = "https://goproxy.cn"

# 拉取依赖
go mod tidy
go install github.com/swaggo/swag/cmd/swag@v1.8.1

# 修改本地变量

app/pkg/utils/constant.go
LocalStore变量, 更新成本地可访问的目录


# 生成api文档
swag fmt -g cmd/main.go -d ./
swag init -g cmd/main.go
http://127.0.0.1:8888/swagger/index.html#/

服务测试

  • 服务部署验证
    # 修改服务地址和上传文件
    baseUrl := "http://127.0.0.1:8888"
    uploadFilePath := "./xxx.jpg"
    # 启动服务
    go run cmd/main.go
    # 测试
    go run test/httptest.go
  • 下载断点续传测试
    wget -c url

本地构建镜像

# 这里使用dockerhub作为镜像仓库,请提前创建好账户密码

# 登录,username和passwd替换成有效的账户密码
echo passwd | docker login --username=username --password-stdin

# 本地构建,version替换成自定义的有效字符,比如v0.1
docker build -t osproxy:version -f deploy/Dockerfile  .

# 镜像重命名,qinguoyi/object-storage-proxy请替换成你的username/repo
docker tag osproxy:version qinguoyi/object-storage-proxy:version

# 镜像上传
docker push qinguoyi/object-storage-proxy:tag

单机部署

注意:单机部署,使用nginx做反向代理;安装docker compose

配置修改

启动服务

docker compose up -d

服务测试

  • 服务部署验证
    # 修改服务地址和上传文件
    baseUrl := "https://124.222.198.8"
    uploadFilePath := "./xxx.jpg"
    
    go run test/httptest.go
  • 下载断点续传测试
    wget -c url

停止服务

docker compose down

集群部署

  • 如果是docker-swarm或者k8s部署,注意修改获取local ip的方法,直接使用GetClientIp即可,集群内可以互通;
  • 如果是腾讯云或者阿里云的服务器,直接部署,服务器间是不能互通内网ip的,需要使用GetOutBoundIP获取外网ip。
// GetClientIp 获取本地网卡ip
func GetClientIp() (string, error) {
	addrs, err := net.InterfaceAddrs()

	if err != nil {
		return "", err
	}

	for _, address := range addrs {
		// 检查ip地址判断是否回环地址
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String(), nil
			}

		}
	}

	return "", errors.New("can not find the client ip address")
}

// GetOutBoundIP 获取外网ip
func GetOutBoundIP() (string, error) {
	//向查询IP的网站发送GET请求
	resp, err := http.Get("http://myexternalip.com/raw")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	//读取响应的内容
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	return string(body), nil
}

服务测试

  • 服务部署验证
    # 修改服务地址和上传文件
    baseUrl := "https://124.222.198.8"
    uploadFilePath := "./xxx.jpg"
    
    go run test/httptest.go
  • 下载断点续传测试
    wget -c url

扩展存储

  • 实现CustomStorage接口并注册,用于存取数据
// CustomStorage 存储
type CustomStorage interface {
	// MakeBucket 创建存储桶
    MakeBucket(string) error

    // GetObject 获取存储对象
    GetObject(string, string, int64, int64) ([]byte, error)

    // PutObject 上传存储对象
    PutObject(string, string, string, string) error
}

// InitStorage 注册启动顺序
func InitStorage(conf *config.Configuration) {
    var storageHandler CustomStorage
    if conf.Minio.Enabled {
        storageHandler = NewMinIOStorage()
        bootstrap.NewLogger().Logger.Info("当前使用的对象存储:Minio")
    } else if conf.Cos.Enabled {
        storageHandler = NewCosStorage()
        bootstrap.NewLogger().Logger.Info("当前使用的对象存储:COS")
    } else if conf.Oss.Enabled {
        storageHandler = NewOssStorage()
        bootstrap.NewLogger().Logger.Info("当前使用的对象存储:OSS")
    } else {
        panic("当前对象存储都未启用")
    }

    lgStorage = &LangGoStorage{
        Mux:     &sync.RWMutex{},
        Storage: storageHandler,
    }
    for _, bucket := range []string{"image", "video", "audio", "archive", "unknown", "doc"} {
        if err := storageHandler.MakeBucket(bucket); err != nil {
            panic(err)
        }
    }
}
  • 实现Plugin接口,用于初始化和健康检查
// Plugin 插件接口
type Plugin interface {
    // Flag 是否启动
    Flag() bool
    // Name 插件名称
    Name() string
    // New 初始化插件资源
    New() interface{}
    // Health 插件健康检查
    Health()
    // Close 释放插件资源
    Close()
}
  • 增加config.yaml配置项
# 类似下面的存储
minio:
  endpoint: 127.0.0.1:9000      # 服务地址
  access_key_id: minioadmin     # 用户名
  secret_access_key: minioadmin # 密码
  use_ssl: false                # 密码
  enabled: true                 # 是否启用

cos:
  appid: 12***63195                              # 唯一标识
  region: ap-nanjing                             # 地域
  secret_id: AKIDc4GCrG*******p9GApIZdU9V        # 用户名
  secret_key: IMDoC1uYw*******1pNTRFpBJOJzSm     # 密码
  enabled: false                                 # 是否启用

oss:
  endpoint: oss-cn-beijing.aliyuncs.com          # 服务地址
  access_key_id: LTAI5t*******X1tHK              # 用户名
  access_key_secret: bfCt*******HudARdfwfvaoS    # 密码
  enabled: false  

local:
  enabled: false                                 # 是否启用

如何接入osproxy

客户端对接存储代理服务,业务侧仅存储文件uid.

  • 上传
    • 客户端对接存储代理,判断秒传/断点续传,获取上传链接后上传数据到存储,返回文件uid,上报到业务侧
    sequenceDiagram
    Note left of 客户端(web/api):上传接入osproxy
    客户端(web/api) ->>+ osproxy: 请求秒传
    alt 数据已上传
    osproxy-->>-客户端(web/api) : 文件uid
    客户端(web/api) ->>+ 业务侧: 文件uid
    业务侧-->>- 客户端(web/api) : 上报成功
    else 数据不存在
    osproxy-->>+客户端(web/api) : 文件不存在
    客户端(web/api) ->>+ osproxy: 请求上传链接
    osproxy-->>-客户端(web/api) : 文件链接及上传uid
    客户端(web/api) ->>+ osproxy: 上传数据
    osproxy-->>-客户端(web/api) : 成功,返回文件uid
    客户端(web/api) ->>+ 业务侧: 文件uid
    业务侧-->>- 客户端(web/api) : 上报成功
    end
    
    Loading
  • 下载
    • 客户端从业务侧获取文件uid,从存储代理获取下载链接,返回文件数据。
    sequenceDiagram
    Note left of 客户端(web/api):下载接入osproxy
    客户端(web/api) ->>+ 业务侧: 获取业务侧文件
    业务侧-->>- 客户端(web/api) : 文件uid
    客户端(web/api) ->>+ osproxy: 文件uid
    osproxy-->>-客户端(web/api) : 加密下载链接
    
    Loading

庖丁解牛

敬请期待...

grpc实现

grpc+ssl

讨论群

  • 微信群失效,加我微信,备注osproxy
微信群 作者微信
group author

License

Distributed under the Apache 2.0 License. See LICENSE for more information.

About

对象存储分布式代理(object storage distrbuted proxy),支持Docker一键部署

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 99.1%
  • Other 0.9%