2016-09-21 4 views
11

Betrachten wir unsere aktuelle Architektur:Wie verhindert man, dass beim Hochladen großer Dateien ein Zeitlimit für Verbindungsunterbrechungen auftritt?

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

Wenn ein Client, mobile oder Web, versuchen, große Dateien (mehr als ein GB) auf unseren Servern laden dann oft inaktive Verbindung Timeouts Gesicht. Entweder von ihrer Client-Bibliothek, beispielsweise von iOS, oder von unserem Load Balancer.

Wenn die Datei tatsächlich vom Client hochgeladen wird, treten keine Zeitüberschreitungen auf, da die Verbindung nicht "inaktiv" ist und Bytes übertragen werden. Aber ich denke, wenn die Datei in die Nginx-Backend-Ebene übertragen wurde und Django die Datei auf S3 hochlädt, wird die Verbindung zwischen dem Client und unserem Server inaktiv, bis der Upload abgeschlossen ist.

Gibt es eine Möglichkeit dies zu verhindern und auf welcher Ebene sollte ich dieses Problem angehen?

+0

Haben Sie client_max_body_size in NGINX conf festgelegt? –

+0

Welches System löst das Timeout aus? ELB oder etwas anderes? ELB ist standardmäßig 60s, aber es ist konfigurierbar. –

+0

In diesem Fall ist es der Client, der eine Zeitversetzung durchführt. –

Antwort

1

Sie können einen Upload-Handler erstellen, um die Datei direkt auf s3 hochzuladen. Auf diese Weise sollten Sie kein Verbindungstimeout erhalten.

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

ich einige Tests gemacht und es funktioniert perfekt in meinem Fall.

Sie müssen ein neues multipart_upload mit Boto zum Beispiel starten und Chunks progressiv senden.

Vergessen Sie nicht, die Chunk-Größe zu validieren. 5 MB ist das Minimum, wenn Ihre Datei mehr als 1 Teil enthält. (S3 Begrenzung)

Ich denke, das ist die beste Alternative zu django-queued-Speicher, wenn Sie wirklich direkt auf s3 hochladen und Verbindungszeitüberschreitung vermeiden möchten.

Sie müssen wahrscheinlich auch ein eigenes Dateifeld erstellen, um die Datei korrekt zu verwalten und nicht ein zweites Mal zu senden.

Das folgende Beispiel ist mit 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

Ich habe das gleiche Problem konfrontiert und es mit auf django-storages behoben. Was django in die Warteschlange stellt, ist, dass beim Empfang einer Datei eine Sellerie-Aufgabe erstellt wird, um sie auf den Remote-Speicher wie S3 hochzuladen und in der Zwischenzeit, wenn Datei von irgendjemandem aufgerufen wird und noch nicht auf S3 verfügbar ist Dateisystem. Auf diese Weise müssen Sie nicht warten, bis die Datei in S3 hochgeladen wurde, um eine Antwort an den Client zu senden.

Als Ihre Anwendung hinter Load Balancer möchten Sie möglicherweise ein gemeinsames Dateisystem wie Amazon EFS verwenden, um den obigen Ansatz zu verwenden.

1

Sie können versuchen, die Datei auf Ihren Server zu überspringen und sie direkt auf s3 hochzuladen, dann erhalten Sie nur eine URL für Ihre Anwendung zurück.

Es gibt eine App dafür: django-s3direct können Sie es versuchen.

Verwandte Themen