总篇31篇 2019年第5篇
1.1 存储相关概念
之家云.云存储采用开源的分布式存储Ceph ,根据业务的需求进行二次开发与优化,目前承载之家的计算服务,视频(短视频,小视频,视频,音频),ARVR,前端JS 等在线数据存储服务,覆盖之家多个业务场景,根据业务不同的场景定制化存储服务,满足业务的需求,帮助业务快速落地。
1)块存储:提供块存储服务,通过Pool来提供服务,业务只需通过key来操作Pool 下数据。
适用场景:OpenStack,Kubernetes 虚拟化服务。
2)对象存储:提供对象存储服务,通过租户来管理文件对象,业务只需通过AccessKey+SecretKey来操作文件的对象。
适用场景:适用于视频,短视频,音频,ARVR,js/css 等,适用于多种业务场景。
3)文件存储:提供文件存储服务,用户通过client IP mount到AFS MonIP上,即可实现文件读写操作,因文件存储底层要求,不建议在一台存储开启多租户模式,但可以托管独立部署单个租户使用。
使用场景:适用于文件归档,备份,临时文件存储。
4)S3基本概念Key
“key”是存储桶中对象的唯一标识符。在同一存储桶中,不会出现两个相同的“键”。对应的,存储桶内的每个对象都只能有一个键。例如,名为demo/test/images/somepic_5cab2a.png的对象存储在mall存储桶中,则可使用 URL:http://x.autoimg.cn/mall/demo/test/images/somepic_5cab2a.png来访问。
这里需要特别说明的是,在对象存储中没有目录的概念。在上述实例的存储key中出现的符号“/”仅仅是键中的一个分隔符(Delimiter),如果实现类似于操作系统中列出某一目录下文件的功能,对应S3的API,需要指定的条件为前缀(Prefix)。即“demo/test/images”只是键“demo/test/images/somepic_5cab2a.png”的前缀。
5)S3基本概念Object
对象是S3协议中存储的基本实体,由对象数据(object data)和元数据( metadata )组成。
1.2前端JS场景
因云存储在之家云适用多个领域,今天主要分享如何从传统的存储js,css,png(小文件)中迁移到之家云S3 存储中,涉及到相关技术积累,例如,Nginx URL多个合并,不同业务缓存配置,多种规则定制化,新老存储数据一致性等相关技术分享,传播,接下来让我们先了解技术架构
技术架构:
关键技术
1)数据同步
为了解决在迁移过程中无缝迁移,需要在迁移之前通过client 全量同步到S3 bucket中,在过渡过程中还需要做到实时双向同步,避免数据丢失。
2)Combo 技术
1.背景
- Combo为汽车之家自研的一款前端资源动态合并工具。实现了任意css/js文件的动态合并,结合CDN使用,加快用户端的静态资源的加载速度。
- 老版Combo为C#实现部署在windows server上,后端挂载静态存储,随着公司的发展,业务结构调整。需要将存储资源迁移到Ceph集群并将Combo服务迁移到linux server上。
2.业务流程
3.技术实现
- 核心代码如下, 主要实现了从Ceph集群拉取资源文件并实现资源内容的合并
-- func gen files
_M.gen_files = function(_files,_contentype,_charset)
-- localstarttime = ngx.now() * 1000
local all = {}
local headers ={
["Content-Type"] = _contentype,
["Host"]= conf.s3_host,
}
--local real =_M.delete_duplicate(_files)
for i=1,#_filesdo
localurl_prefix = conf.s3_ip..conf.s3_url_prefix
if_M.is_no_url_prefix(_files[i]) then
url_prefix = conf.s3_ip
end
localstatus,headers,body = _M.http_get(url_prefix.._files[i], headers)
plog(ERR,status)
if status== 200 then
table.insert(all, "/* Append File:".._files[i].."*/")
if_M.is_utf8_bom(body) then
--body = _M.remove_utf8_bom(body) bug TODO
body = _M.do_iconv("utf8","gb2312",body)
body = _M.do_iconv("gb2312",_charset,body)
end
if_M.is_utf16(body) then
body = _M.do_iconv("utf16",_charset,body)
end
--plog(ERR,string.byte(ret,1))
--plog(ERR,string.byte(ret,2))
--plog(ERR,body)
table.insert(all,body)
else
plog(ERR,status)
plog(ERR,"get file from s3 error,please check s3.")
table.insert(all,"/* Path Not Exist:".._files[i].."*/")
end
end
--ngx.update_time()
-- localendtime = ngx.now() * 1000
return table.concat(all, "\n")
- 程序入口如下:
location~* ^/(com)|(comu)/ {
add_header Cache-Control "public, max-age=31536000";
#add_header Last-Modified $date_gmt;
set $new_content_type ''; content_by_lua_file/usr/local/openresty/nginx/conf/combo/combo_content.lua;
header_filter_by_lua_file /usr/local/openresty/nginx/conf/combo/combo_header.lua;
}
- 配置文件如下:
-- Copyright 2018 AutohomeInc.
-- Author :lijian0423@autohome.com.cn
-- Describe :concat static files
return {
--1 设置 支持的 MIME types
mime_types ={"css:text/css","js:application/x-javascript"},
--2 设置 是否允许混杂不同的MIME types 允许为true,不允许为false
different_mime = false,
--3 设置 最大能接受的文件数量
max_files =50,
--4 设置 不通文件之间添加分隔符
file_separator = "\n",
--5 设置 是否忽略读取错误的文件,忽略为true,不忽略为 false
ignore_error_file = true,
--6 设置 允许的uri路径
allow_uri ={"/com/com.ashx","/com/bo.ashx","/com/co.ashx","/comu/bo.ashx","/comu/co.ashx"},
--7 设置 静态资源所在位置
s3_ip ="source_s3_site",
s3_host ="xx.s3.xx.com.cn",
s3_url_prefix = "/x.autoimg.cn",
s3_no_url_prefix = {"/mall"}, --已字典中配置开头的资源去掉 x.autoimg.cn url前缀
--8 设置 后端请求s3存储的http客户端连接池大小
http_client_pool = 100,
--9 设置 后端请求s3存储的http客户端连接保活时间
http_keepalive = 6000,
--10 设置 后端请求s3存储的http请求超时时间
http_timeout= 3000,
}
3) 缓存策略
为不同业务定制不同的缓存策略,满足特殊业务场景的需求
举例1:设置以下业务的缓存策略
举例2:全局缓存设置
举例3: com|comu 缓存配置
1.3 电商业务迁移实战
接入过程
1) 背景
随着“车商城”项目的日益迭代,频繁的需求上线带来的是前端资源发版频率的加快,这些前端资源(js、css及其引用的图片等资源)也非常消耗存储空间。而原有的CDN源站只提供了FTP服务,对应的磁盘空间即将耗尽。如果将源站存储方式换为对象存储,今后可以根据磁盘使用情况动态附加存储空间。
另外也是基于公司技术方面的云平台建设战略。将基础服务和公共服务“云化”有助于节省各个BU的相关成本。由于S3服务可以有完整的计费模式,每个业务线就能够据使用情况承担成本。多用多付、少用少付。
安全性方面,基于S3协议的ACL(Access Control List 访问控制列表),可以实现更加细粒度的访问权限控制,这一点优于FTP服务。
2) 现状
在未接入对象存储时,整个前端资源的发版流程为:
- 编译:前端样式和逻辑开发好之后进行一些js、css的uglyfy,并且针对文件名加入MD5作为版本号;
- 上传:通过FTP方式上传到指定源站服务器;
- 源站:Nginx通过静态方式映射FTP的上传目录,对外提供对应文件的传输。这里要额外提到一点,为了加速多个js、css批量加载的场景,在源站上提供了一个资源合并接口。当需要先后加载/demo/script_1.js和/demo/script_2.js时,传统的页面写法为:https://x.autoimg.cn/demo/script_1.js和https://x.autoimg.cn/demo/script_2.js。这样的写法使得连接握手次数比较多,造成传输性能损失。而资源合并接口允许以https://x.autoimg.cn/com/co.ashx?path=|demo|script_1.js,|demo|script_2.js的形式加载。接口内部将需要加载的资源拆解,在机房内部进行内容合并,将合并结果一次性返回给浏览器。这样只需要一个握手交互周期即可加载多个文件。
- CDN:作为大型网站架构中重要的组成部分,无论是单次请求一个文件,还是合并请求,最终的结果都会被缓存到CDN,从而对源站进行分流。
3) 初试对象存储
在运维同学帮忙搭建好Ceph集群后,使用分配给我们的租户创建对应的存储桶。本以为接入过程会非常顺利,将原有FTP中的文件同步至该存储桶即可。最后再改一下Nginx配置,将原有https://x.autoimg.cn/mall下的请求转发至该mall存储桶中的对应键。然而,由于资源合并接口开发时间在多年以前,当时没有考虑到存储扩展的情况,内部采用直接“本地”(FTP服务根目录映射到静态服务的虚拟磁盘上)打开进行读取及合并操作的。因此在接入对象存储后,该接口就不能用了。这种状态是不可接受的。因为已经发布出去的页面不计其数,都在使用这种方式加载。由于当时时间紧迫,采用了一种折中的办法——改造自研的前端发布工具。在资源编译完成之后,将比较占空间,但是不需要资源合并的图片文件传输到对象存储中,而传统的js、css文件继续使用FTP方式上传。而对于Nginx下的转发规则也根据请求结尾的文件扩展名做了相应的调整。此时的架构如下图所示:
4) 釜底抽薪
经过上面的折中方案,确实减轻了一部分FTP源站的存储压力,但是并没有从根本上解决横向扩展的问题。纠结其原因,还是资源合并接口不支持对象存储系统。经过运维同学和资源合并接口的维护人员(来自技术中台部)不断努力,终于将此接口成功改造,支持了S3集群的访问,为了印证效果,运维同学进行了一次无感知升级。也就是在FTP服务器与Nginx服务器之间增加了一个S3集群,并通过热同步的方式将FTP服务器中的内容增量同步到该S3集群中。对于开发人员,这个升级是无感知的,js、css依旧通过FTP方式上传,而真正对外提供服务的,是S3集群。具体架构如下图所示:
后续经过一段时间运行没有问题后,逐步将FTP服务器关停,开发工作机将所有资源上传至S3集群公共中。
5) 追求极致
作为云平台建设的一部分,S3公共集群是要面向公司所有BU的。目前还在逐步完善的过程当中,没有完全开放租户密钥信息。而新车电商事业部希望通过标准Amazon S3 SDK的方式,将这一存储服务集成到前端发布工具中。为此,运维小伙伴特别建立了S3集群B以及对应的资源合并接口部署。创建了独立的租户给这边使用。那么接下来的任务就是将原有的资源进行整体接入,涉及到的内容为:
1. FTP服务器中的css、js与S3集群A中的图片资源合并,从而生成全量数据;
2. 将全量数据同步到新的S3集群B中。
3. 修改https://x.autoimg.cn/mall下的请求转发至S3集群B
经过技术调研,选定了现有的两款同步工具作为对比。一个是Amazon官方的aws cli,另外一个是专门针对S3服务的s3cmd。这两款工具都是基于Python实现的。作为文件同步这一单项任务来说,aws cli存在一些问题,或者说做的不好的地方。由于aws cli主要面向的是Amazon自己的云服务,所以一个集群的默认端点url都是和region有对应关系的,因此当一个工具在维护多个集群的连接时,存储凭据的credential和config文件都没有地方配置自定义的endpoint url。而目前汽车之家使用的Ceph实际上是Amazon S3协议的开源实现,必须指定自己的服务域名为endpointurl。虽然aws cli提供了—endpoint-url参数来特别指定,但是每次运行时会非常麻烦(此问题已经提交过issue了:https://github.com/aws/aws-cli/issues/1270,但官方似乎没有要解决的趋势)。更为严重的是在每次同步时,似乎并没有做到增量同步,无论是远程同步到本地,还是本地同步到远程都采用了全量同步,这对于有着几十万文件的场景来说会异常耗时。而且某些情况下从远程同步到本地每次只处理1页的数据(默认1页是1000个对象,应该是个Bug)也是不可接受的。
最终采用的同步工具是s3cmd。同步的方案为:
4. 将FTP同步到本地目录A
5. 将本地目录A同步到S3集群A,此时S3集群A中即保存了全量数据
6. 将S3集群A中的数据同步到本地目录B
7. 将本地目录B同步到S3集群B中,之所以S3集群A同步到S3集群B要通过本地目录B 中转是因为S3协议还不支持两个不同集群间的同步。只支持:
8. 本地目录同步到S3存储桶;
9. S3存储桶同步到本地目录;
10. 同一集群下的S3存储桶A同步到S3存储桶B
这个方案中涉及到了两个集群,s3cmd工具支持通过文件加载连接参数:
exportCONFIG_FILE=/home/backup/s3_cmd_cfg/s3_cluster_a.cfg
s3cmd -c $CONFIG_FILE sync/home/backup/orignal_ftp/ s3://mall/ --acl-public --skip-existing
这里有个细节需要注意,就是来源目录一定要以“/”结尾,否则就会在mall存储桶下先创建orignal_ftp前缀,再向后同步。也就是说会多出一个层级。在同步之前一定要先确定好层级对应关系。因为同步的是一些前端资源,这些资源在匿名状态下必须能够读取,因此需要加上--acl-public参数。--skip-existing参数用来指示增量同步。目标中已经存在的文件将不会再被同步。
割接执行时停止FTP对外提供服务,杜绝了产生增量文件的情况。执行最后一次增量同步,而后将流量切换到S3集群B。后续上传文件直接通过S3集群B的连接参数上传即可:
1.4 额外分享
前文中已经提到,汽车之家云平台使用的S3开源实现产品为Ceph。但如果你仅仅想调试客户端API,或者体验对象存储,该产品就显得稍微有一些笨重了。类似的产品还有Minio Object Storage(https://minio.io/)。它是基于Go语言实现,轻量级作为本地调试,非常方便。至于S3客户端SDK,如果你的开发语言是Java、Node JS,那么推荐使用Amazon官方的SDK,虽然aws cli有一些问题,但Java和Node JS版的SDK还是值得用的。
1.5 感谢
首先要感谢技术部的系统平台团队研发的之家云平台,有效降低了总体基础设施的维护成本,尤其感谢网站运维组在新车电商事业部的对象存储接入过程中提供的无私帮助。由于接入不能影响原有页面的访问,新车电商事业部的质量保证团队做了很多相关测试工作,你们都是最棒的!