小结:
1)
-
单对象最大大小,如果超出该限制,则拒绝传输。类似nginx client_max_body_size配置项
-
单对象允许使用的最大内存大小,超出该限制则将数据放置到一个临时磁盘文件后再进行转发。类似nginx client_body_buffer_size配置项
2)
-
业务方通过S3协议上传下载文件、图片或者音视频;
-
存储服务入口处为限频模块,通过配置中心获取对应业务方的流量配额和带宽配额,配额之内的放行;
-
存储服务对业务方进行验签和鉴权,该模块可降级,降级策略通过配置中心控制;
-
协议转换模块首先通过配置中心获取该业务方对应的后端云平台存储,然后将S3协议和后端云平台协议进行转换和上传下载;
-
对象文件存储之后会通过消息中间件发送信息,触发多平台的同步以及多媒体处理
贝壳Go实现的多云对接存储网关建设 https://mp.weixin.qq.com/s/m2d4GRIB9uR2T3zidiTdHw
贝壳Go实现的多云对接存储网关建设
1、功能介绍
贝壳存储服务通过S3协议向业务方提供文件、图片、音视频的存储及下载。S3协议由AWS推出,在对象存储行业已经成为事实标准。腾讯云对象存储COS、阿里云对象存储OSS均兼容S3协议。
S3协议可以简单理解为一套webapi接口。通过调用接口,业务方可以进行对象数据的存取。每一个对象数据称为一个object,以一个唯一的ID来标识,object可以组织到一个bucket中,便于区分不同的业务场景。总结来说,S3就是一个用bucket组织数据,可以存储近似无限数量object的key-value(其中key为object的标识,value为存储的数据内容)存储系统。
从业务角度来说,数据需要做一些处理之后才会使用。以图片为例,业务方存储一张原图之后,实际使用时可能并不需要原图,而是根据场景进行缩放或者打水印或者格式转换之后下载使用,而且C端场景需要提高下载速度,就近下载。因此贝壳存储服务也提供了多媒体预处理以及通过CDN就近下载的功能。
贝壳存储服务稳定性可以达到4个9,存储近百亿数据,总数据量十PB级。
2、背景
存储网关作为一个底层服务,牵一发而动全身,其稳定性至关重要,初始存储网关架构如下图所示:
业务方通过S3协议上传数据到存储网关,存储网关进行验签、鉴权、文件大小及黑白名单检测之后转发到后端使用的云平台对象存储服务。作为一个存储网关,有如下两种关于大小的设置:
-
单对象最大大小,如果超出该限制,则拒绝传输。类似nginx client_max_body_size配置项
-
单对象允许使用的最大内存大小,超出该限制则将数据放置到一个临时磁盘文件后再进行转发。类似nginx client_body_buffer_size配置项
这些配置可以避免某个业务方上传文件过大影响存储网关稳定性。
任何一种架构都有其优劣势,贝壳存储网关作为一个存储统一入口,便于管理和控制,并且可屏蔽后端实际使用的云平台。因此如果有云平台迁移需求,可以做到业务方无感知,例如贝壳后端存储曾经从AWS迁移到腾讯云。
但其劣势也十分明显,任何集中式的服务都需要做好稳定性建设,防止爆炸半径过大,大面积影响服务。其次由于是统一入口,不在入口部署地域的公网上传会受限于公网网络质量,导致延时或者错误率增加。
存储网关稳定性建设涉及如下三个方面:
- 入口处:如果一个业务方持续上传大量的文件,占用存储网关资源或者业务方出bug,则直接影响其他业务方的使用;
- 验签鉴权:存储网关使用OpenIAM进行验签和鉴权,如果OpenIAM出现故障,则影响业务方使用;
- 后端云平台:如果后端云平台的对象存储服务出现故障,会直接影响业务方使用
也就是上图中标黄的三个部分。很不幸,2021年贝壳存储网关在如上三个方面都出现过故障。因此本文会重点介绍存储网关在稳定性方面的建设。
3、入口建设
入口处需要配置各个业务方的资源配额,防止业务方之间互相影响。除此之外,需要根据存储网关通过压测得出的最大容量做整体限制和报警,防止突增流量压垮整个系统。贝壳存储网关除了常规的QPS限制之外还增加了带宽限制。
3.1 限流
流量限制通过使用开源组件Sentinel实现,增加限流之后流程图如下:
一个请求首先是否触发bucket维度的限流,如果没有,则判断是否超过全局限流,两个限流都没有触发的话可以放行。Go语言十几行代码就可以实现一个限流方案,具体实现方法在此不再叙述,大家可参考Sentinel的实现或者网络上其他的一些案例。
3.2 限带宽
存储网关只有常规的限流并不能满足需求,大家可以想想nginx,作为一个http网关,nginx除了实现limit_req用来限制流量,还提供了limit_rate进行带宽限制。设想一个业务方流量限制为100qps,但每个请求都传输大小为10G的文件,则会将存储网关的带宽打满。
Go语言中,数据读取和写入通过Reader和Writer接口实现,其定义如下:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
因此能够通过做一层封装,实现Reader和Writer接口,每次读取或者写入时先判断是否超过了带宽配额,如果没有触发限制,才会进行真正的上传和下载。其原理图如下:
Reader接口实现如下:
type UploadBandWidtLimitReadCloser struct {
Bucket string
Payload io.ReadCloser
qpsLimiter *limit.Limiter
}
func (this *UploadBandWidtLimitReadCloser) Read(p []byte) (n int, err error) {
// bucket维度配额检查...
// 全局配额检查...
// 如果检查不通过,则使用sentinel的匀速排队方式等待,最多等待2min...
//实际读取
return this.Payload.Read(p)
}
带宽限制也按bucket和全局维度进行了配额配置,除此之外,还区分了上传和下载的维度,毕竟数据首先需要保证上传成功才能够下载使用。
流量和带宽的所有限制都保存在了配置中心Apollo中,可以动态调整。其中全局维度按存储网关的容量进行常态化开启,bucket维度按业务方平时使用量的3倍常态化开启。
4、依赖降级
贝壳参考aws的IAM建设了自己的验签鉴权系统,存储网关使用该套系统进行业务方的验签和鉴权。但IAM系统也会出现故障,因此存储网关需要设计降级方案。简单来说,可以直接通过设置降级开关,放行全部请求,如下配置:
openiamDegradeSwitch = false
openiamDegradeConfig = {"__default__": "allow_all"}
但这样有一定的风险,本来验签和鉴权就是为了安全,不能因为稳定性完全放弃安全性,尤其是存储网关可以通过公网访问,完全放行可能导致恶意上传,损耗成本并且增加合规危险。因此我们增加了如下的一些切换策略:
- 可以按公网和内网分别配置降级策略,例如内网完全放行,公网只有最近12小时有成功访问记录的AK才会放行;
- 按bucket维度进行降级,例如只放行某些指定的bucket
5、多云切换
多云时代,如果能够对接多个云平台,不仅有利于可用性,防止单点故障,还可以通过多个供应商的引入,通过竞争降低成本。随着公司的发展或者合规性的要求,有些数据需要保存在公司自己的IDC,此时多云对接利于引入自建对象存储服务,并且数据迁移可以做到业务方无感知。
受利于S3协议成为事实上的标准,我们可以直接对接阿里云、腾讯云或者开源方案chubaofs,甚至通过协议转换可以对接其他一些分布式对象存储服务。多个云平台只需要实现下列接口就可以通过配置后切换:
type StoreRepository interface {
Initiate(ctx context.Context, config *StoreConfig) (_ context.Context, err error)
// Bucket相关方法...
// Object相关方法...
CopyObject(ctx context.Context, input *s3.CopyObjectInput) (output *s3.CopyObjectOutput, err error)
DeleteObject(ctx context.Context, input *s3.DeleteObjectInput) (output *s3.DeleteObjectOutput, err error)
GetObject(ctx context.Context, input *s3.GetObjectInput) (output *s3.GetObjectOutput, err error)
HeadObject(ctx context.Context, input *s3.HeadObjectInput) (output *s3.HeadObjectOutput, err error)
ListObjects(ctx context.Context, input *s3.ListObjectsInput) (output *s3.ListObjectsOutput, err error)
ListObjectsV2(ctx context.Context, input *s3.ListObjectsV2Input) (output *s3.ListObjectsV2Output, err error)
PutObject(ctx context.Context, input *s3.PutObjectInput) (output *s3.PutObjectOutput, err error)
// 分片上传相关方法…
}
贝壳存储网关通过在Apollo配置中心设置开关,可以按bucket维度选择后端使用的对象存储服务。通过数据同步,还可以将关键bucket保存在多个对象存储,从而进行灾备切换。示意图如下:
云平台A故障时,可以进行读写切换。
6、监控报警
增加限流、限带宽以及依赖降级、多云切换之后,配置中心多了十几个开关。但开关还需要人来执行,因此需要相应的增加监控报警提前发现故障,并做好对应的预案。报警指标包括如下方面:
- QPS、带宽使用量
- 触发限频后的报警,包括bucket、时间、业务方
- IAM系统错误报警
- 云平台错误报警、连接池报警、响应时间报警
通过报警可以提前发现问题,发现之后有相应的处理手段,提高存储网关的可用性
7、新架构整体概览
存储网关整体架构图如下所示:
各部分介绍如下:
-
业务方通过S3协议上传下载文件、图片或者音视频;
-
存储服务入口处为限频模块,通过配置中心获取对应业务方的流量配额和带宽配额,配额之内的放行;
-
存储服务对业务方进行验签和鉴权,该模块可降级,降级策略通过配置中心控制;
-
协议转换模块首先通过配置中心获取该业务方对应的后端云平台存储,然后将S3协议和后端云平台协议进行转换和上传下载;
-
对象文件存储之后会通过消息中间件发送信息,触发多平台的同步以及多媒体处理
8、未来规划
存储网关后续会围绕着如下几个方面做建设:
- 平台化建设:对业务方友好,通过web平台可以进行bucket申请、bucket授权、资源转移、历史趋势观察、成本展现、流量和带宽配额展现、后端存储服务展现。也可以进行上传下载、文件分享。
- 边缘接入:上文介绍架构劣势时提过统一入口导致的上传局限性,跨地域上传一是会大量占用公网带宽,二是会有延迟和失败率高的问题。通过引入边缘代理节点可以加速上传。
- 降本增效:一是管理对象的生命周期,不常使用的可以存储到低成本服务器,二是探索多媒体处理的FAAS化,多媒体处理比较耗资源,并且高低峰明显,通过引入FAAS,不只可以满足高峰期的快速扩容,而且可以在低峰期极端缩容到0资源占用。贝壳正在进行服务的容器化建设,引入了K8S,基于此,探索多媒体处理的降本。
- 多媒体功能建设:图片的裁剪、缩放、水印、格式转换,音视频转码、变声、裁剪合并、缩略图,文件预览等等
9、总结
本文主要介绍了贝壳存储网关的使用场景、架构设计以及稳定性建设,其中稳定性建设是基础。除了稳定性,后续还会在易用性、功能扩充以及降本增效方面持续建设。