背景
作者在实际的工作当中遇到越来越多关于 S3 的实践问题,其中被问的最多的也是使用最广泛的工具就是 AWS S3 CLI 命令行;AWS S3 CLI 命令行工具对大文件提供了默认的分段上传下载的能力,同时支持并发上传下载多个文件;AWS S3 CLI 命令行不仅仅提供了高级抽象的 cp、sync 等操作同时还提供了相对底层的 s3api 相关的操作以帮助客户应对各种高度定制化的应用场景。
本文通过实验帮助大家更好地理解 AWS S3 CLI 常用命令的底层原理及在 AWS EC2 上使用该命令行工具与 AMAZON S3 交互场景下,影响性能的几个关键要素及几个常见 EC2 实例类型上的上传下载测试性能情况。
本文是 AMAZON S3 深度实践系列之一,接着作者会带着大家从 AWS CLI 探索到 Python boto3 S3 多线程应用开发实践,再到如何利用前面学到的知识,基于 AWS 平台利用托管服务构建一个实用的跨区域迁移 S3 数据的解决方案架构探讨及实践。
基本概念
并发上传 vs 分段上传
刚刚使用 AWS S3 命令行工具的时候,总是混淆分段上传和并发上传的含义;分段上传是指将单个大文件切分成多个小数据块进行上传,而并发上传特指有多个待上传文件或小数据块的情况下,利用多线程同时并发上传多个文件或小数据块。
如下图所示,分段上传首先将文件切分成固定数量的小数据块,再利用多线程并发上传这些小数据块,等 S3 收到该文件所有的数据块之后再将它们合并成原始的文件存放在存储桶里。分段上传功能默认会帮你实现并发上传;这样做的好处,显而易见,既可以充分利用网络带宽进行文件处理,同时还可以在网络带宽有限的情况下,减小因网络抖动导致的整个大文件需要重传的问题,即当分段上传中的某个数据块因各种异常没能上传成功时,只需要重新尝试上传该数据块即可。
分段上传 vs 断点续传
AWS CLI S3 命令行工具默认并没有帮大家实现断点续传的功能,也就是说哪怕我们用 cp 或 sync 命令上传一个文件的时候,默认后台会做文件分片进行分段并发上传,但如果由于网络抖动导致其中某些数据块传输失败,那么整个文件又得需要重头开始上传。
但同时我们可以利用 AWS CLI s3api 底层接口非常容易地实现断点续传,后面章节我们再展开探讨。
AWS CLI S3 cp 命令是如何工作的?
AWS S3 cp 命令是最常用的单文件和多文件复制方法,很多人使用过该命令,但大家知道该命令是如何帮我们执行文件复制的吗?
该命令有帮我们自动做分段上传和下载吗?
分段上传切分文件的依据是什么?每个数据块大小是多大?
这么多数据块,有多线程并发上传吗?我们可以指定线程数量吗?
多个文件的上传下载是如何工作的?
下面我们通过实验来观察和研究下这些问题,整个测试环境基于如下 Amazon Linux 上如下版本的 AWS CLI 命令行:
Java
AWS EC2 机型是 R4.2xlarge,挂载了一个 500GB 的 gp2 测试盘
本地单文件上传场景
第一步,我们首先生成一个 48MB 的本地测试文件,并通过 cp 命令上传到 S3 存储桶,并通过 debug 开关把详细的日志记录到本地的 upload.log 文件中,详细命令如下:
Java
第二步,我们通过底层的 s3api 命令来了解存储到 S3 上的对象的基本情况,如下所示,通过 head-object 命令我们可以了解该文件的最后修改时间,对象大小以及一个用于完整性校验的 ETag 值,那我们如何知道该文件是否是分段上传的呢?
Java
要知道该文件是否是利用了分段上传,我们只需要在以上命令中加一个参数就可以判断,如下所示,如果该文件是利用分段上传的功能,通过 head-object 查询第一个数据块的基本信息就可以得到该文件一共包含多少个数据块,测试文件 48Mfile 一共包含 6 个数据块(PartsCount)。
Java
这里我们可以得知,默认情况下 cp 命令就会采用分段上传功能。而且从以上命令返回信息我们可以看出来,该文件第一个数据块分片大小是 8388608 bytes(ContentLength)即 8MB,48MB 大小的文件一共被分了 6 个数据块,所以,可以大胆猜测默认的 AWS CLI S3 命令行工具默认的文件分片大小就是 8MB。
第三步,我们已经判断出 cp 命令的默认是分段上传,接下来我们通过第一步保存的详细日志(该日志文件比较大,可以点击下载)来分析下,cp 命令基本的工作流及它采用的多线程并发上传的具体情况:
通过该实验的日志,我们基本了解了 cp 命令的基本内部流程,因此,这里面有些参数肯定是可以通过配置来进行修改的,接下来我们来根据官方文档:http://docs.aws.amazon.com/cli/latest/topic/s3-config.html 的说明来试验下,修改相应配置,是否如我们所理解的那样运行,这几个参数是:
接下来我们修改参数并对比下,实验内容为同样大小的本地文件在不同参数条件下,通过 cp 上传到 s3,两次运行的结果对比:
在 AWS Configure 配置文件中,指定 S3 的配置参数:
Java
执行 profile 为 dev 的上传测试命令:
Java
对比 upload.log 和 upload2.log,我们可以看出,multipart_threshold 和 multipart_chunksize 参数是对分段上传的影响是很好理解的,但 max_concurrent_requests 参数与单个文件的分段上传的并发线程总数的关系,从日志中只能看出单文件的分段上传的并发线程总数受 max_concurrent_requests 参数影响,但并发线程的总数上限值还取决于文件分片后进入队列被消费者线程消耗的速度。
感兴趣的读者可以在以上实验的基础上,保持其他参数不变,只修改 max_concurrent_requests 参数,观察并发线程数的情况,作者在 max_concurrent_requests 参数值分别为 8、15、20、30 的情况下观察 cp 同样的文件的线程数情况如下:
Java
对于单文件上传,我们经过前面的实验有了基本的认识,那我们接下来再看看 cp 命令在分段上传中途发生网络故障,能否实现类似断点续传的功能效果呢?
整个实验思路如下:
利用 dd 命令产生一个 26GB 大小的文件
在 cp 传送中途强行断开
检查此时在 S3 桶里面的分片情况
尝试再次上传并观察结果
Java
AWS CLI s3api 提供了 list-parts 和 list-multipart-uploads 两个命令分别可以查看已经完成的分片上传及正在进行的分片上传:
Java
list-multipart-uploads 命令会列出指定的 bucket 桶里面所有的正在进行上的分片信息,如上所示,对我们有帮助的是其中的 UploadId 的值,在执行 list-parts 命令时需要传入:
Java
打开 parts.info 文件可以看到所有的已经上传好的分片信息,包含每个分片的最后修改时间,大小,ETag 以及 PartNumber:
接下来,我们看看如果再次运行同样的 cp 命令,会帮我们进行断点续传吗?判断逻辑有两点(1)两次的 UploadId 是否一致(2)同一个 PartNumber 的数据块的最后修改时间是否一致。先记录好目前的这两个值:
UploadId:
Java
选取 PartNumber=1 的数据块,该数据块最后修改时间是:
Java
重新执行一边 cp 命令并记录结果:
Java
从结果我们发现,有两个正在上传的数据分片,一个是我们前一次命令产生的,一个是最近一次 cp 命令被中断产生的。
Java
我们会发现执行两次 cp 命令对同一个文件,会帮我们保留两个 UploadId 及其对应的已经上传的分段数据,根据 complete-multipart-upload 的文档说明,我们知道,分段上传在合并分段数据的时候,是根据 UploadId 进行合并的,两个不同的 UploadId 说明 AWS CLI cp 命令不会智能帮我们判断已经上传的分段之后开始续传我们的文件分片即没有直接支持断点续传。
一个文件如果经过分段上传了一部分数据但没有传完的情况下,已经传完的数据块和正在进行上传的数据块占用的存储是需要收费的,因此,我们需要清除掉这些无用的数据块,一种办法是通过命令,另外一种方式可以利用 AMAZON S3 的生命周期管理定期自动删除掉这样的数据块。
Java
S3 下载到本地单文件场景
该场景下,我们关注的点主要是,对于在 S3 桶里面的对象,cp 命令都会自动帮我分段分片下载吗?
首先尝试通 cp 直接下载上一个章节通过 cp 命令上传到 S3 桶里面的对象:
Java
从日志文件里面可以看出,cp 命令确实是默认采用了分段下载,调用 GetObject 接口,在 Header 里面设置 range 值并通过多线程并发下载;似乎非常完美,但等等,我们还忘了一个场景,假如文件上传到 S3 桶的时候没有使用分段上传呢?我们试验一下:
还是利用本地 dd 出来的 48Mfile 的文件
修改 AWS CLI S3 的参数,将 multipart_threshold 改到 50MB,这样对于小于 50MB 的文件上传就不会采用分段上传
cp 上传该文件并确认该文件没有被分段上传
cp 下载,看看是否是分段下载
第一步,修改 aws configure 配置文件:
第二步,通过 cp 上传该文件,并通过 head-object 命令发现该文件没 PartsCount 信息即没有被分片分段上传(因为前面我们设置了自动分片的最小文件大小是 50MB,该文件 48MB 小于 50MB)
Java
第三步,通过 cp 下载该文件,并分析日志文件
Java
透过日志,我们可以看到,虽然我们上传该文件是没有使用多文件上传,但利用 cp 命令在默认的 S3 参数的情况下也会自动分片分段下载我们的对象。
本地目录与 S3 批量上传下载场景
前面两个小节,我们通过实验深度探索了,单文件通 cp 操作的一些细节;本小节我们一起来看看,在本地批量上传文件到 S3 及从 S3 批量下载文件的场景。对于性能这块我们放到后面章节,本小节主要探讨:
max_concurrent_requests 参数对于文件并发上传下载的影响
cp 命令中的一些高级特性
第一步,随机生成 20 个 100MB 的测试数据文件,并准备 aws configure 配置文件,修改最大并发数的参数值,保持其它参数默认,并通过不同的 profile 指定不同的 max_concurrent_requests 的参数值:
Java
第二步,跑测试命令,记录详细日志:
顺序分析 dev1.log 到 dev30.log 日志文件,可以观察到 Thread 数量变化,最大编号分别为 Thread-7,Thread-9,Thread-14,Thread-18,Thread-26,Thread-36; 为了观察最大的线程数量,作者增加测试了 max_concurrent_requests 分别为 100,1000 的结果:
由上表可见,当我们逐渐增大 max_concurrent_requests 参数值的时候,实际的并发线程数是随着线性增长的,直到一个合理的线程数量,此案例里面 256 个 8MB 的数据分片,AWS CLI cp 命令使用到 309 个线程就足够速度消费添加到队列里面的上传任务。
同理,我们从 S3 批量下载的情况,执行如下命令:
Java
从日志文件中分析出,max_concurrent_requests 为 100 或 1000 时,cp 命令并发下载的线程数最大编号为 107 和 275。
对于批量操作,不可避免有时会有选择的上传和下载某些特征的文件,cp 命令可以通过 include 和 exclude 参数进行文件模式匹配,看以下几个例子:
通过以下命令仅仅下载所有 ”.data” 结尾的文件:
Java
通过以下命令上传当前目录中,除子目录 “data100” 之外的所有文件到存储桶:
Java
S3 到 S3 的复制场景
首先,我们先来看一个同区域的文件复制案例,执行如下命令并记录详细日志:
Java
查看日志文件,可以了解两点(1)默认也是支持分段并发的(2)每个分段是调用的 upload-part-copy 接口执行复制操作的:
而如果不是 S3 到 S3 的复制的话,比如前面两个场景,源是本地目标地址是 S3 的话则 cp 命令最终会调用 upload-part 方法进行上传,这两个命令有什么区别吗?
参见upload-part-copy和upload-part在线文档,仔细对比如下两个命令最终的 REST 请求,可以看到,UploadPartCopy 请求只需要将源数据指向源的对象(x-amz-copy-source)以及相应的数据范围(x-amz-copy-source-range);但 UploadPart 请求中必须要讲该分段的数据包含进去;也就是可以推断,S3 到 S3 的复制不需要经过我们执行该命令的机器进行数据中转;
样例请求(UploadPartCopy):
样例请求(UploadPart):
本章小结
本章节,我们深度解析了 cp 命令在不同场景下的数据传输行为,基本的一些结论见下表;其中,S3 到 S3 的复制,从底层 API 可以分析出不需要经过运行命令的机器进行中转,这样节约进出执行 cp 命令的机器流量;同时我们 s3api 提供了很多底层 S3 底层接口,基于这些接口,可以方便地在分段上传的基础上实现断点续传。
AWS S3 CLI 上传下载性能测试
本章继续和大家一起来探讨下影响 AWS S3 CLI 进行数据传输的性能的基本因素以及实际场景下基于 AWS EC2 的一些数据传输性能测试情况。
同区域不同 S3 桶数据的复制性能测试
测试环境:
BJS 区域
R4.2xlarge 跑 cp 命令
AWS CLI S3 参数 max_concurrent_requests 为 1000
测试方法,脚本跑 10 次取平均时间
测试结果如下,时间单位是秒:
总体平均时间:(29+29+28+6+29+5+6+6+6+29)/10=17.3 秒,root.img 的大小为 8.0GB,AWS 北京区域不同桶之间的数据平均传输速度为 473.52MB/s,最高速度可以达到 1.6GB/s,最低 282.48MB/s。
为了验证该场景下对网络的影响,我们截取了这阶段的网络方面的监控,我们观察到这段时间,该测试机的网络输出和网络输入有几个波峰,但峰值最高没超过 12MB,从侧面验证了我们的前面的判断,即 cp 在 S3 到 S3 复制的场景下,数据不会经过命令行运行的机器转发,而是直接由 S3 服务直接完成:
S3 桶数据到 AWS EC2 下载性能测试
测试环境(针对下面表格的场景一):
BJS 区域
R4.2xlarge 跑 cp 命令,
EC2 挂 500GB 的 gp2 ssd 磁盘
AWS CLI S3 参数 max_concurrent_requests 为 1000
测试方法,脚本跑 10 次取平均时间
测试结果如下,时间单位是秒:
总体平均时间:(67+64+66+65+65+65+66+65+64+65)/10=65.2 秒,root.img 的大小为 8.0GB,该测试场景的数据平均传输速度为 125.64MB/s,下载速率比较平稳。
在继续试验之前,我们总结下,影响 EC2 虚机下载 S3 数据的速度的几个因素:
磁盘的吞吐率(SSD 还是实例存储还是 HDD)
EBS 带宽,是否 EBS 优化(EBS 本身也是通过网络访问,是否有 EBS 优化为 EBS I/O 提供额外的专用吞吐带宽,否则和 EC2 网络共享带宽性能)
S3 服务的带宽(通常我们认为 S3 服务不是性能瓶颈)
我们已经测试了 R4.2xlarge 的下载性能,接下来我们选择几个典型的虚机类型分别进行测试,由于测试时间跨度比较大,测试场景中的 EC2 操作系统层没有任何调优,测试结果仅供参考。所有场景测试都针对一块盘,进行同一个 8GB 的文件下载,下载 10 次,根据时间算平均下载速率。
通过简单的测试我们发现,
虽然随着机型增大比如从 R4.xlarge 到 R4.4xlarge,同样是单块 SSD 磁盘的情况,磁盘就成为整个下载速度的瓶颈,因为 R4.4xlarge EBS 优化的专用吞吐量理论值可以达到 437MB/s,而简单测试下来的下载速度最高到 130MB/s 左右;
SSD 磁盘在整个测试场景中,是最优的选择,而且 SSD 盘的大小无需到 3TB 就可以有很好的性能表现
实例存储在没有预热(dd 整个盘)的情况下表现很一般
st1 盘 6TB 左右的测试盘,下载测试性能不如 500GB 的 SSD 盘
3334GB 的 SSD 盘的表现跟比预期要差很多
测试场景中 EC2 下载 S3 的平均速度没有超过 150MB/s
以上表格中的虽然我们测试了不同系列的不同规格的机型,但是我们可以通过修改实例类型非常方便地切换,因此监控上我们可以看出测试时间跨度中,我们的机器资源使用情况。
整个测试过程中 R4 系列机器的 CPU 的利用率总体还算正常,在 15%到 60%之间浮动:
整个测试过程中 500GB 的 gp2 磁盘的写入带宽变化如下图所示:
整个测试过程中 3334GB 的 gp2 磁盘的写入带宽变化如下图所示:
整个测试过程中 6134GB 的 st1 磁盘的写入带宽变化如下图所示:
AWS S3 CLI 的一些限制
S3 CLI 提供的分段上传算法有些限制在我们实际使用的时候,特别要关注比如分段总大小不能超过 10000,每个分段的数据块最小为 5MB,分段上传最大的对象为 5TB 等等。详细情况请参考AWS官方文档。
下一篇将要探讨和解决的问题
在了解 AWS S3 CLI 命令底层原理和影响基本性能的基本因素之后,我们接下来会继续来探讨,如何利用 S3 的底层 API 实现 S3 中的数据的跨区域可靠、低成本、高效传输。
总结
本文和大家一起深度研究了 AWS S3 cp 命令在各种场景中的底层实现原理,同时利用实验,总结了关于 AWS EC2 下载 S3 数据的基本性能测试结果,期待更多读者可以实践出更多的性能优化方法。随着大数据分析的蓬勃发展,存放在 S3 上的数据越来越多,本系列主要会和大家一起深度探讨 S3 数据复制迁移的最佳实践。
作者介绍:
薛军
AWS 解决方案架构师,获得 AWS 解决方案架构师专业级认证和 DevOps 工程师专业级认证。负责基于 AWS 的云计算方案架构的咨询和设计,同时致力于 AWS 云服务在国内的应用和推广,在互联网金融、保险、企业混合 IT、微服务等方面有着丰富的实践经验。在加入 AWS 之前已有接近 10 年的软件开发管理、企业 IT 咨询和实施工作经验。
本文转载自 AWS 技术博客。
原文链接:
评论 2 条评论