关于django:如何避免在上传大文件时出现空闲连接超时?

How to avoid having idle connection timeout while uploading large file?

考虑我们当前的体系结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
         +---------------+                            
         |    Clients    |                            
         |    (API)      |                            
         +-------+-------+                            
                 a?§                                    
                 a?¨                                    
         +-------+-------+    +-----------------------+
         | Load Balancer |    |   Nginx               |
         | (AWS - ELB)   +<-->+   (Service Routing)   |
         +---------------+    +-----------------------+
                                          a?§            
                                          a?¨            
                              +-----------------------+
                              |   Nginx               |
                              |   (Backend layer)     |
                              +-----------+-----------+
                                          a?§            
                                          a?¨            
         -----------------    +-----------+-----------+
           File Storage       |       Gunicorn        |
           (AWS - S3)     <-->+       (Django)        |
         -----------------    +-----------------------+

在客户端,移动设备或Web上,尝试在我们的服务器上上传大文件(大于1 GB),然后经常面临空闲连接超时。无论是从他们的客户端库(例如在iOS上),还是从我们的负载均衡器中。

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

有没有一种方法可以防止这种情况的发生,我应该在哪一层上解决此问题?


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

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


您可以创建一个上载处理程序以将文件直接上载到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一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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

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

有一个适用于此的应用程序:django-s3direct您可以尝试一下。