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

.NET 7环境下Azure Blob Storage小文件上传超时性能问题

问题分析与解决方案

核心问题定位

你遇到的100秒耗时并非来自GetBlobContainerClient本身(这个方法是轻量级的,仅创建本地对象不发起网络请求),真正的瓶颈是每次上传都调用的CreateIfNotExistsAsync——这个方法会发起HTTP请求检查容器是否存在,不存在则创建。当这个网络请求因连接池耗尽、网络延迟或服务端响应慢触发超时(HttpClient默认100秒)时,就会导致整个流程卡住。

具体优化方案

1. 缓存容器存在状态,避免重复网络请求

容器不会频繁创建,完全可以用一个线程安全的缓存记录已存在的容器,后续请求直接跳过检查:

private readonly ConcurrentDictionary<string, bool> _containerExistsCache = new ConcurrentDictionary<string, bool>();
private readonly ILogger _logger; // 注入日志组件

private async Task<BlobContainerClient> GetContainerInstance(string containerName, bool createIfNotExist = false)
{
    BlobContainerClient container = _blobServiceClient.GetBlobContainerClient(containerName);

    if (createIfNotExist)
    {
        if (!_containerExistsCache.TryGetValue(containerName, out _))
        {
            try
            {
                // 仅第一次检查/创建容器
                await container.CreateIfNotExistsAsync(PublicAccessType.BlobContainer);
                _containerExistsCache.TryAdd(containerName, true);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to create container {ContainerName}", containerName);
                throw;
            }
        }
    }

    return container;
}

2. 自定义Blob客户端配置,优化超时与连接池

Azure Blob客户端允许自定义BlobClientOptions,调整超时时间、重试策略和连接池参数,避免长时间等待:

builder.Services.AddSingleton(_ =>
{
    var blobOptions = new BlobClientOptions();
    
    // 缩短网络超时时间,避免等待100秒才触发超时
    blobOptions.Retry.NetworkTimeout = TimeSpan.FromSeconds(10);
    // 配置合理的重试策略,应对临时网络波动
    blobOptions.Retry.MaxRetries = 3;
    blobOptions.Retry.Delay = TimeSpan.FromSeconds(1);
    
    // 调整HTTP连接池,避免并发请求时连接耗尽
    var httpHandler = new HttpClientHandler
    {
        MaxConnectionsPerServer = 100, // 增加单服务器最大连接数
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
        UseProxy = false
    };
    
    blobOptions.Transport = new HttpClientTransport(httpHandler);

    var connectionString = builder.Configuration
        .GetSection("azureStorageConfiguration")
        .GetValue<string>("ConnectionString");

    return new BlobServiceClient(connectionString, blobOptions);
});

3. 补充日志定位问题

在关键步骤添加日志,明确耗时节点:

public async Task<string> UploadFileFromByteArrayAsync(string containerName, string blobName, byte[] fileBytes, CancellationToken cancellationToken = default)
{
    var stopwatch = Stopwatch.StartNew();
    try
    {
        BlobClient blob = await GetBlobInstance(containerName, blobName, true);
        _logger.LogInformation("GetBlobInstance took {ElapsedMs}ms for container {ContainerName}, blob {BlobName}", 
            stopwatch.ElapsedMilliseconds, containerName, blobName);
        
        stopwatch.Restart();
        await blob.UploadAsync(new BinaryData(fileBytes), overwrite:true, cancellationToken);
        _logger.LogInformation("UploadAsync took {ElapsedMs}ms for blob {BlobName}", 
            stopwatch.ElapsedMilliseconds, blobName);
        
        return blob.Uri.AbsoluteUri;
    }
    finally
    {
        stopwatch.Stop();
        _logger.LogInformation("Total upload took {ElapsedMs}ms for blob {BlobName}", 
            stopwatch.ElapsedMilliseconds, blobName);
    }
}

4. 确保CancellationToken有效传递

确认调用UploadFileFromByteArrayAsync时传入了正确的取消令牌,比如Web请求的HttpContext.RequestAborted,避免卡住的请求无法及时终止。

补充说明

  • GetBlobContainerClient和GetBlobClient都是轻量级对象,无需缓存,它们仅持有对服务端资源的引用,不会创建额外网络连接。
  • 你的小文件场景下,并发上传容易触发连接池瓶颈,调整MaxConnectionsPerServer能有效缓解这个问题。

内容的提问来源于stack exchange,提问作者Haydar Can Kubilay Gümüş

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 19:07:49