cegh&&OSS

相关部署:https://blog.csdn.net/m0_58833554/article/details/134604853

使用golang实现cegh上传

data, _ := ioutil.ReadAll(newFile)
			cephPath := "/ceph/" + fileMeta.FileSha1
			_ = ceph.PutObject("userfile", cephPath, data)
			fileMeta.Location = cephPath

相关的cegh操作:

func GetCephConnection() *s3.S3 {
	if cephConn != nil {
		return cephConn
	}
	// 1. 初始化ceph的一些信息

	auth := aws.Auth{
		AccessKey: cfg.CephAccessKey,
		SecretKey: cfg.CephSecretKey,
	}

	curRegion := aws.Region{
		Name:                 "default",
		EC2Endpoint:          cfg.CephGWEndpoint,
		S3Endpoint:           cfg.CephGWEndpoint,
		S3BucketEndpoint:     "",
		S3LocationConstraint: false,
		S3LowercaseBucket:    false,
		Sign:                 aws.SignV2,
	}

	// 2. 创建S3类型的连接
	return s3.New(auth, curRegion)
}

// GetCephBucket : 获取指定的bucket对象
func GetCephBucket(bucket string) *s3.Bucket {
	conn := GetCephConnection()
	return conn.Bucket(bucket)
}

// PutObject : 上传文件到ceph集群
func PutObject(bucket string, path string, data []byte) error {
	return GetCephBucket(bucket).Put(path, data, "octet-stream", s3.PublicRead)
}

OSS

设置相关参数:

package config

const (
	// OSSBucket : oss bucket名
	OSSBucket = "buckettest-filestore2"
	// OSSEndpoint : oss endpoint
	OSSEndpoint = "oss-cn-shenzhen.aliyuncs.com"
	// OSSAccesskeyID : oss访问key
	OSSAccesskeyID = "<你的AccesskeyId>"
	// OSSAccessKeySecret : oss访问key secret
	OSSAccessKeySecret = "<你的AccessKeySecret>"
)

相关操作:


var ossCli *oss.Client

// Client : 创建oss client对象
func Client() *oss.Client {
	if ossCli != nil {
		return ossCli
	}
	ossCli, err := oss.New(cfg.OSSEndpoint,
		cfg.OSSAccesskeyID, cfg.OSSAccessKeySecret)
	if err != nil {
		fmt.Println(err.Error())
		return nil
	}
	return ossCli
}

// Bucket : 获取bucket存储空间
func Bucket() *oss.Bucket {
	cli := Client()
	if cli != nil {
		bucket, err := cli.Bucket(cfg.OSSBucket)
		if err != nil {
			fmt.Println(err.Error())
			return nil
		}
		return bucket
	}
	return nil
}

// DownloadURL : 临时授权下载url
func DownloadURL(objName string) string {
	signedURL, err := Bucket().SignURL(objName, oss.HTTPGet, 3600)
	if err != nil {
		fmt.Println(err.Error())
		return ""
	}
	return signedURL
}

将文件上传到OSS中

