golang使用阿里云OSS对象存储

概念

  • Bucket:存储空间
  • Object:文件
  • ObjectKey:文件名,文件的唯一标识,与KeyObjectName为同一概念
  • Region:地域,OSS数据中心的地理位置
  • Endpoint:域名
  • AccessKey:密钥,由AccessKey IDAccessKey Secret组成
计费 总费用 = 存储费 + 临时存储费 + 数据取回费 + 流量费 + 请求费 + 其他费用
一、存储费
存储费 = 文件大小(GB) x 单价 ÷ 30 ÷ 24 x 存储时长(hour)
存储类型 单价
标准-本地冗余 0.12
标准-同城冗余 0.15
低频访问-本地冗余 0.08
低频访问-同城冗余 0.10
归档 0.033
冷归档 0.015
最贵的标准存储的单价是最便宜的冷归档存储的8倍。
存储一个大小为1G的文件每月需要支付0.12元的存储费。
二、临时存储费
临时存储费 = 文件大小(GB) x 单价 ÷ 30 ÷ 24 x 解冻时长(hour)
存储类型 单价
冷归档 0.12
冷归档文件在解冻期间,须额外支付一笔按照标准存储单价计算的临时存储费用。
三、数据取回费
【golang使用阿里云OSS对象存储】数据取回费 = 文件大小(GB) x 单价
存储类型 单价
标准 0
低频访问 0.0325
归档 0.06
冷归档 0.03~0.2
四、流量费
流量费 = 流量(GB) x 单价
流量方向 单价
上传 0
内网下载 0
外网下载(0-8) 0.25
外网下载(8-24) 0.50
CDN回源下载 0.15
跨区域复制(中国大陆) 0.50
外网下载(0-8) 0.25
上传免费,下载收费。
一个大小为1G的文件,每下次1次,需要支付0.25元的流量费。
五、请求费
请求费 = math.Ceil(请求次数/10000) x 单价
请求类型 标准 低频访问 归档 冷归档
PUT 0.01 0.1 0.1 0.1
GET 0.01 0.1 0.1 0.1
取回 / / / 0.3 ~ 30
冷归档文件的取回请求每一万次需支付30元的请求费。
六、其他费用
跨区域文件复制流量费、图片处理费、图片压缩费、视频截帧费、标签费、传输加速费、DDoS防护费、元数据管理费、敏感数据保护费等等。
存储类型 标准 + 低频 + 归档 + 冷归档。
golang使用阿里云OSS对象存储
文章图片

