上传大文件时如何避免空闲连接超时?

时间:2016-09-21 20:30:26

标签: django nginx amazon-s3 gunicorn amazon-elb

考虑我们当前的架构:

         +---------------+                             
         |    Clients    |                             
         |    (API)      |                             
         +-------+-------+                             
                 ∧                                     
                 ∨                                     
         +-------+-------+    +-----------------------+
         | Load Balancer |    |   Nginx               |
         | (AWS - ELB)   +<-->+   (Service Routing)   |
         +---------------+    +-----------------------+
                                          ∧            
                                          ∨            
                              +-----------------------+
                              |   Nginx               |
                              |   (Backend layer)     |
                              +-----------+-----------+
                                          ∧            
                                          ∨            
         -----------------    +-----------+-----------+
           File Storage       |       Gunicorn        |
           (AWS - S3)     <-->+       (Django)        |
         -----------------    +-----------------------+

当客户端,移动设备或网络尝试在我们的服务器上上传大文件(超过1 GB)时,通常会面临空闲的连接超时。从他们的客户端库,在iOS上,或从我们的负载均衡器。

当客户端实际上载文件时,不会发生超时,因为连接不是空闲&#34;,正在传输字节。但我认为当文件被转移到Nginx后端层并且Django开始将文件上传到S3时,客户端和我们的服务器之间的连接将变为空闲,直到上传完成。

有没有办法防止这种情况发生,我应该在哪个层面解决这个问题?

3 个答案:

答案 0 :(得分:3)

我遇到了同样的问题,并在django-queued-storage之上使用django-storages修复了此问题。 django排队存储的作用是,当收到文件时,它会创建一个芹菜任务,将其上传到远程存储器(如S3),如果文件被任何人访问,并且在S3上尚未提供,则它从本地服务它文件系统。这样,您就不必等待文件上传到S3,以便将响应发送回客户端。

作为Load Balancer背后的应用程序,您可能希望使用共享文件系统,例如Amazon EFS,以便使用上述方法。

答案 1 :(得分:1)

您可以尝试跳过将文件上传到服务器并直接将其上传到s3,然后只返回您应用的网址。

有一个应用程序:django-s3direct你可以尝试一下。

答案 2 :(得分:1)

您可以创建上传处理程序以直接将文件上传到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