NativeAzureFileSystem递归删除非空文件夹失败求助
问题场景
配置无异常(空文件夹删除等操作均正常),使用最新版本hadoop-azure-3.3.6.jar调用Hadoop FileSystem API删除Azure Blob存储上的非空目录时失败。
调用代码:
// <container-name>,<storage-account> replaced with actual values Path src = new Path("wasbs://<container-name>@<storage-account>.blob.core.windows.net/"); FileSystem fs = src.getFileSystem(jsc.hadoopConfiguration()); fs.delete(src, true);
报错信息:
org.apache.hadoop.fs.azure.AzureException: com.microsoft.azure.storage.StorageException: This operation is not permitted on a non-empty directory. at org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.delete(AzureNativeFileSystemStore.java:2720) at org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.delete(AzureNativeFileSystemStore.java:2730) at org.apache.hadoop.fs.azure.NativeAzureFileSystem.deleteFile(NativeAzureFileSystem.java:2670) at org.apache.hadoop.fs.azure.NativeAzureFileSystem$2.execute(NativeAzureFileSystem.java:2421) at org.apache.hadoop.fs.azure.AzureFileSystemThreadPoolExecutor.executeParallel(AzureFileSystemThreadPoolExecutor.java:223) at org.apache.hadoop.fs.azure.NativeAzureFileSystem.deleteWithoutAuth(NativeAzureFileSystem.java:2433) at org.apache.hadoop.fs.azure.NativeAzureFileSystem.delete(NativeAzureFileSystem.java:2483) at org.apache.hadoop.fs.azure.NativeAzureFileSystem.delete(NativeAzureFileSystem.java:1977)
由于该delete方法被多个Spark函数调用,无法自行替换递归删除逻辑,怀疑为hadoop-azure库的实现bug。
原因分析
Azure Blob存储本身不存在物理目录,所谓的“目录”是通过Blob路径前缀模拟实现的。Hadoop的Azure文件系统客户端需要自行递归遍历所有匹配前缀的Blob并删除,再清理虚拟目录。但从报错栈来看,AzureNativeFileSystemStore的delete方法没有正确执行递归删除逻辑,直接尝试删除非空的虚拟目录,触发了Azure存储的原生错误。
解决方案建议
1. 临时前置清理规避
在Spark触发delete操作前,手动遍历并删除目标路径下的所有Blob,再调用原delete方法清理空目录:
// 递归遍历并删除所有文件 RemoteIterator<LocatedFileStatus> fileIterator = fs.listFiles(src, true); while (fileIterator.hasNext()) { LocatedFileStatus fileStatus = fileIterator.next(); fs.delete(fileStatus.getPath(), false); } // 删除空目录 fs.delete(src, true);
2. 切换Hadoop-Azure版本
尝试升级到hadoop-azure-3.4.0+(若已发布)或降级到3.3.5版本,这类客户端实现bug通常会在版本迭代中被修复,旧版本可能未引入该问题。
3. 提交社区Bug报告
到Apache Hadoop社区提交bug报告,附上完整的复现步骤、代码片段和报错栈,明确说明:递归删除非空虚拟目录时,客户端未正确遍历删除前缀匹配的Blob,直接触发Azure存储的非空目录删除限制。
4. 直接使用Azure Storage SDK绕过Hadoop API
放弃Hadoop FileSystem API,直接使用Azure Storage Blob SDK递归删除目标前缀下的所有资源:
import com.azure.storage.blob.BlobServiceClient; import com.azure.storage.blob.BlobServiceClientBuilder; import com.azure.storage.blob.models.BlobItem; // 初始化Blob服务客户端 BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() .connectionString("your-storage-connection-string") .buildClient(); // 获取容器客户端 BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient("<container-name>"); // 递归删除所有匹配前缀的Blob和虚拟目录 containerClient.listBlobsByPrefix("<target-directory-path>/", true) .forEach(blobItem -> { if (blobItem.isDirectory()) { containerClient.getBlobContainerClient(blobItem.getName()).delete(); } else { containerClient.getBlobClient(blobItem.getName()).delete(); } });
该方式不受Hadoop客户端逻辑限制,可直接操作Azure存储资源,需确保权限配置与原Hadoop作业一致。
内容的提问来源于stack exchange,提问作者Sameh Ayoub

