考虑我们目前的架构:
+---------------+ | Clients | | (API) | +-------+-------+ ∧ ∨ +-------+-------+ +-----------------------+ | Load Balancer | | Nginx | | (AWS - ELB) +<-->+ (Service Routing) | +---------------+ +-----------------------+ ∧ ∨ +-----------------------+ | Nginx | | (Backend layer) | +-----------+-----------+ ∧ ∨ ----------------- +-----------+-----------+ File Storage | Gunicorn | (AWS - S3) <-->+ (Django) | ----------------- +-----------------------+
当客户端,手机或networking,尝试上传我们的服务器上的大文件(超过GB),然后经常面临空闲的连接超时。 无论是从他们的客户端库,例如在iOS上,或从我们的负载均衡器。
当文件实际上被客户端上传时,没有超时发生,因为连接不是“空闲”,字节正在传输。 但是我认为,当文件传输到Nginx后端层,Django开始将file upload到S3时,客户端和我们的服务器之间的连接变为空闲状态,直到上传完成。
有没有办法来防止这种情况发生,我应该在哪一层解决这个问题?
您可以创建一个上传处理程序,直接将文件上传到s3。 这样你就不会遇到连接超时。
https://docs.djangoproject.com/en/1.10/ref/files/uploads/#writing-custom-upload-handlers
我做了一些测试,在我的情况下,它完美的作品。
你必须用boto开始一个新的multipart_upload,并逐步发送块。
不要忘记验证块的大小。 如果您的文件包含超过1个部分,5Mb是最小值。 (S3限制)
我认为这是django-queued-storage的最佳选择,如果你真的想直接上传到s3并避免连接超时。
您可能还需要创建自己的文件字段来正确管理文件,而不是第二次发送。
下面的例子是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
我遇到了同样的问题,并通过在django-storage上使用django-queued-storage来修复它。 django排队存储所做的是当收到一个文件时,创建一个celery任务,将其上传到远程存储器(如S3),同时如果任何人都访问文件,并且在S3上尚不可用,则从本地服务器文件系统。 通过这种方式,您不必等待将文件上传到S3,以便将响应发送回客户端。
作为Load Balancer后面的应用程序,您可能需要使用Amazon EFS等共享文件系统才能使用上述方法。
您可以尝试跳过上传文件到您的服务器,并直接上传到s3,然后只取回你的应用程序的网址。
有一个应用程序: django-s3direct你可以试试看。