对象存储
对象存储是云上三大件(计算、存储、网络)之一,是云端时代必需要掌握的的基础服务之一。
对象存储,其实并不是什么新鲜事。早在 2006 年亚马逊就推出了这项服务,并命名为 Simple Storage Service(S3),S3 的出现甚至早于 EC2,被称为现代互联网正真意义上最早的云服务。
对象存储服务实际上是一种存储方式,有别于其他存储技术,对象存储是一种扁平结构的分布式存储,其中的文件被拆分成多个部分,并散布在多个硬件间。在对象存储中,数据会被分解成被称为“对象”的离散单元,并保存在单个存储库中。由于对象存储是一种分布式存储,理论上,对象存储的总空间是无限大的。
对象存储通过标准化的 RESTful API 给使用者提供服务。我们可以通过对象存储生成的唯一 URL 地址访问文件。
对象存储的基本概念
访问域名根据不同云厂商和不同地域组成,如阿里云杭州地域是 oss-cn-hangzhou.aliyuncs.com,AWS 美西二区是 s3.us-west-2.amazonaws.com。对象存储的访问域名由不同云厂商定义,不可更改。
https://bucketname.oss-cn-hangzhou.aliyuncs.com/filename
协议名://存储桶名.访问域名/对象名
以阿里云对象存储为例,我们在其杭州地域创建一个名叫 geektime 的存储桶,往这个存储桶中创建一个叫 geekbang 的目录(注 1),上传一个名叫 geeker.txt 的文件(注 2),那么这个文件生成的唯一地址是:https://geektime.oss-cn-hangzhou.aliyuncs.com/geekbang/geeker.txt
这样,我们就可以在互联网上,通过 URL 去访问这个文件。
注 1:这里虽然文字表达为创建一个目录,但仅对人类做可读性表达。在对象存储中没有文件系统中所谓的目录的概念,存储空间中所有文件都是扁平化存取的,而不是分层查找。
注 2:geeker.txt 这个文件,在文件存储中称之为对象(object),一个对象是由对项名(key)、对象值(value)以及元数据(meta data)所构成,其中元数据包含了对象的一些熟悉,比如最后修改时间、大小等信息。
对象存储的特性
由于对象存储和文件存储不同,所以对象存储有以下几种特性:
- 一般特性:
- 对象存储空间理论上是无穷大的;
- 对象的名字需要全局唯一;
- 总费用 =(存储费用 + 请求费用 + 数据传输费用)+ 传输加速费用 + 存储管理费用。
- 操作特性:
- Object 操作具有原子性,操作要么成功要么失败,不会存在有中间状态的 Object。一旦上传完成,对象存储需要保证用户读到的 Object 是完整的,不会返回给用户一个部分上传成功的 Object;
- Object 操作具有强一致性,用户一旦收到了一个上传(PUT)成功的响应,那么上传的 Object 就会立即可读,并且 Object 的冗余数据已经写成功。不存在上传的中间状态,也就是说,read-after-write 无法读取到数据。对于删除操作也是一样的,用户成功删除指定的 Object 之后,那么这个 Object 就会立即变为不存在;
- S3 具有写后读一致性(Read after Write consistency)和最终一致性(Eventual consistency)。当向存储桶写入一个新数据,数据立即生效。这是写后读一致性。当向存储桶进行覆盖操作时,可能不会立即生效。因为数据还没同步,需要等待一段时间后才会生效,但是最终一定会生效,这是最终一致性。
对象存储作为 PaaS 服务,给我们提供了多种功能,比如存储层分类、版本控制、生命周期管理等,这些是最标准、最常用的功能。
对象存储的存储层级、生命周期及版本控制
下面,我们就以阿里云对象存储(OSS)为例,来讲解对存储的特性。对于阿里云的对象存储层级,阿里云把它分为了四种类型,分别是标准、低频、归档、冷归档四种层级。每种类型都有优劣。越冷的存储,存储的容量费用越低,但是取回的费用越高,同时需要解冻的时间也越长,存储的附加要求也越高。
通常来说,我们会使用标准层来存取经常访问的数据,比如一些用户的头像、文章图片、商品详情等,这些都是经常访问的。而一些资料数据,如金融公司的审计,医院的档案。这些需要长期存储但不需要立刻访问的,我们可以使用归档层或者冷归档层,容许提前一定的时间(如几分钟至几小时)解冻再取回,这样既减少了成本,又优化了资源,算得上是对象存储的最佳实践了。
当然,如果你不希望手动去分层,而想要实现定期地将数据一层层降温,就可以使用对象存储中的“生命周期管理”功能,让对象存储自动地去进行不同时间的归纳。
另外需要说明的是,在默认情况下,新上传的同名对象存储文件会覆盖掉旧的同名文件,如果我们想保存每一个历史的版本防止误操作,我们可以开启版本控制功能,这样,无论我们是更新、删除、还是覆盖都会自动生成唯一的版本 ID,这样便于我们查找和恢复旧版本。
对象存储的计费方式
在使用对象存储的过程中,一定会产生使用费用,OSS 的计算公式如下:
总费用 =(存储容量费用 + 请求费用 + 数据传输费用)+(传输加速费用 + 存储管理费用)等其他费用
其中,存储容量费用、请求费用、公网流量费用是基本的费用,而传输加速、图片处理等是高级增值服务,如果我们不需要使用高级服务,那以上公式可以简化为:
虽然 OSS 存储计费方式复杂,但使用费用并没有想象中那么贵。因为 OSS 是典型的按量付费,也就是不存储不收费,存储多少收多少的使用模式。比如 1T 的存储,如果我们购买云盘,无论存储空间是否存满,都需要收 1 个月的磁盘费用 373 元。但对象存储,使用 1G 只需要 0.12 元,就算是每月使用满 1T,总费用 122 元,也远比使用云盘 373 元便宜。如果我们购买相关的包月资源包,价格会更加优惠。
这是价格方面的优势,当然剩下的请求费用和公网流程费用也要计入进去,这里我们只说大概费用,具体费用需要通过业务去估算,从整体上看,使用 OSS 的费用要比使用云盘的费用便宜得多。
对象存储的使用技巧
对象存储的优势不仅仅体现在费用方面,如果说使用对象存储就是为了节省费用,那格局就小了。使用对象存储的好处,是在于能够使用云化的服务,通过云产品自带的特性,发挥优势,把它运用得炉火纯青。
下面,我通过一个例子,来讲解怎么高效的使用对象存储。
在对象存储中,附带了数据处理的功能,在访问 URL 的时候,在 URL 后加上参数,就可以使用图片处理模块对图片进行处理。
我们在图片的 URL 的后面加入代码,代码的意思是使用 GET 方式调用图片,并对 OSS 中的图片进行处理。
?x-oss-process=image/circle,r_100 # 将图片裁切成圆形
于是,我们就得到了处理好的图片。
除了裁切,还有很多图片常用操作,比如:
?x-oss-process=image/rotate,90 # 将图片顺时针旋转 90°
?x-oss-process=image/contrast,-50 # 降低图片对比度
?x-oss-process=image/bright,60 # 提高图片亮度
?x-oss-process=image/format,png # 将图片格式转换为 png 格式
?x-oss-process=image/resize,p_50 # 缩放图片为原图的 50%
那么在对象存储中直接处理图片有什么好处呢?以往我们需要通过自己编写代码,裁切、翻转、处理图片。而如今,我们可以省略这些步骤,把这部分工作交由云上的服务进行处理,我们就不需要重复“造轮子”了,减少了自身服务器的计算压力。
拿一个近期最火爆的小应用说起,圣诞节将近的时候,很多用户想要在头像上加上挂件装饰。通常思路是,用户上传一个图片到业务服务器上,业务服务器运行一段处理图片的代码,图片处理好后,把它传输到对象存储中进行持久化保存,然后 OSS 返回 URL 给到业务服务器,业务服务器收到响应后返回给用户,客户收到处理后的图片,流程完成。这样,需要我们程序员进行图片处理程序的开发,开发完成后再 push 发布。如果代码中有问题可能还需要回滚重做,特别麻烦。
使用了对象存储中的数据处理模块后,我们可以把业务处理模块功能“转嫁”给 OSS,也就是说,让云厂商来承受这部分的算力,这样,我们的服务器可以腾出资源运行更多的服务,或者可以减小服务器的配置,节省企业的成本。
临时管控访问方式
通过观察架构,我们发现,以前的业务服务器部分,我们是充当了处理图片的角色,也就是说,必须由“工人”来处理图片。而使用了 OSS 对象处理后,我们的图片处理工作可以不在业务服务器上进行。那么是否能够简化业务服务器中这个图片处理工人的角色,优化结构,减轻其工作量呢?当然有,这个时候我们就要换一种思路。
既然上传文件可以直接上传到 OSS 中。那么也就是说,完全可以不经过业务服务器。直接把图片给到 OSS 中进行处理。这样除了能够减少自身业务服务器的压力外,也有一些其他的好处。
首先,以前的架构,用户需要把数据传到应用服务器中,应用服务器再传到 OSS 中,从步骤上来说,如果通过 OSS 直传,减少一步,可以提高效率,提升速度。
其次,使用改造后的架构,在费用上,可以减少服务器的配置费用。
最后,也是解决最关键的一点,就是性能瓶颈问题。如果我们所有的用户流量都从服务器走,那么服务器就会成为花瓶中最细的口,限制性能。一旦用户增多,就要优化程序或升级服务器配置,这样又回到了升配置和改代码的死循环中。使用新架构,就能解决这个问题。
所以,我们把客户端的上传和处理直接指向对象存储,对象存储处理完再直接指回客户端。这就解决了刚刚提到的问题,但同时又会产生新问题。
- 客户端直接指向对象存储,对象存储如何保证客户端的可信度?
- 客户端与对象存储直接交互,所有数据不经过业务服务器端,业务服务器端如何知道对象存储已操作完成?
这时候,就是 STS(Security Token Service)大显身手的时候了。STS 提供了临时的访问授权能力,从 STS 获取的权限会受到严格的访问限制,通过使用 STS 返回临时的 AK 和 Token。这些 Token 自带短时失效的访问限制,又能够保证每个用户中的数据隔离,充分保障其安全性。这就解决了我们上述的第一个问题。
而第二个问题,我们可以通过 OSS 自带的回调方案来实现。在客户端上传文件、OSS 端返回结果前,触发回调函数,回调给服务器,得到服务器返回的内容后再将内容反馈给客户端,这样就能让客户端、业务服务器、对象存储实现闭环,达到跟踪检测客户端上传的文件的目的。
修改后的架构图如下:
对象存储六大权限管控策略
刚刚说到在需要临时访问的情景下,我们可以使用对象存储的 STS 进行访问授权,那么是否有别的临时授权访问方式能够实现临时访问呢?
为了说明对象存储的权限安全,我画了个思维导图帮助你理解这个问题。
对象存储针对 Bucket 和 Object 提供了访问策略控制(Access Control Lists),这个策略既可以针对 Bucket,又可以针对 Object。通常来说,对象存储的 Object 权限继承 Bucket,但是,如果为 Object 设置了专门的权限,那 Object 不受 Bucket 权限的控制。
对于对象存储的 ACL,访问的颗粒度较粗,只有如上几个读、写等权限,它做到的是对所有人的访问控制。当我们需要做到更加细致地访问控制,比如对某个部门的某个 RAM 子账号,或者某个资源进行特定的操作权限管理,比如只允许上传,不允许删除等特定访问的控制,我们就需要使用基于用户的授权策略 RAM Policy 和基于资源的授权策略 Bucket Policy 了。
你可以看一下,这是一个 19000000000002 账号下 bucket 名称为 geektime 的存储桶,下面的代码块中,我列出了它的访问策略,你可以仔细阅读一下。它以 JSON 为格式,是一个通过对不同字段的定义来实现精细粒度的访问控制。
# 基于用户的授权策略 RAM Policy
# 对 geekeer 资源完全访问权限
{
"Version": "1", # 版本号
"Statement": [{ # 描述
"Effect": "Allow", # 效能
"Action": [ # 行为
"oss:*"
],
"Principal": [ # 授信对象
"*"
],
"Resource": [ # 资源
"acs:oss:*:19000000000002:geektime",
"acs:oss:*:19000000000002:geektime/*"
]
}]
}
而如果我们想设置对 bucket 只读操作,我们可以把“Action”中的 oss:* 改为只读的行为。同时再修改“Resource”中的字段,就能实现特定资源特定操作。
# 基于资源的授权策略 Bucket Policy
# 对 geektime 设置只读权限
{
"Version": "1",
"Statement": [{
"Effect": "Allow",
"Action": [
"oss:GetObject", # 只读 action
"oss:GetObjectAcl",
"oss:ListObjects",
"oss:RestoreObject",
"oss:GetVodPlaylist",
"oss:ListObjectVersions",
"oss:GetObjectVersion",
"oss:GetObjectVersionAcl",
"oss:RestoreObjectVersion"
],
"Principal": [
"*"
],
"Resource": [
"acs:oss:*:19000000000002:geektime/*"
]
}, {
"Effect": "Allow",
"Action": [
"oss:ListObjects", # 只读 action
"oss:GetObject"
],
"Principal": [
"*"
],
"Resource": [
"acs:oss:*:19000000000002:geektime"
],
"Condition": {
"StringLike": {
"oss:Prefix": [
"*"
]
}
}
}]
}
如果你还想加入更多的策略,你可以尝试继续细化 policy,对不同的 RAM 账号、不同的 bucket、不同的操作实现精准控制。
很多时候,我们必须要将文件设置为公共读,方便网站的业务访问,但会存在被恶意盗取链接消耗流量的问题。这个时候我们使用基于对象存储的防盗链白名单,把允许访问的自家网站添加到 OSS 防盗链设置中。这个时候,他人盗取链接进行访问,就会提示错误,让他无法访问。
以上服务控制的方法都是永久生效的,如果不手动修改,那策略会长期存在。有时我们会有临时访问的需求,这个时候,我们就可以使用 STS 或者签名 URL 来获取临时权限。
STS 能够解决账号安全的核心问题——如何在不暴露账号 AK 的情况下安全地授权临时访问。如图所示,客户端向服务器发送登录请求和上传请求,服务器申请并返回 STS 凭证,凭证里包含了安全令牌。临时 AK/SK 以及失效时间等信息,客户端通过这个请求去访问对象存储。而对象存储只需要验证这个临时的 STS 来进行验证,一旦匹配成功,就继续后面的响应操作。
这是 STS 的授权路径示意图,结合客户端,就可以构建类似图 1 的访问架构。
另一种是分享的场景,比如,微信经常有文章预览页,或者是你希望把图片分享给朋友,设置成超过特定时间失效,你就可以使用签名 URL 进行设置,并且这里的权限是最高权限,即使 object 是私有的,设置了签名的 URL,就可以进行公共读访问。
签名 URL 有 3 个参数**Expires、OSSAccessKeyId、Signature。**Expires 就是我们所说的失效的时间,取时间戳格式,可自定义时间。OSSAccessKeyId 是 RAM 账号的 AK,Signature 为特定算法计算出来的签名值,三者共同组成临时签名 URL。