分布式存储使用
联犀支持微服务,集群和单体模式的部署,要支持如此灵活的部署方式,文件存储也必须要能支持多种部署方式,如果使用本地文件存储的方式,那么就没办法进行横向拓展,所以联犀文件存储使用oss的方式进行文件存储,存储方式默认支持minio和阿里云oss两种oss的方式,本文讲述在联犀中如何正确的使用oss及minio.
# 前置说明
# oss
OSS(Object Storage Service)即对象存储服务。
它以对象的形式存储数据。每个对象包含数据、元数据(如日期、大小等)。和传统文件存储不同,它没有复杂的目录层级结构。OSS 具有海量存储能力,能轻松应对大规模数据。它可以通过网络访问,提供高可用、高可靠的数据存储解决方案。在互联网应用场景中非常实用,比如存储网站的图片、视频、用户文件等海量数据,许多云服务提供商都提供 OSS,是现代数据存储的重要方式。
# minio
Minio 是一款出色的高性能、分布式对象存储系统。
在存储方式上,它基于对象存储,每个对象含数据、元数据和唯一标识符,和传统存储方式有别,适合海量非结构化数据存储。其存储引擎高度优化,读写性能优异,通过纠删码技术保障数据可靠性和高效存储。
在架构方面,支持分布式部署,能灵活扩展节点,轻松应对大规模存储需求。兼容性良好,与亚马逊 S3 API 兼容,这让众多基于 S3 开发的工具能与之集成。并且它是开源的,在 GitHub 上有活跃社区,方便开发人员定制和获取支持。
应用场景广泛,在云存储服务中,可构建企业私有云存储,用于文件存储、备份和共享。在数据湖存储场景下,能存储各种原始数据,供数据科学家和分析师使用。对于容器存储,可为容器提供持久化存储,保障微服务架构下应用程序的数据存储和共享。
# bucket
在 Minio 等对象存储系统中,bucket(存储桶)是一个基本概念。它类似于文件系统中的文件夹,但又有所不同。bucket 是用于存储对象(如文件、图片等)的容器。可以将多个相关的对象放在一个 bucket 中进行管理,比如一个公司把所有用户头像放在一个名为 “user - avatars” 的 bucket 里。每个 bucket 有自己的名称,在一个存储系统中,bucket 名称是全局唯一的,通过它可以方便地组织和访问其中存储的对象。
# 联犀oss定义
# 初始化
配置文件定义位置: share/conf/oss.go
type OssConf struct {
OssType string `json:",default=minio,options=minio|aliyun"` //oss的类型
AccessKeyID string `json:",default=root,optional"` //账号
AccessKeySecret string `json:",default=password,optional"` //密码
PublicBucketName string `json:",default=ithings-public,optional"` //公开桶的名称
TemporaryBucketName string `json:",default=ithings-temporary,optional"` //临时桶,30分钟有效期
PrivateBucketName string `json:",default=ithings-private,optional"` //私有桶的名称
Location string `json:",default=localhost:9000,optional"` // oss的地址
UseSSL bool `json:",optional"` //是否使用ssl
CustomHost string `json:",default=/oss,env=OssCustomHost"` //带上host的返回前缀,支持环境变量
CustomPath string `json:",default=/oss,optional"` //相对路径返回的前缀
ConnectTimeout int64 //连接超时
ReadWriteTimeout int64 //读写超时
}
2
3
4
5
6
7
8
9
10
11
12
13
14
初始化: oss.NewOssClient(c.OssConf)
# 接口定义
联犀将不同的oss抽象出一组公共的接口提供给业务进行使用.
定义位置:share/oss/oss.go
type Handle interface {
//提供带签名的上传url
SignedPutUrl(ctx context.Context, filePath string, expiredSec int64, opKv common.OptionKv) (string, error)
//提供带签名的下载url
SignedGetUrl(ctx context.Context, filePath string, expiredSec int64, opKv common.OptionKv) (string, error)
//删除文件
Delete(ctx context.Context, filePath string, opKv common.OptionKv) error
//上传本地文件
Upload(ctx context.Context, filePath string, reader io.Reader, opKv common.OptionKv) (string, error)
//获取文件信息
GetObjectInfo(ctx context.Context, filePath string) (*common.StorageObjectInfo, error)
//将文件下载到本地
GetObjectLocal(ctx context.Context, filePath string, localPath string) error
//列出符合前缀的文件列表
ListObjects(ctx context.Context, prefix string) (ret []*common.StorageObjectInfo, err error)
//获取私有桶
PrivateBucket() Handle
//获取公开桶
PublicBucket() Handle
//获取临时桶
TemporaryBucket() Handle
//从临时桶中将文件复制到选定桶的路径
CopyFromTempBucket(tempPath, dstPath string) (string, error)
//不带过期时间的获取文件url
GetUrl(path string, withHost bool) (string, error)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 联犀文件上传
在所有业务系统中文件的操作是少不了的.如果设计的不好,就会导致到处都是文件上传的接口.如果都使用同一个上传接口,那么又会导致文件的混乱,不断增长和不可控制.
联犀在经历多次踩坑之后总结了一套通用且好用的方式.
- 初始化的时候,联犀在oss上会默认创建三个存储桶(bucket):
- 临时桶: 文件有保存期限,并且上传只能上传到这个桶中
- 私有桶: 当文件只能给有权限的人看的时候,文件会放到这个桶里,获取文件的时候要通过指定的链接才能拿到
- 公开桶: 如果这个文件不在乎权限,如用户头像,ota固件升级包,那么放到这个桶里的文件只要知道url都可以获取
- 联犀提供了一个通用的上传接口,可以将文件上传到临时桶中,并返回文件存储的路径,这个接口所有服务和应用都可以使用.
- 前端拿到临时文件的路径后,路径作为业务接口的参数传递给业务后端,业务后端借助oss的方法将临时桶中的文件移动到私有桶或公开桶中.
至此,一个通用的上传流程就搞定了,下面是流程图示例:
代码示例(位置: things/service/dmsvr/internal/logic/productmanage/productInfoUpdateLogic.go:136):
if data.ProductImg != "" && data.IsUpdateProductImg == true { //如果填了参数且不等于原来的,说明修改头像,需要处理
if old.ProductImg != "" {
err := l.svcCtx.OssClient.PublicBucket().Delete(l.ctx, old.ProductImg, common.OptionKv{})
if err != nil {
l.Errorf("Delete file err path:%v,err:%v", old.ProductImg, err)
}
}
nwePath := oss.GenFilePath(l.ctx, l.svcCtx.Config.Name, oss.BusinessProductManage, oss.SceneProductImg, fmt.Sprintf("%s/%s", data.ProductID, oss.GetFileNameWithPath(data.ProductImg)))
path, err := l.svcCtx.OssClient.PublicBucket().CopyFromTempBucket(data.ProductImg, nwePath)
if err != nil {
return errors.System.AddDetail(err)
}
old.ProductImg = path
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
其中,我们需要关注 GenFilePath 这个函数,这个函数的签名如下:
func GenFilePath(ctx context.Context, svrName, business, scene, filePath string) string
- svrName:一般是我们的服务名,如core,路径的第一个就是服务名
- business: 一般为一类业务的称呼,如产品管理,区域管理
- scene: 场景,具体用在什么地方,如头像,配置文件等
同时如果ctx中带有租户号,也会放到文件的路径中,保证多租户的隔离.
# 联犀文件下载
文件下载有两种方式,一种是返回相对路径,一种是返回绝对路径,联犀默认都是返回相对路径,这种方式的好处是无需配置oss的返回地址,但是在类似设备ota升级这种场景,设备是需要获取绝对路径才可以使用,这种时候才需要进行绝对路径的配置.
相对路径的获取方式:
if pi.ProductImg != "" {
dpi.ProductImg, err = svcCtx.OssClient.PublicBucket().GetUrl(pi.ProductImg, false)
if err != nil {
logx.WithContext(ctx).Errorf("%s.SignedGetUrl err:%v", utils.FuncName(), err)
}
}
2
3
4
5
6
绝对路径的获取方式:
svcCtx.OssClient.PublicBucket().GetUrl(files[0].FilePath, true)
绝对路径配置位置:
也可以使用环境变量的方式进行赋值,关键字是: OssCustomHost
# minio运维
# 后台
- 访问地址为: http://127.0.0.1:9090/
- 账号: root
- 密码: password
# 数据迁移
# 迁出
#进入容器后执行
mc alias set local http://localhost:9000 root password
mc cp --recursive local/ ./ #私有桶迁出
# 退回宿主机后执行
docker cp ienv-minio:/file ./ # 将文件复制出镜像
2
3
4
5
6
7
# 迁入
将文件拷贝到需要迁入的服务器后
# 宿主机中执行
docker cp ./ ienv-minio:/file # 将文件复制进镜像
# 进入容器后执行
mc alias set local http://localhost:9000 root password
mc cp --recursive ./* local
2
3
4
5
6
# 总结
通过以上方式,我们各个业务使用文件无需再单独定义自己的上传接口,仅需定义文件路径即可,实现通用的oss能力.