.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üş
相关产品推荐
相关产品推荐