对比指标 标准 低频 归档 冷归档
最小计量单位 0 64KB 64KB 64KB
最小存储时长 0 30天 60天 180天
访问延迟 0 0 1min 1-12hour
数据取回费用 0 30天 60天 180天
URL 公共:://./
私有:://./?签名信息
Bucket CRUD
Bucket创建后不支持修改,所以只有CRD,没有U
package mainimport ( "fmt" "os" "github.com/aliyun/aliyun-oss-go-sdk/oss" )func main() { // 创建OSSClient实例。 // yourEndpoint填写Bucket对应的Endpoint。 // 以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。 client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 新增 err = client.CreateBucket( "examplebucket", oss.StorageClass(oss.StorageIA), oss.ACL(oss.ACLPublicRead), oss.RedundancyType(oss.RedundancyZRS) ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) } /* StorageClass:存储类型。 StorageStandard=标准存储 StorageIA=低频访问 StorageArchive=归档存储 StorageColdArchive=冷归档存储 ACL:访问权限。 ACLPrivate=私有 ACLPublicRead=公共读 ACLPublicReadWrite=公共读写 RedundancyType:冗余类型。 RedundancyLRS=本地冗余 RedundancyZRS=同城冗余 */// 查询 marker := "" for { lsRes, err := client.ListBuckets(oss.Marker(marker)) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 默认情况下一次返回100条记录。 for _, bucket := range lsRes.Buckets { fmt.Println("Bucket: ", bucket.Name) }if lsRes.IsTruncated { marker = lsRes.NextMarker } else { break } }// 查询Region ID regionID, err := client.GetBucketLocation("examplebucket") if err != nil { fmt.Println("Error:", err) os.Exit(-1) } fmt.Println("Bucket Location:", regionID) // oss-cn-shanghai /* Region ID可以理解为Endpoint的前缀: 外网Endpoint:oss-cn-shanghai.aliyuncs.com 内网Endpoint:oss-cn-shanghai-internal.aliyuncs.com */// 删除 err = client.DeleteBucket("examplebucket") if err != nil { fmt.Println("Error:", err) os.Exit(-1) } }

生命周期
通过设置生命周期,可以实现定期将Bucket内的Object转换存储类型,或者将过期的Object和碎片文件删除,进而降低存储费用。
可以基于最后一次修改时间(Last Modified Time)或最后一次访问时间(Last Access Time)创建生命周期规则。
限制 最后一次修改时间 最后一次访问时间
适用场景 访问模型确定 访问模型不确定
是否支持删除Object 支持 不支持
存储类型的转换是否可逆 不可逆 可逆
基于最后一次修改时间 最后一次修改时间,其实基本可以理解为创建时间。
这样就可以实现类似「上传N天后删除」或者「上传N天后将存储类型由标准切换为低频访问」的需求。
一些限制
  1. 仅支持根据前缀和标签进行匹配,不支持通配符匹配、后缀匹配以及正则匹配。
  2. 可以叠加,但上限为1000条。
  3. 溯及既往
  4. 生效:规则创建成功后的24小时内完成加载,加载成功后将于每天的08:00执行。
  5. 最后修改时间与规则执行时间的间隔必须大于24小时。
package mainimport ( "fmt" "os" "github.com/aliyun/aliyun-oss-go-sdk/oss" )func Main() { client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 定义规则// 前缀为10086的Object将于最后一次修改时间后的第14天删除 ruleA := oss.BuildLifecycleRuleByDays("ruleA", "10086", true, 14)// 前缀为10010的Object将于2025年10月10日的08:00删除 ruleB := oss.BuildLifecycleRuleByDate("ruleB", "10010", true, 2025, 10, 10)// 前缀为10000的Object的存储类型将于最后一次修改时间的30天后变更为低频访问 ruleC := oss.LifecycleRule{ ID:"ruleC", Prefix: "10000", Status: "Enabled", Transitions: []oss.LifecycleTransition{ { Days:30, StorageClass: oss.StorageIA, }, }, }// 设置规则 err = client.SetBucketLifecycle( "examplebucket", []oss.LifecycleRule{ruleA, ruleB, ruleC}, ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) } }

基于最后一次访问时间 基于最后一次访问时间的生命周期规则似乎不支持SDK
Object 类型
golang使用阿里云OSS对象存储
文章图片

Object根据上传方式可以分为以下三类:
上传方式 类型
简单上传 Normal
分片上传 Multipart
追加上传 Appendable
命名
  • UTF-8编码
  • 1 ~ 1024个字符
  • 不能以/\开头
上传和删除
package mainimport ( "fmt" "os" "github.com/aliyun/aliyun-oss-go-sdk/oss" )func Main() { client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 生成Bucket实例 bucket, e := client.Bucket("examplebucket") if err != nil { fmt.Println("Error:", e) os.Exit(-1) }// 上传 if err = bucket.PutObject("objectKey", file); err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 删除 if err = bucket.DeleteObject("objectKey"); err != nil { fmt.Println("Error:", err) os.Exit(-1) } }

访问控制 阿里云提供四种类型的访问权限控制策略:
类型 约束对象
RAM Policy OSS服务所属阿里云主账号的RAM
Bucket Policy 所有阿里云主账号的RAM和匿名用户
Bucket ACL Bucket
Object ACL Object
RAM Policy
RAM Policy为JSON格式,其结构为:
{ "Version": "1", "Statement": [ { "Effect": "Deny", "Action": [ "oss:PutObject" ], "Resource": [ "acs:oss:*:*:examplebucket" ], "Condition": {} } ] }

Version:版本
Statement:授权语句
Effect:效力(Allow / Deny)
Action:操作
Resource:资源
Condition:条件
可以使用官方的RAM策略编辑器快速生成RAM策略。
【示例】禁止指定RAM上传Object到Bucket。 step1:生成RAM Policy
{ "Version": "1", "Statement": [ { "Effect": "Deny", "Action": [ "oss:PutObject" ], "Resource": [ "acs:oss:*:*:avatar" ], "Condition": {} } ] }

step2:添加权限策略: 控制台 - RAM访问控制 - 权限管理 - 创建权限策略 - 脚本编辑
step3:为RAM添加权限策略: 控制台 - RAM访问控制 - 身份管理 - 用户 - 添加权限
Bucket Policy
package mainimport ( "fmt" "os" "github.com/aliyun/aliyun-oss-go-sdk/oss" )func Main() { client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 配置policy(禁止192.168.1.1之外的所有人对examplebucket的所有请求) policy := `{ "Version": "1", "Statement": [ { "Principal": ["*"], "Effect": "Deny", "Action": ["oss:*"], "Resource": ["acs:oss:*:*:examplebucket"], "Condition": { "NotIpAddress": { "acs:SourceIp": ["192.168.1.1"] } } } ] }`// 设置 err = client.SetBucketPolicy("examplebucket", policy) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 查询 policy, e := client.GetBucketPolicy("examplebucket") if e != nil { fmt.Println("Error:", e) os.Exit(-1) }// 删除 err = client.DeleteBucketPolicy("examplebucket") if err != nil { fmt.Println("Error:", err) os.Exit(-1) } }

Bucket ACL
package mainimport ( "fmt" "os" "github.com/aliyun/aliyun-oss-go-sdk/oss" )func Main() { client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 设置examplebucket的权限为公共读 err = client.SetBucketACL("examplebucket", oss.ACLPublicRead) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 设置examplebucket的权限为公共读写 err = client.SetBucketACL("examplebucket", oss.ACLPublicReadWrite) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 设置examplebucket的权限为私有 err = client.SetBucketACL("examplebucket", oss.ACLPrivate) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 查询Bucket ACL res, e := client.GetBucketACL("examplebucket") if e != nil { fmt.Println("Error:", e) os.Exit(-1) } }

Object ACL
package mainimport ( "fmt" "os" "github.com/aliyun/aliyun-oss-go-sdk/oss" )func Main() { client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 生成Bucket实例 bucket, e := client.Bucket("examplebucket") if e != nil { fmt.Println("Error:", e) os.Exit(-1) }// 设置exampleobject的权限为公共读 err = bucket.SetObjectACL("exampleobject", oss.ACLPublicRead) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 设置exampleobject的权限为公共读写 err = bucket.SetObjectACL("exampleobject", oss.ACLPublicReadWrite) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 设置exampleobject的权限为私有 err = bucket.SetObjectACL("exampleobject", oss.ACLPrivate) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 设置exampleobject的权限为继承Bucket err = bucket.SetObjectACL("exampleobject", oss.ACLDefault) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 查询Object ACL res, er := bucket.GetObjectACL("exampleobject") if er != nil { fmt.Println("Error:", er) os.Exit(-1) } }

数据容灾 同城冗余
如果把默认的本地冗余比喻为一份数据存在同一机房内的三个不同硬盘中的话,那么同城冗余就是将数据存在同一数据中心的三个不同机房。
从本地冗余到同城冗余,隔离等级由硬盘级上升到机房级。
err = client.CreateBucket( "examplebucket", oss.StorageClass(oss.StorageIA), oss.ACL(oss.ACLPublicRead), oss.RedundancyType(oss.RedundancyZRS) ) /* RedundancyType:冗余类型。 RedundancyLRS=本地冗余 RedundancyZRS=同城冗余 */

冗余类型仅支持创建Bucket时指定,一旦创建不支持修改。
跨城冗余
跨城冗余就是将数据存放在不同城市的机房。
从同城冗余到跨城冗余,隔离等级由机房级上升到城市级。
package mainimport ( "fmt" "github.com/aliyun/aliyun-oss-go-sdk/oss" "os" )func main(){ client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }/* 指定仅同步复制规则创建后新写入的数据 不同步源Bucket的历史数据 且此后将源Bucket执行的所有操作都将实时同步到目标Bucket */ putXml := `
prefix_1
prefix_2 ALL destexamplebucket oss-cn-beijing oss_acc disabled aliyunramrole Enabled c4d49f85-ee30-426b-a5ed-95e9139d**** `// 开启跨区域复制 err = client.PutBucketReplication("srcexamplebucket",putXml) if err != nil { fmt.Println("Error:", err) os.Exit(-1) } }

数据加密 OSS服务侧加密
上传Object时,OSS对收到的文件进行加密,再将加密文件持久化保存。
下载Object时,OSS自动将加密文件解密后返回,并在响应头中返回x-oss-server-side-encryption,用于声明该文件进行了OSS服务侧加密。
package mainimport ( "fmt" "os""github.com/aliyun/aliyun-oss-go-sdk/oss" )func main() { // 创建OSSClient实例 client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 初始化加密规则,使用AES256算法 config := oss.ServerEncryptionRule{ SSEDefault: oss.SSEDefaultRule{SSEAlgorithm: "AES256"}, }// 设置OSS服务侧加密 err = client.SetBucketEncryption("examplebucket", config) if err != nil { fmt.Println("Error:", err) os.Exit(-1) } }

应用服务侧加密
应用服务侧加密是指将Object发送到OSS之前进行加密,然后将加密后的Object传输至OSS持久化保存。
golang使用阿里云OSS对象存储
文章图片

package mainimport ( "bytes" "fmt" "io" "io/ioutil" "log" "os""github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/aliyun/aliyun-oss-go-sdk/oss/crypto" )func main() { // 创建OSSClient实例。 client, err := oss.New( "", "", "", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 创建一个主密钥的描述信息,创建后不允许修改。主密钥描述信息和主密钥一一对应。 // 如果所有的Object都使用相同的主密钥,主密钥描述信息可以为空,但后续不支持更换主密钥。 // 如果主密钥描述信息为空,解密时无法判断使用的是哪个主密钥。 // 强烈建议为每个主密钥都配置主密钥描述信息(json字符串), 由客户端保存主密钥和描述信息之间的对应关系(服务端不保存两者之间的对应关系)。// 由主密钥描述信息(json字符串)转换的map。 materialDesc := make(map[string]string) materialDesc["desc"] = ""// 根据主密钥描述信息创建一个主密钥对象。 masterRsaCipher, err := osscrypto.CreateMasterRsa( materialDesc, "", "", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 根据主密钥对象创建一个用于加密的接口, 使用aes ctr模式加密。 contentProvider := osscrypto.CreateAesCtrCipher(masterRsaCipher)// 获取bucket。 cryptoBucket, err := osscrypto.GetCryptoBucket(client, "", contentProvider) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 上传时时自动加密。 err = cryptoBucket.PutObject( "", bytes.NewReader([]byte("yourObjectValueByteArray")), ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 下载时自动解密。 body, err := cryptoBucket.GetObject("") if err != nil { fmt.Println("Error:", err) os.Exit(-1) } defer func(b *io.ReadCloser) { err = (*b).Close() if err != nil { log.Println("Error:", err) } }(&body)data, err := ioutil.ReadAll(body) if err != nil { fmt.Println("Error:", err) os.Exit(-1) } fmt.Println("data:", string(data)) }

版本控制 版本控制以回滚至任一历史版本的方式,为错误覆盖和错误删除提供容错空间。
版本控制的一些限制:
  1. 与合规保留策略无法同时配置
  2. 开启版本控制的Bucket,x-oss-forbid-overwrite请求头将不再生效
  3. 不建议同时开通OSS-HDFS服务并设置保留策略
版本控制有三个状态:未开启、开启、暂停。
package mainimport ( "fmt" "os""github.com/aliyun/aliyun-oss-go-sdk/oss" )func main() { // 创建Client实例。 client, err := oss.New( "", "", "", ) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 创建Bucket实例。 err = client.CreateBucket("") if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 开启版本控制。 config := oss.VersioningConfig{Status: "Enabled"} err = client.SetBucketVersioning("", config) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 暂停版本控制。 config.Status = "Suspended" err = client.SetBucketVersioning("", config) if err != nil { fmt.Println("Error:", err) os.Exit(-1) } }

防盗链 OSS通过对比request headerReferer与配置规则是否一致来判断允许或拒绝该请求。
package mainimport ( "fmt" "os""github.com/aliyun/aliyun-oss-go-sdk/oss" )func main() { // 创建Client实例。 client, err := oss.New("yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret") if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 初始化Referer白名单。支持通配符星号(*)和问号(?)。 referers := []string{ "http://www.aliyun.com", "http://www.???.aliyuncs.com", "http://www.*.com", }// 设置Referer白名单,并禁止空Referer // 注意:如果你配置了白名单但允许空Referer,结局会很僵硬... err = client.SetBucketReferer("yourBucketName", referers, false) if err != nil { fmt.Println("Error:", err) os.Exit(-1) }// 查询Bucket Referer res, e := client.GetBucketReferer("yourBucketName") if e != nil { fmt.Println("Error:", e) os.Exit(-1) } fmt.Println(res.RefererList, res.AllowEmptyReferer) }

合规保留策略 合规保留策略允许用户以“不可删除、不可篡改”方式保存和使用数据,符合美国证券交易委员会(SEC)和金融业监管局(FINRA)的合规要求。
package mainimport ( "fmt" "os""github.com/aliyun/aliyun-oss-go-sdk/oss" )func main() { // 创建OSSClient实例。 client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { handleError(err) }// 初始化WORM规则(锁定60天)。 // 最短锁定1天,最长锁定70年。 wormID, e := client.InitiateBucketWorm("examplebucket", 60) if e != nil { handleError(e) }/* 初始化后的24小时内,WORM处于InProgress状态。 在此期间,可以提交锁定,或者删除。 若在此期间即未提交锁定也未删除,则WORM自动失效。 */// 提交锁定 err = client.CompleteBucketWorm("examplebucket", wormID) if err != nil { handleError(err) }// 或者删除 /* err = client.AbortBucketWorm("examplebucket") if err != nil { handleError(err) } */// 通过Bucket查询WORM wormConfiguration, er := client.GetBucketWorm("examplebucket") if er != nil { handleError(er) }// WORM一旦提交锁定,在解锁日期前,WORM和Bucket内的所有Object均不支持删除。 // 但可以延长锁定期: err = client.ExtendBucketWorm( "examplebucket", 180, wormConfiguration.WormId, ) if err != nil { handleError(err) } }func handleError(err error) { fmt.Println("Error:", err) os.Exit(-1) }

日志
package mainimport ( "fmt" "os""github.com/aliyun/aliyun-oss-go-sdk/oss" )func main() { // 创建OSSClient实例。 client, err := oss.New( "yourEndpoint", "yourAccessKeyId", "yourAccessKeySecret", ) if err != nil { handleError(err) }// 开启 // 将examplebucket的日志存入destbucket。 err = client.SetBucketLogging("examplebucket", "destbucket", "log/", true) if err != nil { handleError(err) }// 查询 res, e := client.GetBucketLogging("examplebucket") if e != nil { handleError(e) } fmt.Println( res.LoggingEnabled.TargetBucket, res.LoggingEnabled.TargetPrefix, )// 关闭 err = client.DeleteBucketLogging("examplebucket") if err != nil { handleError(err) } }func handleError(err error) { fmt.Println("Error:", err) os.Exit(-1) }

细节 防止意外覆盖
默认情况下,如果上传的文件与已有文件同名,则覆盖已有文件。
为防止文件被意外覆盖,可以在上传请求的Header中携带参数x-oss-forbid-overwrite,并指定其值为true
避免修改路径
OSS可以通过一些操作来模拟类似文件夹的功能,但是代价非常昂贵。
比如重命名目录,希望将test1目录重命名成test2,那么OSS的实际操作是将所有以test1/开头的Object都重新复制成以test2/开头的Object
这是一个非常消耗资源的操作。因此在使用OSS的时候要尽量避免类似的操作。
性能与扩展性最佳实践
Object Key不要使用顺序前缀!!!
Object Key不要使用顺序前缀!!!
Object Key不要使用顺序前缀!!!
阿里云 对象存储 OSS

    推荐阅读