背景
在Amazon S3 深度实践系列之一:S3 CLI深度解析及性能测试一文中,我们深度剖析了 AWS CLI S3 相关命令的实际工作原理及单机下载 S3 数据的基本性能测试情况。在实际工作场景中,很多客户会在 AWS 多个区域的 S3 桶里面存储大量数据,而且会遇到将数据批量从一个区域一次转移到另外一个区域的情形;因此,在本篇中,作者和大家一起来探讨下出现这样的需求我们如何进行架构设计及高效实现。
架构设计
存储在 S3 中的对象随着时间的推移,对象数量逐渐增加,而且总体的数据量也不断膨胀,如果碰到需要将数据从某一个区域的 S3 存储桶完全复制到另外一个 S3 存储桶里面,我们会遇到哪些挑战呢?
网络传输带宽的限制
存储桶里面所有对象的分析和列表
源存储桶和目标存储桶权限的设定
传输失败识别和重试的挑战
如何利用并发来加速传输及降低成本
如何判断目标存储桶中的对象和源存储桶中的对象差异及完整性
在通用架构设计环节,我们将复杂的问题分解成一系列的子问题进行分析,并讨论在不同场景下的具体实现时要考虑的因素。如下图所示,我们将该任务分解成独立的五个环节,从图上我们也可以看出来,如何实现大规模数据或任务的并发执行是每个环节能否高效完成的一个很关键的技术要求;而且,只有在步骤三执行数据传输任务时,才会涉及到具体场景中的技术限制,因此我们在执行数据传输任务章节来讨论,同区域不同存储桶之间,AWS 海外不同区域存储桶之间,以及 AWS 海外和国内不同存储桶之间的具体技术考量点。
S3 对象“清单”
了解源和目标存储桶里的 S3 对象是非常重要的准备工作,该章节我们讨论,如何获得 S3 存储桶的所有对象列表,包含对象的基本的信息,比如最新版本的对象大小,ETag 等等。
Amazon S3 本身提供了存储桶管理功能之清单生成功能,该功能是一个异步的 AWS 后台定期执行,可以实现每天生成一个存储桶清单保存成 Excel 格式。
同时我们也看到很多用户提问,如何实现一个自定义的清单功能,满足大家对于对象变化比较频繁的存储桶对象的实时统计场景以及更多高级自定义的业务逻辑。
接下来我们来看看这两种方法的具体实现逻辑。
利用 S3 CLI 实现高效的清单功能
作者利用 AWS S3 CLI 实现高效的清单功能基于以下两个事实前提:
s3api 的 list-objects-v2 虽然文档中说明最多返回 1000 个对象,但实测可以获得所有对象列表
同样利用 s3api 的 list-objects-v2 的 delimiter 和 prefix 参数,我们可以实现类似文件夹目录逐级扫描功能
基于以上两个事实,我们实现桶清单的主要逻辑如下图所示:
输入参数主要是:bucket,region 和 IAM 配置的 profile 名字,profile 默认为 default;另外 depth 控制扫描的“目录”层级
当 depth 为零时,我们直接尝试利用 list-objects-v2 一次性获取存储桶中所有对象列表并生成一个 json 格式的文件(但当桶里面对象太多时,该操作会超时)
当 depth 为零即单线程无法直接生成存储桶清单时,我们就尝试如下迭代逻辑:
生成存储桶当前“目录”里面的所有对象和该目录中所有“子目录”列表
遍历上一步的“子目录”列表,迭代生成该目录下的对象列表和“子目录”列表
如果遍历的深度等于输入参数 depth=n,或者“子目录”列表为空,那么停止遍历子目录,直接生成该层级“目录”里面所有的对象列表
以下是几个关键点实现的代码说明,首先,生成某个“目录”前缀下所有对象列表的 AWS S3 CLI 命令参考,如下命令将在操作系统后台执行并生成存储桶 jason 中“目录”前缀“qwikLabs/”下的所有对象列表(包括所有嵌套“子目录”中的所有对象):
Java
其次,如下命令将仅仅生成指定“目录”前缀“qwikLabs/”下的对象列表(不包括嵌套“子目录“的对象)和所有下一层“子目录“列表,为了加强”子目录“输出格式,我们增加了 query 参数:
Java
另外,为了实现并发我们利用了迭代算法以及操作系统后台异步执行 AWS S3 CLI 命令的方法,最终程序会生成一系列的 json 文件结果,存储桶中所有的对象列表分布在这些文件当中。
S3 自带的清单功能
在了解了我们通过 AWS CLI S3 命令行工具实现自定义的清单功能之后,我们再来对比下,Amazon S3 自带的清单功能。
在存储桶页面,导航到“管理“标签,Amazon S3 目前提供了四项 S3 管理功能,其中跟本文相关的是”清单“功能。该功能支持我们对某一个存储桶,定义多个清单,用户可以根据需要,定义针对不同 S3 对象前缀生成各自的清单列表,并可以存储在独立的存储桶中:
同时,自带清单功能还支持定义生成清单的频率及清单中包含的对象字段,检查并确定好清单选项之后,服务会帮助我们在保存清单的目标存储桶中设置好相应的 IAM 策略:
清单任务保存之后,后台会异步定期执行,每次都会按时间生成 manifest.json 文件和一系列的清单文件,manifest.json 里面包含这次生成的所有清单文件列表:
S3 对象清单小结
Amazon S3 存储桶已经内置了清单功能,基本可以满足我们的日常需求,我们不需要重复造轮子;本章节所讨论的利用 AWS S3 CLI 命令行自定义实现清单功能,更多的是作者好奇的发现,AWS S3 CLI 本身非常好用,也可以帮助我们实现类似文件目录的逐级对象列表功能,提供给有特殊场景需求的用户参考。
对象清单分解成传输任务
有了存储桶中所有的对象清单,接下来,我们就看看如何设计传输任务。设计传输任务的原则如下:
如果网络条件非常良好,比如同区域的不同存储桶之间,按照作者的测试,复制带宽平均可以达到 xxMB/s,如此可以直接利用 S3 cp 命令
尽可能将单进程的复制任务分解成多个子任务并发执行,任务分解后进入到 Amazon SQS 队列,这样将任务分解和任务执行进行解耦
如果网络条件非常一般,比如平均在 10KB/s 并且网络抖动大的情况下,对于超过一定大小的文件需要切割成小文件,组成子任务并发执行
传输任务分解算法的设计,涉及到几个关键参数:
max_task_size_mb:单个任务的对象总大小上限,比如最大大小限制在 100MB,那么单个任务最多有 100MB 的对象列表,或者该任务就一个对象,该对象本身大小就超过了 100MB
max_task_objects:单个任务的对象数量上限,比如数量上限为 50,那么单个任务中最多有 50 个对象需要传输
multipart_threshold 及 multipart_chunksize:对象太大时,需要分割成多个小对象传输任务,那么多大的对象需要进行分解?分段的单位大小是多少?比如阈值是 10MB,单位大小是 2MB,那么大于 10MB 的对象都需要再分解成 2MB 的多个子对象并发续传
Amazon SQS 任务队列设计与实现
传输任务在设计时分成两大类,一种是本身对象就是小文件,我们按照 max_task_size_mb 和 max_task_objects 进行分组,即每个任务总数据量大小不会超过 max_task_size_mb,而且对象数量也不超过 max_task_objects ;这些任务我们会发送到自动创建的 S3Task_NormalQueue 开头的 SQS 队列中,每个队列的消息数量上限本文设为 80000 条;另外一类是,对象大小超过 multipart_threshold 限制的,我们会进一步把该对象分解成 multipart_chunksize 大小的独立对象,同样按照 max_task_size_mb 和 max_task_objects 的算法进行分组,但这些任务会保存到自动创建的以 S3Task_BigSizeQueue 开头的队列中。
另外遵循 Amazon SQS 操作的最佳实践,我们分别为这两类任务队列设定了同样的死信队列,当消息被读取 10 次而没被处理成功的会自动转移到 S3Task_DeadQueue 进行存储和后续处理。
执行数据传输任务
当任务队列产生之后,接下来,就到了如何高效执行如何多的传输任务的阶段,很多网络和客观条件的限制,我们都放到了如何分解传输任务的算法里面进行了实现,在执行数据传输任务环节,逻辑非常简单:
读取一条 SQS 任务
根据任务中的具体对象,每个对象利用独立线程进行复制或者下载再上传
只有该任务中所有的对象都传输成功,才把该任务消息从 SQS 队列中删除
同区域 S3 数据复制
同区域不同 S3 存储桶之间数据复制,由于网络条件较好,IAM 权限简单,可以尽量利用 boto3 的 copy-object 方法直接利用 S3 服务本身能力,进行快速数据复制,该方法数据无需经过命令执行的机器中转。因此在任务分解环节,multipart_threshold 的值需要设置一个比较大而且合理的值,避免大文件被分片之后,需要先下载后上传,这样会消耗更多的流量。
AWS 海外不同区域 S3 数据复制
海外不同区域的 S3 存储桶之间复制,和同区域的不同存储桶复制场景类似,但由于跨区域传输,网络状况取决于两个区域的位置及它们之间的互联网状况。
AWS 海外区域和 BJS 区域 S3 数据复制
该情况最复杂,AWS 海外和国内是独立的区域,需要不同的账号权限体系,因此,对象需要先进行下载再上传,这样就需要占用执行命令的机器的内存和网络带宽;
在任务分解时,需要尽量把大对象分解成小的片段比如 2MB 或者 1MB 的大小以提高单次数据传输的成功率。
在任务执行时,需要尽量并发,以单任务小带宽累积成可以接受的总体平均传输速度,并充分 AWS 出口带宽的优势
数据跨区域迁移实践
基于我们之前的架构设计,那我们分阶段来具体动手实践一个具体场景, Amazon 官网有很多公开的数据集,我们选定Next Generation Weather Radar (NEXRAD) 作为数据源,该数据源在美东(us-east-1)区域;目标存储桶我们选择在 BJS 区域。该实验仅仅为了验证技术可行性,完整的参考代码见s3deepdive github;代码不作为生产用途仅仅用来学习用途。
准备环境
为了简单地完成技术验证,我们所有的测试环境基于一台 r4.2xlarge 的 Amazon Linux 机型展开,系统需要准备好:
在 AWS Global 美东区域创建一台 EC2 实例
关联一个 IAM Role,需要有访问 S3 及 SQS 相关的管理权限
Python2.7.x
Boto3
300GB gp2 EBS 磁盘
配置好目标存储桶的 IAM Profile 及修改默认获取 IAM Role 的临时 Token 的默认超时时间和重试次数:
Java
生成对象清单
Amazon 公共数据集没有提供清单列表,因此,我们利用前文的逻辑,并尝试利用 AWS S3 CLI 命令生成该存储桶的对象清单。该数据集按照年月进行数据分区,我们设定对象的 Prefix 的迭代深度为 3,后台执行以下命令,并观察执行日志:
Java
由于数据集非常大,该命令执行需要点时间,最终,3 层的扫描帮助我们并发生成了 2751 个对象清单文件,总大小 4.5GB:
Java
设计并提交传输任务
由于该场景下,源存储桶和目标存储桶之间的单次传输的速度非常有限,实测该场景下大概在 9KB/s 左右,而且网络抖动比较厉害,因此,我们尽量缩小单个任务的总数据量大小,并设定大对象的大小阈值设置为 2MB;具体参数需要在 Python 常量参数中修改:
由于清单文件太多,总数据量太大,因此,我们可以数据清单分成多个目录,分别进行计算,比如如下命令:
我们把大小小于 800000 bytes 的文件放到目录./1/里面
把大小小于 2MB 的文件放到./2/里面
把大小小于 6MB 的文件放到./3/里面
大家可以根据自己的需要,分成不同的对象清单文件夹
Java
为了演示,我们没有生成所有对象清单的传输任务,仅仅选取了其中某连个文件夹,生成的传输任务如下图所示,有些队列的消息数为 0,表示我们后台还有传输任务消息没有发送到队列中:
我们来看看队列里面的一个任务的结构组成,S3Task_Bigsize*队列中的任务相比于普通队列中的任务多了一组分片的 Range 范围:
设计并执行传输任务
在并发执行数据传输任务之前,我们先看看单个任务执行情况,任务执行需要指明任务队列,源和目的存储桶以及访问目标存储桶的 IAM Profile 名:
Java
从执行日志可以分析出,对于 NormalQueue 中的单个任务,由于是小对象,而且数量是 10,因此我们的执行代码可以并发执行,总体执行时间是 26 秒;对比 BigsizeQueue 中的任务,虽然总体数据大小和 NormalQueue 差不多,但由于只有 2 个对象并发复制,该任务的总体执行时间是 363 秒。
关于并发任务执行,本质上是一个批处理的业务逻辑,假定有 1000 个任务列表,
每个任务数据量上限 20MB,如果传输速度在 10KB/s 那么一个任务需要大概需要 2048 秒即 34 分钟,但我们的的任务执行是多线程并发操作,按每个任务最多 10 个对象算,在 10KB/s 的速度下,一个任务最快需要执行 3.4 分钟左右(10 个对象并发上传),最慢 34 分钟(一个对象的情况下)
如果同时 100 个并发执行,完成所有任务,需要至少执行 10 次,总时长在 34 分钟到 340 分钟之间
如果并发 1000 个,完成所有任务需要至少执行 1 次;总时长 3.4 分钟到 34 分钟之间
本实验为了学习的目的,我们在测试机 r4.2xlarge 的机器上,后台并发执行 100 个任务,并观察数据传输的实际状况,
Java
Java
Java
针对下面这两个队列,每个运行了 50 个并发任务,因此在 SQS 界面上,可以看到传输中的消息是 50,也就是同时有 50 个消息任务正在被处理:
可以看到 BigsizeQueue 队列一次就成功完成的数据传输任务总数为 79949+50-79971=28;NormalQueue 队列一次就成功完成的数据传输任务总数为 79950+50-79958=42;我们定义任务的成功与否,为该任务中所有的对象都成功传输完成;该实验我们对于分段采取的大小是 2MB,在 9KB/s 左右的互联网传输速度下,还是有点大,容易失败;普通队列上中的任务,对象大小都在几百 KB 左右,一次传输成功的概率大很多。
数据完整性检查
本文不对单个对象的完整性问题展开探讨,对于用户首先最关心的问题是,源存储桶的对象有没有完全迁移到目前存储桶中;因此,可以定期生成目标存储桶的对象清单,并比对源存储桶的对象清单,在自定义的清单程序中,我们是逐级生产对象清单文件,有一定的规律,如果两个存储桶使用同样的 depth 参数生成,生成的对象清单文件个数首先一致的;具体到识别出有没有遗漏的迁移对象,可以进一步对比清单中的对象列表。
总结
本文就跨区域 S3 数据迁移整体架构作了基本探讨,并在架构的基础上,学习和实践了利用 AWS S3 CLI 以及 boto3 库如何实现自定义的对象清单,传输任务分解及执行逻辑。现实的生产场景下,还需要更多细节的思考和实践,接下来,我们会继续在大规模批处理,大规模对象集的完整性校验方面和大家继续探讨。
作者介绍:
薛军
AWS 解决方案架构师,获得 AWS 解决方案架构师专业级认证和 DevOps 工程师专业级认证。负责基于 AWS 的云计算方案架构的咨询和设计,同时致力于 AWS 云服务在国内的应用和推广,在互联网金融、保险、企业混合 IT、微服务等方面有着丰富的实践经验。在加入 AWS 之前已有接近 10 年的软件开发管理、企业 IT 咨询和实施工作经验。
本文转载自 AWS 技术博客。
原文链接:
评论