AWS S3 Multipart Upload
0x00 前言
记录一下S3分段上传相关的内容,主要涉及到原理以及使用方法,还有一些坑什么的。
0x01 如何生成大小合适的垃圾文件?
为了完全覆盖到上传时可能涉及到的边界条件,我们需要构造大小合适的垃圾文件。在这里我们可以使用如下命令生成指定大小的文件:1
dd if=/dev/zero of=tonghua.5G bs=1M count=5120
0x02 单文件上传
单文件上传区别于分段上传,就是我一个文件就是一个文件,也不做任何切割,直接把文件整个上传到S3就完事了。单文件上传本质上调用了PutObject这个API,他有一些限制,比如说它最多支持上传5G的文件,大于5G的我们就只能使用分段上传了。
但是这里也不是说我5G以下就只使用这个也是不对的,根据自己的业务场景,选择合适的分段大小可以极大的提到上传效率,也会避免文件过大中途由于网络等问题导致上传失败什么的。
需要留意的是,S3单文件上传最多支持上传5G的文件,大于就不行了。这点可以在官方文档中找到:Uploading Objects
这里我做个演示:
- 先生成一个5G大小的垃圾文件:
1
2
3
4
5
6
7
8[root@ip-10-0-0-64 test]# dd if=/dev/zero of=tonghua.5G bs=1M count=5120
5120+0 records in
5120+0 records out
5368709120 bytes (5.4 GB) copied, 81.8725 s, 65.6 MB/s
[root@ip-10-0-0-64 test]# file tonghua.5G
tonghua.5G: data
[root@ip-10-0-0-64 test]# ls -alth tonghua.5G
-rw-r--r-- 1 root root 5.0G Nov 5 06:09 tonghua.5G
- 然后将其上传至指定的S3 Bucket中
1
2
3
4
5
6
7[root@ip-10-0-0-64 test]# aws s3api put-object --bucket aaabbb --key test/tonghua.5G --body tonghua.5G
{
"ETag": "\"ec4bcc8776ea04479b786e063a9ace45\""
}
[root@ip-10-0-0-64 test]# md5sum tonghua.5G
ec4bcc8776ea04479b786e063a9ace45 tonghua.5G
[root@ip-10-0-0-64 test]#
可以看到目前已经上传成功了。(md5值也是一样的,说明这个文件是完整的)
- 那我如果多加1个字节的内容会发生啥事呢?
可以看到直接报错了,文件过大导致上传失败。1
2
3
4
5
6
7[root@ip-10-0-0-64 test]# echo 1 >> tonghua.5G
[root@ip-10-0-0-64 test]# ls -alth tonghua.5G
-rw-r--r-- 1 root root 5.1G Nov 5 06:59 tonghua.5G
[root@ip-10-0-0-64 test]# aws s3api put-object --bucket aaabbb --key test/tonghua.5G --body tonghua.5G
An error occurred (EntityTooLarge) when calling the PutObject operation: Your proposed upload exceeds the maximum allowed size
[root@ip-10-0-0-64 test]#
好了,这块不是重点,知道单文件最大传5个G就完事了,接下来我们看看分段上传。
0x03 S3分段上传原理
分段上传,文档里有提到,最小5M,最大5T。
分段上传拢共分为三步:
- 初始化
- 分段上传
- 组装
这里我拿1G的文件做个测试:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30[root@ip-10-0-0-64 test]# dd if=/dev/zero of=tonghua.1G bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 14.9912 s, 71.6 MB/s
[root@ip-10-0-0-64 test]# file tonghua.1G
tonghua.1G: data
[root@ip-10-0-0-64 test]# ls -alth tonghua.1G
-rw-r--r-- 1 root root 1.0G Nov 5 08:10 tonghua.1G
[root@ip-10-0-0-64 test]# aws s3 cp tonghua.1G s3://aaabbb/test/tonghua.1G
upload: ./tonghua.1G to s3://aaabbb/test/tonghua.1G
[root@ip-10-0-0-64 test]# aws s3api get-object --bucket aaabbb --key test/tonghua.1G 233
{
"AcceptRanges": "bytes",
"ContentType": "binary/octet-stream",
"LastModified": "Tue, 05 Nov 2019 08:11:27 GMT",
"ContentLength": 1073741824,
"ETag": "\"c789e490a90359de2bd3b09d7e957cfd-128\"",
"Metadata": {}
}
[root@ip-10-0-0-64 test]# aws s3api head-object --bucket aaabbb --key test/tonghua.1G
{
"AcceptRanges": "bytes",
"ContentType": "binary/octet-stream",
"LastModified": "Tue, 05 Nov 2019 08:11:27 GMT",
"ContentLength": 1073741824,
"ETag": "\"c789e490a90359de2bd3b09d7e957cfd-128\"",
"Metadata": {}
}
[root@ip-10-0-0-64 test]# md5sum tonghua.1G
cd573cfaace07e7949bc0c46028904ff tonghua.1G
可以看到文件上传成功,通过ETag的内容我们可知其被分成了128段,然后组装在一起了,也就是8M一段,这也是默认的分段大小。
我拿了一个16M/9M的两个文件做了个测试,会被分成两段,至此可以得出结论,cli默认文件大于8M之后就会采用分段上传了。(下面的文档上也写了这一点)
这个也是可以在CLI配置文件中做修改的,具体可以参考这个链接AWS CLI S3 Configuration。
- multipart_chunksize 参数控制一个文件片段为多大
- multipart_threshold 参数控制大于多大的文件会采用分段上传操作
需要提及的是,这个只支持aws s3命令,而不适用与aws s3api命令。
0x04 实践S3分段上传
这里我用s3api演示一下手动分段上传。
- 先把一个1G的文件拆开(200M一份,拢共6份):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19[root@ip-10-0-0-64 test]# split -b 200M tonghua.1G
[root@ip-10-0-0-64 test]# ls -alth
total 14G
-rw-r--r-- 1 root root 24M Nov 7 06:20 xaf
drwxr-xr-x 2 root root 182 Nov 7 06:20 .
-rw-r--r-- 1 root root 200M Nov 7 06:20 xae
-rw-r--r-- 1 root root 200M Nov 7 06:20 xad
-rw-r--r-- 1 root root 200M Nov 7 06:20 xac
-rw-r--r-- 1 root root 200M Nov 7 06:20 xab
-rw-r--r-- 1 root root 200M Nov 7 06:20 xaa
dr-xr-x--- 13 root root 4.0K Nov 7 00:00 ..
-rw-r--r-- 1 root root 9.0M Nov 5 08:56 tonghua.9M
-rw-r--r-- 1 root root 16M Nov 5 08:34 tonghua.16M
-rw-r--r-- 1 root root 1.0G Nov 5 08:28 233
-rw-r--r-- 1 root root 1.0G Nov 5 08:10 tonghua.1G
-rw-r--r-- 1 root root 5.1G Nov 5 06:59 tonghua.5G
-rw-r--r-- 1 root root 5.0G Nov 5 06:04 tmp.5G
-rw-r--r-- 1 root root 89 Oct 30 08:39 1.py
[root@ip-10-0-0-64 test]#
- 再做个初始化上传的操作:
1
2
3
4
5
6
7[root@ip-10-0-0-64 test]# aws s3api create-multipart-upload --bucket aaabbb --key tonghua.1G.MTU
{
"Bucket": "aaabbb",
"UploadId": "z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGvtF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.",
"Key": "tonghua.1G.MTU"
}
[root@ip-10-0-0-64 test]#
把刚刚切下来的6段分别上传上去:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25[root@ip-10-0-0-64 test]# aws s3api upload-part --bucket aaabbb --key tonghua.1G.MTU --part-number 1 --body xaa --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGv tF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\""
}
[root@ip-10-0-0-64 test]# aws s3api upload-part --bucket aaabbb --key tonghua.1G.MTU --part-number 2 --body xab --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGv tF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\""
}
[root@ip-10-0-0-64 test]# aws s3api upload-part --bucket aaabbb --key tonghua.1G.MTU --part-number 3 --body xac --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGv tF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\""
}
[root@ip-10-0-0-64 test]# aws s3api upload-part --bucket aaabbb --key tonghua.1G.MTU --part-number 4 --body xad --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGv tF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\""
}
[root@ip-10-0-0-64 test]# aws s3api upload-part --bucket aaabbb --key tonghua.1G.MTU --part-number 5 --body xae --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGv tF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\""
}
[root@ip-10-0-0-64 test]# aws s3api upload-part --bucket aaabbb --key tonghua.1G.MTU --part-number 6 --body xaf --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGv tF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"ETag": "\"77377273b0a4b61febdbf7bbf52b9db9\""
}
[root@ip-10-0-0-64 test]#上传完成之后先确认一下是否全都传上去了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51[root@ip-10-0-0-64 test]# aws s3api list-parts --bucket aaabbb --key tonghua.1G.MTU --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGvtF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"Owner": {
"DisplayName": "tonghua",
"ID": "75ca81520a6a8f31111e9ac0a8efec8c3b89076f67d161cdbc7382cb0aadc377"
},
"Initiator": {
"DisplayName": "xxxxx",
"ID": "arn:aws-cn:iam::297123123419:user/xxxxx"
},
"Parts": [
{
"LastModified": "2019-11-08T09:05:05.000Z",
"PartNumber": 1,
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\"",
"Size": 209715200
},
{
"LastModified": "2019-11-08T09:05:35.000Z",
"PartNumber": 2,
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\"",
"Size": 209715200
},
{
"LastModified": "2019-11-08T09:05:51.000Z",
"PartNumber": 3,
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\"",
"Size": 209715200
},
{
"LastModified": "2019-11-08T09:06:07.000Z",
"PartNumber": 4,
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\"",
"Size": 209715200
},
{
"LastModified": "2019-11-08T09:06:21.000Z",
"PartNumber": 5,
"ETag": "\"3566de3a97906edb98d004d6b947ae9b\"",
"Size": 209715200
},
{
"LastModified": "2019-11-08T09:06:35.000Z",
"PartNumber": 6,
"ETag": "\"77377273b0a4b61febdbf7bbf52b9db9\"",
"Size": 25165824
}
],
"StorageClass": "STANDARD"
}
[root@ip-10-0-0-64 test]#确认无误后,将这些文件段组装在一起:
先创建1个文件fileparts.json1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26{
"Parts": [{
"ETag": "3566de3a97906edb98d004d6b947ae9b",
"PartNumber":1
},
{
"ETag": "3566de3a97906edb98d004d6b947ae9b",
"PartNumber":2
},
{
"ETag": "3566de3a97906edb98d004d6b947ae9b",
"PartNumber":3
},
{
"ETag": "3566de3a97906edb98d004d6b947ae9b",
"PartNumber":4
},
{
"ETag": "3566de3a97906edb98d004d6b947ae9b",
"PartNumber":5
},
{
"ETag": "77377273b0a4b61febdbf7bbf52b9db9",
"PartNumber":6
}]
}
然后执行如下命令将其组装:1
2
3
4
5
6
7
8[root@ip-10-0-0-64 test]# aws s3api complete-multipart-upload --multipart-upload file://fileparts.json --bucket aaabbb --key tonghua.1G.MTU --upload-id z6NUjopY8OMv0Qd4Uomi9U4L_hs8ceLesZA4hJZzCm2mRwa0FW4U6ndTsnSnJ6gcVWAPYY_xtV6wIwjeb_AYPRqjGvtF6dtv3NOez3boX9.d4cWudryKsnpfieanIl5.
{
"ETag": "\"9088a3364a043bb55249cda2b0ff5fe1-6\"",
"Bucket": "aaabbb",
"Location": "https://aaabbb.s3.cn-north-1.amazonaws.com.cn/tonghua.1G.MTU",
"Key": "tonghua.1G.MTU"
}
[root@ip-10-0-0-64 test]#
那现在呢,分段上传操作其实就已经完成了。
可以再做一个文件完整性校验,两种方法,一种是将文件下载回来做与大文件做比对,一种是用S3 etag完整性校验做一个比对。
先来看看方法一:1
2
3
4
5
6
7[root@ip-10-0-0-64 test]# aws s3 cp s3://aaabbb/tonghua.1G.MTU tonghua.1G.MTU
download: s3://aaabbb/tonghua.1G.MTU to ./tonghua.1G.MTU
[root@ip-10-0-0-64 test]# md5sum tonghua.1G.MTU
cd573cfaace07e7949bc0c46028904ff tonghua.1G.MTU
[root@ip-10-0-0-64 test]# md5sum tonghua.1G
cd573cfaace07e7949bc0c46028904ff tonghua.1G
[root@ip-10-0-0-64 test]#
嗯,没啥毛病。
方法二:
算法就是每一个分段的md5串在一起在求一个md5,完事后面-N就完事了。1
2
3
4
5
6
7
8
9
10
11
12[root@ip-10-0-0-64 test]# echo "3566de3a97906edb98d004d6b947ae9b3566de3a97906edb98d004d6b947ae9b3566de3a97906edb98d004d6b947ae9b3566de3a97906edb98d004d6b947ae9b3566de3a97906edb98d004d6b947ae9b77377273b0a4b61febdbf7bbf52b9db9" | xxd -r -p | md5sum
9088a3364a043bb55249cda2b0ff5fe1 -
[root@ip-10-0-0-64 test]# aws s3api head-object --bucket aaabbb --key tonghua.1G.MTU
{
"AcceptRanges": "bytes",
"ContentType": "binary/octet-stream",
"LastModified": "Thu, 07 Nov 2019 06:27:23 GMT",
"ContentLength": 1073741824,
"ETag": "\"9088a3364a043bb55249cda2b0ff5fe1-6\"",
"Metadata": {}
}
[root@ip-10-0-0-64 test]#
至此,手动S3分段上传完成,也校验了文件完整性,感觉问题不大了。
0x05 关于S3多文件上传的一些坑点
- 我执行cp命令,自动分段上传,中间由于网络或者我手动停止了,我中间上传一半可能就断了,导致这部分残留的object片段不能看到完事还收钱。
可以用这个命令看到有没有残留的文件片段:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19[root@ip-10-0-0-64 test]# aws s3api list-multipart-uploads --bucket aaabbb {
"Uploads": [
{
"Initiator": {
"DisplayName": "11111",
"ID": "arn:aws-cn:iam::2971111111111111:user/11111"
},
"Initiated": "2019-11-08T13:24:45.000Z",
"UploadId": "fdsBIrZ4amSsimTB4uzZosKp0toutli9kZr8Twir0YGi.QqVvWTWwsCpjnxazEdHMjAlbSMqsEgcOqku2MO9FTXKsr_guaall14gYFZz5aVze05WSdbscDR07NlCZ09p",
"StorageClass": "STANDARD",
"Key": "test_multipart",
"Owner": {
"DisplayName": "tonghua",
"ID": "75ca81520a6a8f399c2e9ac0a8efec8c3b89076f67d161cdbc7382cb0aadc377"
}
}
]
}
[root@ip-10-0-0-64 test]#
删除这些残留的文件,通常有两种方法:
方法一:使用cli命令手动删除1
2[root@ip-10-0-0-64 test]# aws s3api abort-multipart-upload --bucket aaabbb --key "test_multipart" --upload-id "fdsBIrZ4amSsimTB4uzZosKp0toutli9kZr8Twir0YGi.QqVvWTWwsCpjnxazEdHMjAlbSMqsEgcOqku2MO9FTXKsr_guaall14gYFZz5aVze05WSdbscDR07NlCZ09p"
[root@ip-10-0-0-64 test]#
方法二:CCR生命周期规则走一波
保存之后就可以利用CCR定期删除残留的文件片段了。
0x06 后记
大概就是这样,先到这里了,溜了溜了。