上传大文件时如何避免空闲连接超时?
How to avoid having idle connection timeout while uploading large file?
考虑我们当前的架构:
+---------------+
| Clients |
| (API) |
+-------+-------+
∧
∨
+-------+-------+ +-----------------------+
| Load Balancer | | Nginx |
| (AWS - ELB) +<-->+ (Service Routing) |
+---------------+ +-----------------------+
∧
∨
+-----------------------+
| Nginx |
| (Backend layer) |
+-----------+-----------+
∧
∨
----------------- +-----------+-----------+
File Storage | Gunicorn |
(AWS - S3) <-->+ (Django) |
----------------- +-----------------------+
当客户端、手机或 Web 尝试在我们的服务器上上传大文件(超过 GB)时,通常会遇到空闲连接超时。来自他们的客户端库,例如 iOS,或者来自我们的负载均衡器。
当客户端实际上传文件时,没有发生超时,因为没有连接 "idle",正在传输字节。但是我认为当文件已经传输到Nginx后端层并且Django开始将文件上传到S3时,客户端和我们的服务器之间的连接变为空闲直到上传完成。
有没有办法防止这种情况发生,我应该在哪一层解决这个问题?
我遇到了同样的问题并使用 django-queued-storage on top of django-storages 修复了它。 django 排队存储的作用是,当收到文件时,它会创建一个 celery 任务以将其上传到远程存储(例如 S3),同时如果文件被任何人访问并且它在 S3 上尚不可用,它会从本地提供服务文件系统。通过这种方式,您不必等待文件上传到 S3 即可将响应发送回客户端。
作为负载均衡器背后的应用程序,您可能希望使用 Amazon EFS 等共享文件系统,以便使用上述方法。
您可以尝试跳过将文件上传到您的服务器并直接将其上传到s3,然后只为您的申请取回一个url。
有一个应用程序:django-s3direct你可以试一试。
您可以创建上传处理程序将文件直接上传到 s3。这样你应该不会遇到连接超时。
https://docs.djangoproject.com/en/1.10/ref/files/uploads/#writing-custom-upload-handlers
我做了一些测试,在我的情况下效果很好。
例如,您必须使用 boto 启动一个新的 multipart_upload 并逐步发送块。
不要忘记验证区块大小。如果您的文件包含超过 1 个部分,则最小值为 5Mb。 (S3 限制)
如果你真的想直接上传到 s3 并避免连接超时,我认为这是 django-queued-storage 的最佳替代方案。
您可能还需要创建自己的文件字段以正确管理文件而不是再次发送它。
以下示例使用 S3BotoStorage。
S3_MINIMUM_PART_SIZE = 5242880
class S3FileUploadHandler(FileUploadHandler):
chunk_size = setting('S3_FILE_UPLOAD_HANDLER_BUFFER_SIZE', S3_MINIMUM_PART_SIZE)
def __init__(self, request=None):
super(S3FileUploadHandler, self).__init__(request)
self.file = None
self.part_num = 1
self.last_chunk = None
self.multipart_upload = None
def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None):
super(S3FileUploadHandler, self).new_file(field_name, file_name, content_type, content_length, charset, content_type_extra)
self.file_name = "{}_{}".format(uuid.uuid4(), file_name)
default_storage.bucket.new_key(self.file_name)
self.multipart_upload = default_storage.bucket.initiate_multipart_upload(self.file_name)
def receive_data_chunk(self, raw_data, start):
buffer_size = sys.getsizeof(raw_data)
if self.last_chunk:
file_part = self.last_chunk
if buffer_size < S3_MINIMUM_PART_SIZE:
file_part += raw_data
self.last_chunk = None
else:
self.last_chunk = raw_data
self.upload_part(part=file_part)
else:
self.last_chunk = raw_data
def upload_part(self, part):
self.multipart_upload.upload_part_from_file(
fp=StringIO(part),
part_num=self.part_num,
size=sys.getsizeof(part)
)
self.part_num += 1
def file_complete(self, file_size):
if self.last_chunk:
self.upload_part(part=self.last_chunk)
self.multipart_upload.complete_upload()
self.file = default_storage.open(self.file_name)
self.file.original_filename = self.original_filename
return self.file
考虑我们当前的架构:
+---------------+
| Clients |
| (API) |
+-------+-------+
∧
∨
+-------+-------+ +-----------------------+
| Load Balancer | | Nginx |
| (AWS - ELB) +<-->+ (Service Routing) |
+---------------+ +-----------------------+
∧
∨
+-----------------------+
| Nginx |
| (Backend layer) |
+-----------+-----------+
∧
∨
----------------- +-----------+-----------+
File Storage | Gunicorn |
(AWS - S3) <-->+ (Django) |
----------------- +-----------------------+
当客户端、手机或 Web 尝试在我们的服务器上上传大文件(超过 GB)时,通常会遇到空闲连接超时。来自他们的客户端库,例如 iOS,或者来自我们的负载均衡器。
当客户端实际上传文件时,没有发生超时,因为没有连接 "idle",正在传输字节。但是我认为当文件已经传输到Nginx后端层并且Django开始将文件上传到S3时,客户端和我们的服务器之间的连接变为空闲直到上传完成。
有没有办法防止这种情况发生,我应该在哪一层解决这个问题?
我遇到了同样的问题并使用 django-queued-storage on top of django-storages 修复了它。 django 排队存储的作用是,当收到文件时,它会创建一个 celery 任务以将其上传到远程存储(例如 S3),同时如果文件被任何人访问并且它在 S3 上尚不可用,它会从本地提供服务文件系统。通过这种方式,您不必等待文件上传到 S3 即可将响应发送回客户端。
作为负载均衡器背后的应用程序,您可能希望使用 Amazon EFS 等共享文件系统,以便使用上述方法。
您可以尝试跳过将文件上传到您的服务器并直接将其上传到s3,然后只为您的申请取回一个url。
有一个应用程序:django-s3direct你可以试一试。
您可以创建上传处理程序将文件直接上传到 s3。这样你应该不会遇到连接超时。
https://docs.djangoproject.com/en/1.10/ref/files/uploads/#writing-custom-upload-handlers
我做了一些测试,在我的情况下效果很好。
例如,您必须使用 boto 启动一个新的 multipart_upload 并逐步发送块。
不要忘记验证区块大小。如果您的文件包含超过 1 个部分,则最小值为 5Mb。 (S3 限制)
如果你真的想直接上传到 s3 并避免连接超时,我认为这是 django-queued-storage 的最佳替代方案。
您可能还需要创建自己的文件字段以正确管理文件而不是再次发送它。
以下示例使用 S3BotoStorage。
S3_MINIMUM_PART_SIZE = 5242880
class S3FileUploadHandler(FileUploadHandler):
chunk_size = setting('S3_FILE_UPLOAD_HANDLER_BUFFER_SIZE', S3_MINIMUM_PART_SIZE)
def __init__(self, request=None):
super(S3FileUploadHandler, self).__init__(request)
self.file = None
self.part_num = 1
self.last_chunk = None
self.multipart_upload = None
def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None):
super(S3FileUploadHandler, self).new_file(field_name, file_name, content_type, content_length, charset, content_type_extra)
self.file_name = "{}_{}".format(uuid.uuid4(), file_name)
default_storage.bucket.new_key(self.file_name)
self.multipart_upload = default_storage.bucket.initiate_multipart_upload(self.file_name)
def receive_data_chunk(self, raw_data, start):
buffer_size = sys.getsizeof(raw_data)
if self.last_chunk:
file_part = self.last_chunk
if buffer_size < S3_MINIMUM_PART_SIZE:
file_part += raw_data
self.last_chunk = None
else:
self.last_chunk = raw_data
self.upload_part(part=file_part)
else:
self.last_chunk = raw_data
def upload_part(self, part):
self.multipart_upload.upload_part_from_file(
fp=StringIO(part),
part_num=self.part_num,
size=sys.getsizeof(part)
)
self.part_num += 1
def file_complete(self, file_size):
if self.last_chunk:
self.upload_part(part=self.last_chunk)
self.multipart_upload.complete_upload()
self.file = default_storage.open(self.file_name)
self.file.original_filename = self.original_filename
return self.file