Amazon S3 现在是 strongly consistent。这意味着一旦您上传了一个对象,所有阅读该对象的人都可以保证获得该对象的更新版本。
从表面上看,这听起来可以保证您的问题是“是的,所有客户端都会获得文件的旧版本或新版本”。真相比这还要模糊。
在幕后,许多 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()
此代码将一个 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]
最后一行很重要。下载的文件大部分是“0”字节,但有 20mb 的“1”字节。意思是,尽管只执行了一次下载调用,但我得到了文件 v1 的一部分和 v2 的一部分。
现在,在实践中,这不太可能发生,更何况如果你有更好的网络带宽,那么我会在这里运行一个普通的家庭互联网连接。
但它总是可能发生。如果您需要确保下载者永远不会看到这样的部分文件,您要么需要做一些事情,比如验证文件的哈希是否正确,或者我的偏好是每次使用不同的密钥上传,并有一些机制客户端来发现“最新”密钥,因此他们可以下载整个未更改的文件,即使在他们上传时上传完成。