【问题标题】:How to avoid having idle connection timeout while uploading large file?上传大文件时如何避免空闲连接超时?
【发布时间】:2017-01-30 06:20:32
【问题描述】:

考虑我们当前的架构:

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

当客户端、移动设备或 Web 尝试在我们的服务器上上传大文件(超过 1 GB)时,通常会遇到空闲连接超时。来自他们的客户端库,例如在 iOS 上,或者来自我们的负载均衡器。

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

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

【问题讨论】:

  • 你在 NGINX conf 中设置了 client_max_body_size 吗?
  • 什么系统触发了超时? ELB还是别的什么? ELB 默认为 60s,但它是可配置的。
  • 在这种情况下,是客户端超时
  • 能否列出你已经在各个级别调整的所有超时相关设置?
  • @Michael-sqlbot 我已经将该值增加到 20 分钟,但我认为这很骇人听闻,因为等待大文件从我们的服务器上传到 S3 的操作不应被视为“闲置的”。此外,我无法控制客户端的空闲超时,所以这不能完全解决问题。谢谢

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


【解决方案1】:

我遇到了同样的问题,并通过在django-storages 之上使用django-queued-storage 来修复它。 django queued storage 的作用是,当接收到文件时,它会创建一个 celery 任务将其上传到远程存储,例如 S3,同时如果文件被任何人访问并且它在 S3 上尚不可用,它会从本地提供它文件系统。这样,您无需等待文件上传到 S3 即可将响应发送回客户端。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      您可以尝试跳过将文件上传到您的服务器并直接将其上传到 s3,然后只获取您的应用程序的 url。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-04-09
        • 2020-04-29
        • 2017-04-19
        • 2017-07-19
        • 2018-09-09
        • 1970-01-01
        • 1970-01-01
        • 2012-10-25
        相关资源
        最近更新 更多