2016-09-21 47 views
11

考虑我们当前的体系结构:如何避免上传大文件时出现空闲连接超时?

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

当一个客户端,手机或网页,尝试我们的服务器上上传大文件(超过GB以上)则经常面临空闲连接超时。无论是从其客户端库(例如iOS)还是来自我们的负载均衡器。

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

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

+0

您是否在NGINX conf中设置了client_max_body_size? –

+0

什么系统启动超时? ELB还是别的? ELB默认为60秒,但可以配置。 –

+0

在这种情况下,客户端正在计时 –

回答

1

您可以创建一个上传处理程序将文件直接上传到s3。这样你就不会遇到连接超时。

https://docs.djangoproject.com/en/1.10/ref/files/uploads/#writing-custom-upload-handlers

我做了一些测试,它完美的作品在我的情况。

您必须以boto为例开始一个新的multipart_upload,并逐步发送块。

不要忘记验证块大小。如果您的文件包含多个部分,则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 
3

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

作为您在Load Balancer后面的应用程序,您可能想要使用共享文件系统(例如Amazon EFS)以使用上述方法。

1

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

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