Django 如何避免上传大文件时出现空闲连接超时?
以我们当前的架构为例:Django 如何避免上传大文件时出现空闲连接超时?,django,nginx,amazon-s3,gunicorn,amazon-elb,Django,Nginx,Amazon S3,Gunicorn,Amazon Elb,以我们当前的架构为例: +---------------+ | Clients | | (API) | +-------+-------+
+---------------+
| Clients |
| (API) |
+-------+-------+
∧
∨
+-------+-------+ +-----------------------+
| Load Balancer | | Nginx |
| (AWS - ELB) +<-->+ (Service Routing) |
+---------------+ +-----------------------+
∧
∨
+-----------------------+
| Nginx |
| (Backend layer) |
+-----------+-----------+
∧
∨
----------------- +-----------+-----------+
File Storage | Gunicorn |
(AWS - S3) <-->+ (Django) |
----------------- +-----------------------+
+-------------+
|客户|
|(API)
+-------+-------+
∧
∨
+-------+-------+ +-----------------------+
|负载平衡器| | Nginx|
|(AWS-ELB)++(服务路由)|
+---------------+ +-----------------------+
∧
∨
+-----------------------+
|Nginx|
|(后端层)|
+-----------+-----------+
∧
∨
----------------- +-----------+-----------+
文件存储| Gunicorn|
(AWS-S3)+(Django)|
----------------- +-----------------------+
当客户机、移动设备或网络试图在我们的服务器上上传大文件(超过GB)时,通常会面临空闲连接超时。可以从他们的客户端库(例如,在iOS上),也可以从我们的负载平衡器
当客户端实际上载文件时,不会发生超时,因为连接不是“空闲”,正在传输字节。但我认为,当文件被传输到Nginx后端层,Django开始将文件上传到S3时,客户端和服务器之间的连接将处于空闲状态,直到上传完成
有没有办法防止这种情况发生?我应该在哪一层解决这个问题?我也遇到过同样的问题,并通过在顶部使用来修复它。django队列存储所做的是,当接收到文件时,它会创建一个芹菜任务,将其上载到远程存储(如S3),同时,如果任何人访问了该文件,但该文件在S3上尚不可用,它会从本地文件系统提供该文件。通过这种方式,您不必等待文件上传到S3才能将响应发送回客户端
作为负载平衡器背后的应用程序,您可能希望使用共享文件系统,例如,以便使用上述方法。您可以尝试跳过将文件上载到服务器并直接上载到s3,然后只获取应用程序的url
有一个应用程序可以实现这一点:您可以尝试一下。您可以创建一个上载处理程序,将文件直接上载到s3。这样,您就不会遇到连接超时问题 我做了一些测试,它在我的情况下非常有效 例如,您必须使用boto启动新的多部分上传,并逐步发送块 不要忘记验证块大小。如果文件包含多个部分,则最小为5Mb。(S3限制) 如果您真的想直接上传到s3并避免连接超时,我认为这是django排队存储的最佳替代方案 您可能还需要创建自己的文件字段来正确管理文件,而不是第二次发送 下面的示例与S3BOToRage一起使用
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\u最小零件尺寸=5242880
类S3FileUploadHandler(FileUploadHandler):
chunk\u size=设置('S3\u文件\u上传\u处理程序\u缓冲区\u大小',S3\u最小\u部分\u大小)
定义初始化(self,请求=None):
超级(S3FileUploadHandler,self)。\uu初始化(请求)
self.file=None
self.part_num=1
self.last_chunk=无
self.multipart\u upload=无
定义新文件(self、字段名称、文件名称、内容类型、内容长度、字符集=None、内容类型=extra=None):
super(S3FileUploadHandler,self).新文件(字段名称、文件名称、内容类型、内容长度、字符集、内容类型额外)
self.file_name=“{}}{}”。格式(uuid.uuid4(),文件名)
默认存储.bucket.new\u密钥(self.file\u名称)
self.multipart\u upload=默认存储.bucket.initiate\u multipart\u upload(self.file\u名称)
def接收数据块(自身、原始数据、开始):
缓冲区大小=sys.getsizeof(原始数据)
如果self.last_块:
file\u part=self.last\u块
如果缓冲区大小小于S3最小部分大小:
文件\u部分+=原始\u数据
self.last_chunk=无
其他:
self.last\u chunk=原始\u数据
self.upload\u part(part=file\u part)
其他:
self.last\u chunk=原始\u数据
def上传_部件(自身,部件):
self.multipart\u upload.upload\u part\u from\u file(
fp=StringIO(部分),
part_num=self.part_num,
size=sys.getsizeof(部分)
)
self.part_num+=1
def文件完成(自身,文件大小):
如果self.last_块:
self.upload\u part(part=self.last\u块)
self.multipart\u upload.complete\u upload()
self.file=default\u storage.open(self.file\u名称)
self.file.original\u filename=self.original\u filename
返回self.file
您是否在NGINX conf中设置了客户端\u max\u body\u大小?哪个系统触发超时?ELB还是别的什么?ELB默认为60秒,但它是可配置的。在这种情况下,是客户端超时。您可以列出所有级别上已调整的所有超时相关设置吗?@Michael sqlbot我已经将该值增加到20分钟,但我认为这是一种黑客行为,因为等待一个大文件从我们的服务器上传到S3的行为不应该被视为“空闲”。此外,我无法控制客户端的空闲超时,因此我认为这不能完全解决问题。谢谢