else if cfg.CurrentStoreType == cmn.StoreOSS {
			// 文件写入OSS存储
			ossPath := "oss/" + fileMeta.FileSha1
			// 判断写入OSS为同步还是异步
			if !cfg.AsyncTransferEnable {
				err = oss.Bucket().PutObject(ossPath, newFile)
				if err != nil {
					fmt.Println(err.Error())
					w.Write([]byte("Upload failed!"))
					return
				}
				fileMeta.Location = ossPath
			}

对下载进行改造
目标:生成文件的下载地址,根据文件的存储位置(本地、Ceph、OSS)生成相应的下载链接,并返回给客户端。代码的工作流程如下:

代码流程解析:

  1. 获取文件的哈希值

    • 使用 r.Form.Get("filehash") 从请求中获取文件的哈希值(filehash),这个值唯一标识文件,方便在存储系统中查找文件。
  2. 从数据库查找文件信息

    • 通过 dblayer.GetFileMeta(filehash) 从文件元数据表中查找与该哈希值对应的文件记录。记录中包含文件的存储路径等信息。
  3. 判断文件的存储位置

    • 代码通过 row.FileAddr.String 的值来判断文件存储在何处。依据文件存储路径的前缀,分别处理:
      • 本地存储:如果文件路径以 "/tmp" 开头,则表示文件存储在本地服务器。
      • Ceph 存储:如果路径以 "/ceph" 开头,表示文件存储在分布式存储系统 Ceph 中。
      • OSS 存储:如果路径以 "oss/" 开头,表示文件存储在对象存储服务 OSS(如阿里云OSS、Amazon S3)中。
  4. 生成下载URL

    • 本地文件:如果文件存储在本地,使用 r.Hostfilehashusernametoken 生成一个临时的下载链接,返回给客户端。客户端通过这个链接可以直接从服务器下载文件。
    • OSS文件:如果文件存储在 OSS,代码调用 oss.DownloadURL() 方法生成一个带签名的下载链接(signedURL)。签名下载URL是一种安全的访问方式,允许用户在规定的时间内下载文件。
    • Ceph文件:Ceph部分的逻辑暂未实现,但类似于 OSS,通常也需要生成特定的下载URL供客户端使用。

为什么要这样做:

  1. 不同存储位置的不同处理方式

    • 文件可以存储在多个位置(本地、Ceph、OSS)。根据文件的存储位置生成不同的下载链接,确保客户端可以从正确的存储源获取文件。不同的存储方案有各自的下载方式,故需要分别处理。
  2. 避免不必要的数据传输

    • 对于存储在OSS中的文件,不需要将文件传回上传服务器再返回给客户端。直接生成OSS的签名URL并返回给客户端,可以大大减少服务器的负载和带宽消耗。OSS等云存储通常具有独立的CDN或下载能力,客户端直接从OSS下载比通过服务器中转要更高效。
  3. 提高系统的扩展性和性能

    • 当文件规模变大、访问频率增加时,服务器负载会随之上升。通过将下载过程直接交给OSS或其他存储系统,可以减少对主服务器的压力,提高系统的整体性能和扩展性。

具体实现

func DownloadURLHandler(w http.ResponseWriter, r *http.Request) {
	filehash := r.Form.Get("filehash")
	// 从文件表查找记录
	row, _ := dblayer.GetFileMeta(filehash)

	// TODO: 判断文件存在OSS,还是Ceph,还是在本地
	if strings.HasPrefix(row.FileAddr.String, "/tmp") {
		username := r.Form.Get("username")
		token := r.Form.Get("token")
		tmpUrl := fmt.Sprintf("http://%s/file/download?filehash=%s&username=%s&token=%s",
			r.Host, filehash, username, token)
		w.Write([]byte(tmpUrl))
	} else if strings.HasPrefix(row.FileAddr.String, "/ceph") {
		// TODO: ceph下载url
	} else if strings.HasPrefix(row.FileAddr.String, "oss/") {
		// oss下载url
		signedURL := oss.DownloadURL(row.FileAddr.String)
		w.Write([]byte(signedURL))
	}
}

这段代码的主要作用是为指定的OSS(对象存储服务)存储桶设置生命周期规则(Lifecycle Rule)。它通过定义规则,自动管理文件(对象)的存储周期,如自动删除或归档过期文件,从而优化存储成本和提高管理效率。

OSS对象声明周期管理

对象声明周期存储的必要性:

  1. 自动化文件管理

    • 对象存储系统中,文件可能随着时间积累而增加。手动管理这些文件的存储周期(如定期删除不再使用的文件)费时费力,且容易出错。通过设置生命周期规则,系统可以自动执行这些任务,简化文件管理流程。
  2. 优化存储成本

    • 随着数据量增长,存储成本也会增加,尤其是对于那些不再频繁访问的文件。通过设置文件自动过期和删除规则,企业可以有效减少存储空间占用,进而降低存储成本。OSS等云存储服务通常根据使用的存储空间收费,因此及时删除不需要的文件可以显著节约费用。
  3. 提升系统效率

    • 自动清理过期文件不仅节省了存储空间,也有助于提高存储系统的性能。减少无效或过期文件的占用,有助于加快数据查找和处理速度,提升整体响应效率。
  4. 减少人为错误

    • 手动清理数据可能会带来误删文件的风险,而通过明确的生命周期规则,可以确保数据在合理的时间点被自动清理,避免人为操作失误。

代码流程解析:

  1. 定义生命周期规则

    • ruleTest1 := oss.BuildLifecycleRuleByDays("rule1", "test/", true, 30)
      • 调用 oss.BuildLifecycleRuleByDays 方法创建一个生命周期规则 ruleTest1,其中包含:
        • 规则的唯一标识符 "rule1"
        • 对象的前缀 "test/",表示该规则只对路径以 test/ 开头的文件生效。
        • true 表示规则开启(启用状态)。
        • 30 表示生命周期的时长,即文件最后修改时间30天后将被删除。
    • 该规则的作用是:任何存储在存储桶中并且路径以 test/ 开头的文件,在距最后修改时间30天后会自动过期和删除。
  2. 规则列表

    • rules := []oss.LifecycleRule{ruleTest1}
      • 将创建的生命周期规则 ruleTest1 放入一个规则列表 rules 中。虽然此处只定义了一个规则,但OSS允许对一个存储桶应用多个规则,方便用户根据不同的前缀或文件类型进行分类管理。
  3. 设置生命周期规则到存储桶

    • Client().SetBucketLifecycle(bucketName, rules)
      • 调用OSS客户端的 SetBucketLifecycle 方法,将前面创建的规则列表 rules 应用于指定的 bucketName(存储桶)。此操作会在OSS后台生效,自动管理符合规则的对象文件。

具体实现


func BuildLifecycleRule(bucketName string) {
	// 表示前缀为test的对象(文件)距最后修改时间30天后过期。
	ruleTest1 := oss.BuildLifecycleRuleByDays("rule1", "test/", true, 30)
	rules := []oss.LifecycleRule{ruleTest1}

	Client().SetBucketLifecycle(bucketName, rules)
}

优化客户端上传文件流程

传统架构中的问题(左图)

在左图所示的传统文件上传架构中,文件上传由一个“上传服务(调度)”进行统筹调度,整个流程大致如下:

  1. 客户端上传文件到上传服务

    • 客户端将文件上传到位于中间层的上传服务。上传服务负责处理客户端请求,并根据系统中的存储规则将文件转发至对应的存储系统,如 Ceph 或 OSS。
  2. 上传服务调用存储 SDK

    • 上传服务根据不同的存储系统选择合适的 SDK 来进行文件上传,ceph_sdk 用于上传到 Ceph,oss_sdk 用于上传到 OSS。
  3. 上传服务的存储调度

    • 上传服务在这个流程中不仅需要接收文件,还要负责传输和调度整个文件上传的过程。虽然这样的设计能统一管理文件上传的行为,但存在以下几个问题:
      • 性能瓶颈:上传服务充当了文件上传的中间人,导致其成为整个上传流程的瓶颈。
      • 冗余操作:客户端实际上可以直接与存储系统交互,但中间层的存在增加了额外的延迟。
      • 复杂性高:上传服务既要处理调度,还要负责传输,增加了系统复杂性和维护成本。

优化架构中的提升(右图)

为了解决上述问题,右图展示了一个经过优化的文件上传流程。通过对流程的简化和优化设计,客户端上传文件的效率显著提升。优化后的流程如下:

  1. 获取授权,直接上传

    • 客户端通过上传服务获取到目标存储系统(如 OSS)的上传授权。这一步骤不再由上传服务直接处理上传,而是授权给客户端进行操作。授权可以通过获取短期访问凭证(如 STS token)实现,确保客户端有权访问存储系统。
  2. 客户端直接上传文件到存储系统

    • 获取授权后,客户端可以直接将文件上传到 OSS。这种方式跳过了上传服务的中转过程,客户端与存储系统直接交互,不仅减少了上传延迟,还降低了上传服务的负载。
  3. 上传完成后通知服务

    • 文件上传完成后,客户端会通知上传服务,告知上传已成功完成。上传服务负责记录此次上传的状态或进一步处理文件的元数据。这个步骤可以帮助系统实现上传结果的追踪和后续业务逻辑的执行。

优化带来的核心好处

  1. 提高上传效率

    • 通过去除上传服务作为中转层的设计,客户端直接与存储系统交互,减少了不必要的数据传输步骤,大大提升了上传效率,尤其是在大文件上传的场景下。
  2. 减轻上传服务负担

    • 传统架构中的上传服务不仅要调度,还要处理数据的转发,增加了负载。而在优化后的架构中,上传服务仅负责权限管理和调度,实际的文件上传由客户端直接完成,从而减轻了上传服务的负担,提升了系统的可扩展性。
  3. 增强系统灵活性

    • 优化后的架构更加模块化,客户端的上传操作与存储系统的交互更加直接。如果未来需要更换存储系统或增加新的存储选项,客户端只需更新获取授权的逻辑,而不需要修改上传服务的核心逻辑。
  4. 上传完成通知机制提升稳定性

    • 上传完成通知的设计使得上传服务可以更好地追踪每次上传的状态,从而为后续的文件处理、审核或其他业务操作提供保障。这种机制有助于在大规模并发上传时保持系统的稳定性和一致性。

实现建议

在实现过程中,可以参考以下几点来进行部署和优化:

  1. 分布式授权管理:使用类似 STS 的授权机制来管理不同存储系统的访问权限,客户端可以根据需要动态获取访问凭证。
  2. 上传进度控制:在客户端实现上传进度的管理和重试机制,以应对网络抖动或中断等问题。
  3. 智能调度:上传服务可以基于存储系统的负载、文件大小、网络状态等多因素动态调度上传策略。
  4. 缓存与断点续传:对于大文件或不稳定网络环境,可以在客户端实现缓存和断点续传机制,进一步提升用户体验。
posted @ 2024-09-09 09:19  daligh  阅读(52)  评论(0)    收藏  举报