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

这里我做个演示:

  1. 先生成一个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

image

  1. 然后将其上传至指定的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]#

image

可以看到目前已经上传成功了。(md5值也是一样的,说明这个文件是完整的)

  1. 那我如果多加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]#

image

好了,这块不是重点,知道单文件最大传5个G就完事了,接下来我们看看分段上传。

0x03 S3分段上传原理

分段上传,文档里有提到,最小5M,最大5T。
分段上传拢共分为三步:

  1. 初始化
  2. 分段上传
  3. 组装

这里我拿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演示一下手动分段上传。

  1. 先把一个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]#

image

  1. 再做个初始化上传的操作:
    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]#

image

  1. 把刚刚切下来的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]#
  2. 上传完成之后先确认一下是否全都传上去了:

    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]#
  3. 确认无误后,将这些文件段组装在一起:
    先创建1个文件fileparts.json

    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
    {
    "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多文件上传的一些坑点

  1. 我执行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生命周期规则走一波
image
保存之后就可以利用CCR定期删除残留的文件片段了。

0x06 后记

大概就是这样,先到这里了,溜了溜了。

0x07 参考链接