我正在使用 ASP.NET Core 9 Web API,该 API 从我们的客户端应用程序之一获取(可能很大)文件上传,将其流式传输到服务器上的临时文件,进行一些处理,然后上传重新将文件的打包版本保存到 blob 存储,并将有关它的一些元数据发送到数据库。这些都在 Azure 中(Azure 容器应用、Azure Blob 存储、Azure SQL DB)。请求是
Content-Type: multipart/form-data
,文件只有一个部分
Content-Disposition: form-data; name=""; filename="<some_file_name>"
我观察到的问题是容器的内存使用量大致遵循上传文件的大小,导致容器内存不足(参见屏幕截图)。我的印象是,将上传直接流式传输到文件存储应该避免使用超出流缓冲所需的内容。
代码主要遵循在 ASP.NET Core 中上传文件中的示例,除了 1)这里较少进行大小写检查以保持测试简单,2)我只能处理文件流上传,因为真正的代码将沿着流传递到客户端的库,客户端的库将对其进行膨胀、处理等。此代码会导致观察到的内存问题。
/// <summary>
/// Adds a new Document
/// </summary>
[HttpPost("test", Name = nameof(AddDocumentAsync))]
[DisableFormValueModelBinding]
[DisableRequestSizeLimit]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> AddDocumentAsync()
{
if ( !HttpContext.Request.HasFormContentType )
return BadRequest("No file uploaded.");
string boundary = HttpContext.Request.GetMultipartBoundary();
if ( string.IsNullOrEmpty(boundary) )
return BadRequest("Invalid multipart form-data request.");
MultipartReader multipartReader = new MultipartReader(boundary, HttpContext.Request.Body);
MultipartSection? section = await multipartReader.ReadNextSectionAsync();
if ( section == null )
return BadRequest("No file found in request body.");
FileMultipartSection? fileSection = section.AsFileSection();
if ( fileSection?.FileStream == null )
return BadRequest("Invalid file.");
string tempDirectory = Path.GetTempPath();
string tmpPath = Path.Combine(tempDirectory, Path.GetRandomFileName());
using ( FileStream fs = new FileStream(tmpPath, FileMode.Create) )
await fileSection.FileStream.CopyToAsync(fs);
return Created();
}
我观察到文件在
/tmp
中增长,但不幸的是,内存使用量以大致相同的速度增长。
如果我更改目标,以便文件从
fileSection.FileStream
流式传输到 blob 存储而不是本地文件,我不会观察到内存问题。
我还尝试使用带有模型绑定的最小 API 来实现
IFormFile
。我从here看到,默认情况下,如果文件超过64k,它将被缓冲到磁盘,这正是我想要的。我注意到文件在 /tmp
中增长,但不幸的是,此解决方案的内存使用量也以相同的速度增长。
我还尝试为容器安装存储卷,因为我想知道容器是否由于没有安装存储卷而使用内存。我在
/blah
安装了一个 Azure 文件实例,并将临时文件的目标从 /tmp
更改为 /blah
。我注意到文件正确地流入 Azure 文件存储实例,但在这种情况下仍然观察到内存问题,就像其他情况一样。
最后,我在 Azure Web 服务应用程序中尝试了相同的代码(上面发布的代码片段),但没有观察到内存增加问题。同样,我在本地运行该应用程序,但没有观察到我的系统或进程内存像在 Azure 容器应用程序中那样增加。
更新:为了回应评论,我还尝试将文件从 blob 存储下载到容器应用程序。这也会导致容器的内存使用量根据正在下载的文件的大小而增加。使用了下面的代码片段。
[HttpGet("test", Name = nameof(TestDocumentAsync))]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> TestDocumentAsync()
{
string tempDirectory = Path.GetTempPath();
string tmpPath = Path.Combine(tempDirectory, Path.GetRandomFileName());
BlobClient blobClient = _blobContainerClient.GetBlobClient("c1f04a61-5ec3-43a8-b7ad-de51ae5185bb.tmp");
using ( FileStream fs = new FileStream(tmpPath, FileMode.Create) )
await blobClient.DownloadToAsync(fs);
return Ok();
}
我认为我在这里误解或误用了某些东西。在处理 Azure 容器应用程序和 ASP.NET 时,将大型(1GB 到 ?GB)
multipart/form-data
文件上传到临时存储进行处理和后续删除的正确方法是什么?或者如何解释内存使用情况,即使是从 blob 存储进行简单下载?
您可以尝试的一件事是每次将缓冲区写入磁盘时刷新流。
public async Task WriteStreamToFileWithFlushAsync(Stream inputStream, string filePath)
{
byte[] buffer = new byte[8192];
int bytesRead;
using (FileStream outputStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await outputStream.WriteAsync(buffer, 0, bytesRead);
await outputStream.FlushAsync();
}
}
}