并发请求Blob Storage时存在文件却间歇性提示Blob不存在
Azure Blob Storage并发读取时ExistsAsync误判文件不存在的问题解决
问题描述
使用下方代码读取Blob Storage文本文件时,非并发场景完全正常,但同时发送多个请求时,blobClient.ExistsAsync()会在文件实际存在的情况下返回false,最终返回null。需要排查问题并实现同一文本文件的多并发读取支持。
原代码
/// <summary> /// Extracts the Text content from a file in the Blob Storage. /// </summary> /// <param name="blobContainerName">The name of the Container in which the file is present.</param> /// <param name="blobName">The name of the file to be Read.</param> /// <returns> /// File is Present - The Content of the Text File; /// File is Empty - An Empty String; /// File Not Found - null</returns> public async Task<string> ReadTextFileFromBlob(string blobContainerName, string blobName) { // Create a BlobServiceClient using account details BlobServiceClient blobServiceClient = _blobServiceClient; // Get the container client BlobContainerClient blobContainerClient = blobServiceClient.GetBlobContainerClient(blobContainerName); // Get the blob client BlobClient blobClient = blobContainerClient.GetBlobClient(blobName); // Check if the blob exists if (!await blobClient.ExistsAsync()) { // Return null if the blob does not exist return null; } // Download the blob content as a stream using (MemoryStream memoryStream = new MemoryStream()) { await blobClient.DownloadToAsync(memoryStream); // Check if the content is empty if (memoryStream.Length == 0) { // Return null if the content is empty return ""; } // Convert the stream to a string string content = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray()); return content; } }
问题根源
原代码中ExistsAsync()和DownloadToAsync()是两个独立的HTTP请求,存在竞态条件:
- 并发场景下,两次请求的时间窗口内可能出现临时网络波动,导致
ExistsAsync()请求失败返回false; - 即使
ExistsAsync()返回true,后续下载请求仍可能因文件被删除、权限变更等原因失败,两次分离的请求无法保证原子性; - Azure Blob客户端默认的重试策略可能在并发请求时未正确处理部分异常,导致误判文件不存在。
此外,原代码使用MemoryStream增加了内存开销,并发场景下会加剧资源消耗。
解决方案
方案1:直接下载+捕获404异常(推荐)
取消ExistsAsync()检查,直接尝试下载文件,通过捕获404异常判断文件是否存在。这种方式将两次请求合并为一次,消除竞态条件,同时提升性能。
修改后的代码:
/// <summary> /// Extracts the Text content from a file in the Blob Storage. /// </summary> /// <param name="blobContainerName">The name of the Container in which the file is present.</param> /// <param name="blobName">The name of the file to be Read.</param> /// <returns> /// File is Present - The Content of the Text File; /// File is Empty - An Empty String; /// File Not Found - null</returns> public async Task<string> ReadTextFileFromBlob(string blobContainerName, string blobName) { BlobContainerClient blobContainerClient = _blobServiceClient.GetBlobContainerClient(blobContainerName); BlobClient blobClient = blobContainerClient.GetBlobClient(blobName); try { // 直接下载Blob内容,无需提前检查存在性 var downloadResponse = await blobClient.DownloadContentAsync(); string content = downloadResponse.Value.Content.ToString(); // 处理空内容场景 return string.IsNullOrWhiteSpace(content) ? "" : content; } catch (RequestFailedException ex) when (ex.Status == 404) { // 404状态码表示文件不存在,返回null return null; } }
方案2:保留Exists检查并优化请求一致性
如果必须保留存在性检查,可通过设置BlobRequestConditions增强请求的一致性,同时确保客户端实例的线程安全:
- 确保
_blobServiceClient是单例实例(通过DI容器注册为Singleton),BlobServiceClient、BlobContainerClient、BlobClient均为线程安全类型,单例复用可避免连接池问题; - 在
ExistsAsync()中添加请求条件,避免缓存或 stale 响应:
var existsOptions = new BlobExistsOptions { Conditions = new BlobRequestConditions { // 禁用缓存,强制从服务端获取最新状态 IfNoneMatch = ETag.All } }; if (!await blobClient.ExistsAsync(existsOptions)) { return null; }
额外优化点
- 避免使用
MemoryStream,DownloadContentAsync()直接返回Blob内容的字符串表示,减少内存分配和流操作开销; - 确保客户端配置了合理的重试策略,可在创建
BlobServiceClient时设置BlobClientOptions的Retry属性,应对临时网络波动:
var blobClientOptions = new BlobClientOptions { Retry = { Delay = TimeSpan.FromSeconds(2), MaxDelay = TimeSpan.FromSeconds(10), MaxRetries = 3, Mode = RetryMode.Exponential } }; var blobServiceClient = new BlobServiceClient(connectionString, blobClientOptions);
关键说明
Azure Blob Storage本身支持高并发读取,同一Blob可被多个客户端同时访问,问题并非来自存储服务本身,而是原代码中分离的存在性检查与下载操作导致的竞态条件。采用直接下载+异常捕获的方式能从根本上避免此类问题。
内容的提问来源于stack exchange,提问作者Gangadhar
相关产品推荐
相关产品推荐

