Azure Databricks Notebook读取Azure Blob Storage文件挂起解决方法
问题原因
本地环境代码运行正常但Azure Databricks环境永久挂起,均为Databricks运行时和本地环境的差异导致,常见触发场景如下:
- 网络连通性被静默拦截:如果Databricks工作区部署在自定义VNet下,未配置存储账户服务终结点、NSG/防火墙规则拦截了集群节点到Blob存储443端口的出站流量,或是存储账户防火墙允许列表未加入Databricks集群的出口网段/公网IP,TCP连接会被中间设备静默丢弃。而Azure Blob SDK默认无连接超时配置,会持续重试TCP握手,表现为进程永久挂起,不会抛出异常。
- 依赖类路径冲突:Databricks运行时自带了内置版本的Azure Storage SDK、Netty网络依赖,如果代码调用了和运行时内置版本不兼容的SDK包,会出现HTTP客户端初始化死锁、底层请求无调度资源的问题,这类问题不会抛出类冲突、类不存在的报错,仅会表现为请求卡住。
- SDK默认客户端兼容性问题:新版Azure Blob SDK默认使用Reactor Netty异步客户端,在Databricks Notebook的主线程类加载上下文下,偶发异步线程调度死锁,导致打开输入流的请求永远无法拿到执行资源。
- 额外注意:示例代码中
containerName参数占位符多写了一个右尖括号>,替换真实参数时如果带入了多余符号,也可能出现非预期的请求异常。
排查解决步骤
按以下顺序逐步排查,可覆盖99%以上的同类问题:
- 先验证基础网络连通性
在Notebook新建单元格执行如下Shell命令,跳过SDK直接测试节点到存储的连通性:%sh curl -v -m 15 "https://<storage-account>.blob.core.windows.net/<container-name>/<blob-name>?<sas-token>"- 如果命令执行超时、报Connection refused,直接定位为网络规则问题:VNet注入的工作区需要给子网配置Microsoft.Storage服务终结点,或将子网加入存储账户防火墙允许列表;公网访问的工作区需要把集群出口公网IP加入存储账户允许列表,同时检查集群关联NSG是否放开443端口到存储服务的出站规则。
- 如果命令正常返回Blob内容,说明网络无问题,继续往下排查。
- 解决依赖冲突
在Notebook最开头新增依赖配置单元格,显式引入经过兼容性验证的SDK版本,避免和运行时内置依赖冲突,执行后重启集群内核:%dep z.add("com.azure:azure-storage-blob:12.23.0") z.add("com.azure:azure-core-http-netty:1.43.0") - 显式配置SDK超时与HTTP客户端,规避默认配置死锁
不要使用SDK默认的无超时配置,手动初始化HTTP客户端并设置合理超时,修改后的可运行代码如下:import com.azure.storage.blob.BlobClientBuilder import com.azure.core.http.netty.NettyAsyncHttpClientBuilder import java.io.InputStream import java.time.Duration // 显式初始化HTTP客户端,配置短超时避免永久挂起 val httpClient = new NettyAsyncHttpClientBuilder() .connectTimeout(Duration.ofSeconds(10)) .responseTimeout(Duration.ofSeconds(30)) .build() val input: InputStream = new BlobClientBuilder() .endpoint("https://<storage-account>.blob.core.windows.net") .sasToken("<sas-token>") .containerName("<container-name>") // 替换时注意删除多余的>符号 .blobName("<blob-name>") .httpClient(httpClient) .buildClient() .openInputStream() - 兼容方案兜底
如果上述方案仍有问题,可直接使用Databricks内置的Hadoop ABFS客户端访问存储,无需额外引入第三方SDK,完全兼容Databricks运行时环境:// 配置SAS认证 spark.conf.set( "fs.azure.sas.<container-name>.<storage-account>.blob.core.windows.net", "<sas-token>" ) // 读取Blob获取输入流 val inputStream = spark.read.format("binaryFile") .load("wasbs://<container-name>@<storage-account>.blob.core.windows.net/<blob-name>") .select("content") .head() .getAs[Array[Byte]](0) .inputStream()
内容的提问来源于stack exchange,提问作者Ryan Lindbeck
相关产品推荐
相关产品推荐

