2016-09-21 6 views
11

Рассмотрим нашей текущая архитектуры:Как избежать простоя таймаута подключения при загрузке большого файла?

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

Когда клиент, мобильный телефон или Интернет, попытайтесь загрузить большие файлы (больше, чем в Великобритании) на наших серверах, то часто сталкивается неработающее время ожидания соединения. Либо из их клиентской библиотеки, например, в iOS, либо из нашего балансировщика нагрузки.

Когда файл фактически загружается клиентом, никаких таймаутов не происходит, потому что соединение не является «незанятым», байты передаются. Но я думаю, что когда файл был перенесен в базовый слой Nginx, и Django начинает загрузку файла на S3, соединение между клиентом и нашим сервером становится бездействующим до завершения загрузки.

Есть ли способ предотвратить это и на каком уровне я должен решить эту проблему?

+0

Вы установили client_max_body_size в NGINX conf? –

+0

Какая система запускает тайм-аут? ELB или что-то еще? ELB по умолчанию - 60 секунд, но он настраивается. –

+0

В этом случае это время ожидания клиента –

ответ

1

Вы можете создать обработчик загрузки для загрузки файла непосредственно на s3. Таким образом, вы не должны сталкиваться с таймаутом соединения.

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

Я сделал несколько тестов, и это прекрасно работает в моем случае.

Вам необходимо запустить новый multipart_upload с помощью boto, например, и постепенно отправлять куски.

Не забудьте проверить размер куска. 5Mb является минимальным, если ваш файл содержит более 1 части. (Ограничение 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-queued-storage поверх django-storages. Что такое хранилище в очереди django, так это то, что когда файл получен, он создает задачу celery, чтобы загрузить его в удаленное хранилище, такое как S3, и в среднем, если файл доступен кому-либо, и он еще не доступен на S3, он обслуживает его из локального файловая система. Таким образом, вам не нужно ждать, пока файл будет загружен на S3, чтобы отправить ответ клиенту.

В качестве приложения для балансировки нагрузки вы можете использовать общую файловую систему, такую ​​как Amazon EFS, чтобы использовать вышеуказанный подход.

1

Вы можете попытаться пропустить загрузку файла на свой сервер и загрузить его непосредственно на s3, а затем получить только URL-адрес вашего приложения.

У вас есть приложение для этого: django-s3direct вы можете попробовать.

Смежные вопросы