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

Azure Block Blob压缩大于2GB文件时报指定块列表无效异常如何解决

异常产生原因

  1. Azure Blob 分块上传特性与ZipArchive写入逻辑冲突
    你使用的Microsoft.Azure.Storage.Blob 11.2.2版本中,CloudBlockBlob.OpenWrite()返回的CloudBlobStream属于只进流,默认不支持随机查找(Seek)操作。同时该版本默认的单Blob上传阈值为2GB,小于2GB的文件会走单块直接上传逻辑,不会触发分块;超过2GB后会自动切换为分块上传,每个块默认大小为4MB,最多允许50000个分块。
    而ZipArchive在Create模式下,当所有压缩条目写入完成、释放ZipArchive对象时,会
    尝试回退到流的起始位置写入Zip文件的头部元数据,以及在流末尾写入中央目录记录
    ,这个操作依赖流的查找能力。当流不支持查找时,这部分写入操作会被当作新的内容追加到上传流中,导致分块的顺序、长度和预期不符,最终提交给Azure存储服务的块列表不符合规范,抛出"The specified block list is invalid."异常。
  2. 隐式问题:你的代码中没有显式设置分块大小,默认4MB的分块虽然理论上可以支持到近200GB的文件,但和ZipArchive的查找操作冲突后,刚好在超过2GB触发分块上传的阈值时暴露问题。

解决方案

方案1:使用本地临时文件作为中间缓冲(推荐,大文件场景内存占用最低)

先生成完整的Zip文件到本地临时磁盘,等Zip完全写完后,再把整个文件上传到Blob存储,从根源避免直接写Blob流的查找冲突问题。
修改后的代码逻辑示例:

public async Task DoWork(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        await _messageSemaphore.WaitAsync(cancellationToken);
        MyModel model = await _queue.PeekMessage();
        if(model != null)
        {
            try
            {
                var zipBlockBlob = await _storageAccount.GetFileBlobReference(_configuration[ConfigKeys.ContainerName], model.Filename, model.FileRelativePath);
                // 生成临时文件路径
                var tempZipPath = Path.GetTempFileName();
                try
                {
                    // 先写Zip到本地临时文件
                    using (var tempFileStream = File.Open(tempZipPath, FileMode.Create))
                    {
                        using (var archive = new ZipArchive(tempFileStream, ZipArchiveMode.Create, false))
                        {
                            foreach (var fileUri in Files)
                            {
                                var file = new Uri(fileUri);
                                var cloudBlockBlob = blobContainer.GetBlockBlobReference(file);
                                var zipEntry = archive.CreateEntry(model.Filename, CompressionLevel.Fastest);
                                using (var blobStream = await cloudBlockBlob.OpenReadAsync())
                                using (var zipEntryStream = zipEntry.Open())
                                {
                                    await blobStream.CopyToAsync(zipEntryStream, cancellationToken);
                                }
                            }
                        }
                    }

                    // 临时文件写完后上传到Blob
                    using (var uploadStream = File.OpenRead(tempZipPath))
                    {
                        // 显式设置分块大小为100MB,最大支持50000*100MB=5TB的Zip文件
                        zipBlockBlob.StreamWriteSizeInBytes = 100 * 1024 * 1024;
                        await zipBlockBlob.UploadFromStreamAsync(uploadStream, cancellationToken);
                    }
                }
                finally
                {
                    // 上传完成后删除临时文件
                    if (File.Exists(tempZipPath))
                        File.Delete(tempZipPath);
                }
            }
            catch (FileNotFoundException e)
            {
                Console.WriteLine($"Download Error: {e.Message}");
            }
            catch (Microsoft.Azure.Storage.StorageException e)
            {
                Console.WriteLine($"Storage Exception Error: {e.Message}");
            }
            finally
            {
                _messageSemaphore.Release();
            }
        }
        else
        {
            _messageSemaphore.Release();
            await Task.Delay(_sleepTime, cancellationToken);
        }
    }
}

方案2:使用可查找的内存流作为中间缓冲(适合磁盘IO受限、文件不会过大的场景)

如果你的服务有足够的内存资源,可以使用可回收内存流代替本地临时文件,逻辑和方案1一致,只是把中间存储从磁盘换成内存,可以避免磁盘IO开销。

额外优化点

你现有的DoWork方法用了async void,属于不安全的异步写法,建议改成async Task返回值,避免未捕获的异常直接终止进程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 10:54:01