AWS S3 GetObject是否同时读取上传到s3的部分对象

rav*_*avi 4 amazon-s3 amazon-web-services

我有一个 lambda (L1) 将文件 (100MB) 替换到 s3 位置 ( s3://bucket/folder/abc.json )。我有另一个 lambda(L2、L3)同时读取同一文件,一个通过 golang api,另一个通过 Athena 查询。s3 存储桶/文件夹没有版本控制。

问题是:lambdas L2、L3 是否会读取文件的旧副本,直到新文件上传为止?或者它是否读取正在上传的部分文件?如果是后者,那么如何确保 L2、L3 仅在完全上传时读取文件?

Ano*_*ard 8

Amazon S3 现在是强一致的。这意味着一旦您上传对象,所有读取该对象的人都保证获得该对象的更新版本。

从表面上看,这听起来像是保证您的问题是“是的,所有客户端都将获得文件的旧版本或新版本”。事实仍然比这更模糊。

在幕后,许多 S3 API 都通过分段上传进行上传。这是众所周知的,并且不会改变我上面所说的,因为上传必须在对象可用之前完成。然而,许多 API 还在下载过程中使用多个字节范围请求来下载较大的对象。这是有问题的。这意味着下载可能会下载文件 v1 的一部分,然后当它去下载另一部分时,如果 v2 刚刚上传,则可能会得到 v2。

通过一点努力,我们可以证明这一点:

#!/usr/bin/env python3

import boto3
import multiprocessing
import io
import threading

bucket = "a-bucket-to-use"
key = "temp/dummy_key"
size = 104857600

class ProgressWatcher:
    def __init__(self, filesize, downloader):
        self._size = float(filesize)
        self._seen_so_far = 0
        self._lock = threading.Lock()
        self._launch = True
        self.downloader = downloader

    def __call__(self, bytes_amount):
        with self._lock:
            self._seen_so_far += bytes_amount
            if self._launch and (self._seen_so_far / self._size) >= 0.95:
                self._launch = False
                self.downloader.start()

def upload_helper(pattern, name, callback):
    # Upload a file of 100mb of "pattern" bytes
    s3 = boto3.client('s3')
    print(f"Uploading all {name}..")
    temp = io.BytesIO(pattern * size)
    s3.upload_fileobj(temp, bucket, key, Callback=callback)
    print(f"Done uploading all {name}")

def download_helper():
    # Download a file
    s3 = boto3.client('s3')
    print("Starting download...")
    s3.download_file(bucket, key, "temp_local_copy")
    print("Done with download")

def main():
    # See how long an upload takes
    upload_helper(b'0', "zeroes", None)

    # Watch how the next upload progresses, this will start a download when it's nearly done
    watcher = ProgressWatcher(size, multiprocessing.Process(target=download_helper))
    # Start another upload, overwriting the all-zero file with all-ones
    upload_helper(b'1', "ones", watcher)

    # Wait for the downloader to finish
    watcher.downloader.join()

    # See what the resulting file looks like
    print("Loading file..")
    counts = [0, 0]
    with open("temp_local_copy") as f:
        for x in f.read():
            counts[ord(x) - ord(b'0')] += 1
    
    print("Results")
    print(counts)

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

此代码将一个 100mb 的“0”对象上传到 S3。然后,它使用相同的密钥开始上传 100mb 的“1”,当第二次上传完成 95% 时,它会开始下载该 S3 对象。然后它会计算在下载的文件中看到了多少个“0”和“1”。

使用最新版本的 Python 和 Boto3 运行此程序,由于网络条件的原因,您的确切输出无疑会与我的不同,但这是我在测试运行中看到的结果:

Uploading all zeroes..
Done uploading all zeroes
Uploading all ones..
Starting download...
Done uploading all ones
Done with download
Loading file..
Results
[83886080, 20971520]
Run Code Online (Sandbox Code Playgroud)

最后一行很重要。下载的文件大部分是“0”字节,但也有 20mb 的“1”字节。这意味着,尽管只执行了一次下载调用,但我还是获得了文件 v1 的某些部分和 v2 的某些部分。

现在,在实践中,这种情况不太可能发生,如果你有更好的网络带宽,那么我在普通的家庭互联网连接上就更是如此。但它总是有可能发生。如果您需要确保下载者永远不会看到这样的部分文件,您要么需要执行一些操作,例如验证文件的哈希值是否正确,或者我的偏好是每次使用不同的密钥上传,并有一些机制客户端发现“最新”密钥,以便他们可以下载整个未更改的文件,即使上传在上传时完成。