You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure Blob存储批量并行调用过载问题求助

问题原因分析
  1. 前端无限制并发请求过载:
    前端通过两层嵌套的Promise.all发起图片上传请求,所有图片请求会瞬间并行发送。当章节和图片数量较多时,短时间内会产生大量请求(你提到的200+次),直接导致后端线程池被占满,CPU使用率飙升;同时数据库请求因线程资源不足排队等待,出现耗时极久的情况。

  2. 后端不必要的资源重复创建:

    • 每次调用GetUploadUrl时,都重复调用_blobServiceClient.GetBlobContainerClient创建容器客户端,虽然该操作轻量,但高频调用下累计开销不可忽视。
    • 每次都实例化StorageSharedKeyCredential,该对象涉及密钥加密运算,高频创建会大幅增加CPU负载,这是CPU飙升的核心原因之一。
  3. 连接池与线程池耗尽:
    大量并发请求耗尽后端HTTP线程池和数据库连接池,即使重启应用,若前端仍发起大量并发请求,会再次触发资源耗尽,导致请求挂起。


优化方案

前端优化:限制并发请求数量

替换无限制的Promise.all,改为带并发数限制的批量处理,避免瞬间压垮后端。示例如下(使用p-limit库,也可自行实现并发控制):

import pLimit from 'p-limit';

// 限制同时最多5个请求并发
const limit = pLimit(5);

const processedChapters = await Promise.all(
  chapters.map(async (chapter) => {
    const search_language = getSearchLanguage(diplomaSettings.languages);

    if (chapter.images && chapter.images.length > 0) {
      // 对图片上传请求添加并发限制
      const uploadedImages = await Promise.all(
        chapter.images.map((imageDataUrl, index) => limit(async () => {
          if (typeof imageDataUrl === "string" && imageDataUrl.startsWith("data:")) {
            const imageUrl = await uploadImageToBlobStorage(
              imageDataUrl,
              chapter.id,
              index
            );
            return imageUrl;
          }
          return imageDataUrl;
        }))
      );

      return {
        ...chapter,
        search_language,
        images: uploadedImages.filter((url) => url !== null),
      };
    }

    return {
      ...chapter,
      search_language,
    };
  })
);

后端优化:复用资源+异步化+限流

1. 复用BlobContainerClient和StorageSharedKeyCredential

将容器客户端和密钥凭证改为单例,在服务启动时初始化,避免重复创建:

// 在服务注入时初始化单例
private readonly BlobContainerClient _blobContainerClient;
private readonly StorageSharedKeyCredential _storageSharedKeyCredential;

// 构造函数注入
public YourBlobService(IConfiguration configuration, BlobServiceClient blobServiceClient)
{
    _imagesContainerName = configuration["ImagesContainerName"];
    _accountName = configuration["StorageAccountName"];
    _accountKey = configuration["StorageAccountKey"];
    
    // 初始化单例容器客户端
    _blobContainerClient = blobServiceClient.GetBlobContainerClient(_imagesContainerName);
    // 初始化单例密钥凭证
    _storageSharedKeyCredential = new StorageSharedKeyCredential(_accountName, _accountKey);
}

public async Task<string> GetUploadUrlAsync(string fileName)
{
    var uniqueFileName = $"{Path.GetFileNameWithoutExtension(fileName)}-{Guid.NewGuid()}{Path.GetExtension(fileName)}";

    // 复用已初始化的容器客户端
    var blobClient = _blobContainerClient.GetBlobClient(uniqueFileName);

    var sasBuilder = new BlobSasBuilder
    {
        BlobContainerName = _imagesContainerName,
        BlobName = uniqueFileName,
        Resource = "b",
        ExpiresOn = DateTime.UtcNow.AddMinutes(15)
    };

    sasBuilder.SetPermissions(BlobSasPermissions.Write | BlobSasPermissions.Create | BlobSasPermissions.Read);

    // 复用密钥凭证
    var sasQueryParameters = sasBuilder.ToSasQueryParameters(_storageSharedKeyCredential);
    var sasUrl = $"{blobClient.Uri}?{sasQueryParameters}";

    return sasUrl;
}

2. 异步化方法

将同步方法改为异步Task<string>,避免阻塞线程池线程,提升后端处理并发请求的能力。

3. 添加接口限流

在ASP.NET Core中使用内置的Rate Limiting中间件,限制该接口的请求频率,防止瞬间大量请求:

// Program.cs中配置限流
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("imageUpload", opt =>
    {
        opt.Window = TimeSpan.FromSeconds(10);
        opt.PermitLimit = 10; // 10秒内最多允许10个请求
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        opt.QueueLimit = 5; // 排队最多5个请求
    });
});

// 在控制器接口上应用限流
[EnableRateLimiting("imageUpload")]
public async Task<IActionResult> GetUploadUrl([FromQuery] string fileName)
{
    var sasUrl = await _blobService.GetUploadUrlAsync(fileName);
    return Ok(sasUrl);
}

内容的提问来源于stack exchange,提问作者Vadym Yesipov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 08:48:22