简介围绕华为OBS对象存储服务的Java开发需求这份 esdk-obs-java-3.20.3.zip 是面向中高级Java开发者与云端应用集成人员的官方SDK资源包。压缩包共766个文件大小仅7.31MB以449个HTML API文档和286个Java源码/示例为主辅以XML配置、JAR依赖、Markdown说明等便于查阅类接口、跟踪版本变更并快速上手。已有1833人学习下载。包内包含pom.xml、samples_java示例目录、log4j2.xml日志配置、javadoc归档及source源码等覆盖上传、下载、桶管理、对象删除等典型操作并涉及预签名URL、MD5校验等进阶实践。借助这些材料开发者可系统理解OBS SDK结构减少集成排错成本在Java应用中高效落地云存储功能。 拿到一个叫esdk-obs-java-3.20.3.zip的压缩包第一反应不是去解压而是先确认它解决什么问题。这套东西是华为云 OBS对象存储服务的 Java SDK 分发包版本 3.20.3。你所在的项目大概率已经存在一个 Java 后端要做文件上传、下载、日志归档、临时下载链接这类事而手写 REST 签名请求在第一个月就会让人崩溃MD5、签名算法、Chunked 编码、重试、断点续传全要自己造。用这个 SDK你只需关心桶名、对象名和 IO 流。这篇笔记直接把工程项目里最常用的初始化、上传下载、参数调整和排错路径拆开讲新手能照做熟手能避坑。1. 拿到 esdk-obs-java-3.20.3.zip 后先搞清这个包怎么进你的 Java 工程这套 SDK 不是那种下载下来解压丢进 lib 就能跑的玩具包。它自带协议实现、签名逻辑、连接池管理和分片上传能力所以依赖一批第三方库。真正需要它的场景通常出现在三类人手里做 Java 后端集成对象存储的工程师、写数据迁移脚本的运维、以及需要在内网环境离线构建 Java 服务的交付人员。你会因为它踩坑也大概率是因为依赖冲突或 endpoint 配置错误而不是对象存储本身。解压之前先看两个问题你的服务能不能访问 Maven 中央仓库部署环境是公网访问 OBS 还是同区域 VPC 内网访问这两个答案决定了下面的集成方式。我给你的建议是如果你的构建机可以联网优先用 Maven 坐标引入如果被安全合规卡住必须离线zip 包就是官方准备好的离线依赖来源。2. 从 zip 到 ObsClient最小初始化与离线依赖引入ObsClient 是整个 SDK 的入口所有上传、下载、列举、删除操作都从它身上发起。这一章的目标是让你在 20 分钟内得到一个能跑通的最小工程而不是停留在jar 包放哪这一步。2.1 先看 zip 里装了什么命名规律与离线引入解压后你通常能看到这几类内容主 jar 包命名类似esdk-obs-java-3.20.3.jar一批依赖 jar放在 lib 或 dependencies 类型的目录下Sleep 示例代码一般有 sample 包另外就是 README 或版本说明里面会标注这个版本对应的第三方依赖清单和已知问题。你不需要手动把每个 jar 都塞进 WEB-INF/lib但需要知道它依赖了 HTTP 客户端、日志框架、JSON 解析这类基础库。这里有个容易忽略的点zip 里的依赖 jar 和项目里已有的框架可能冲突。最常见的翻车现场是 SDK 自带的 HTTP 客户端版本把 Spring Boot 的版本顶掉了。如果你在 Maven 中央仓库可用的环境里千万别解压 zip 后手动引 jar直接把坐标写进 pom 让 Maven 解析依赖树冲突时还能用 exclusions 精准排除。如果你确实在隔离内网离线装 jar 到本地仓库才是正路命令如下mvn install:install-file \ -Dfile/opt/offline/esdk-obs-java-3.20.3.jar \ -DgroupIdcom.huaweicloud.esdk.obs \ -DartifactIdesdk-obs-java \ -Dversion3.20.3 \ -Dpackagingjar执行完后esdk-obs-java就进入了本机 Maven 仓库之后在 pom 里按坐标引用即可。-Dfile指向你解压出的主 jar 绝对路径-DgroupId、-DartifactId、-Dversion必须和 pom 里即将声明的坐标一致否则运行时会出现 NoClassDefFoundError 这种让你怀疑人生的错误。如果你还需要把依赖 jar 也装进仓库就逐个执行同样的命令或者直接用 nexus 私服批量上传不要在本地仓库手工拷文件。顺带提醒一句离线环境下请把整个 zip 连同它的 checksum 文件一起收进公司的依赖管理目录。你不知道哪次安全扫描会要求核对 jar 包完整性到时候没有原始校验值只能重新走一遍流程。2.2 用 Maven 坐标引入还是解压 jar两种方式对比如果构建机能连公网直接使用 Maven 坐标dependency groupIdcom.huaweicloud.esdk.obs/groupId artifactIdesdk-obs-java/artifactId version3.20.3/version /dependency这种方式的好处是 Maven 会帮你把 log4j、HTTP client 等传递依赖自动拉齐。坏处是你无法完全控制传递依赖版本所以建议在这个依赖上显式加一个exclusions把和 Spring Boot 内置版本冲突的库排除掉。离线场景则是另一套玩法zip 包解压后你面对的是一个固定的依赖集合。它的好处是交付给服务器时不用在构建机上联网解析依赖坏处是升级麻烦。下次想升 3.20.4你必须重新要一份完整 zip而不是只换主 jar因为新版本可能新增了依赖 jar。对比下来我的实践经验是开发环境一律用 Maven 坐标打生产包用离线 zip 加本地仓库。两套方案并存但不要混用。混用的后果通常是 classpath 里出现两个版本的同一个类然后 JVM 静默加载了老的你排查一整天只看到 java.lang.NoSuchMethodError。2.3 初始化 ObsClient 的最小可用代码无论是上传还是下载第一步都是创建 ObsClient。上面提到的两种引入方式在代码层面没有任何区别API 是同一套。最简初始化代码如下import com.obs.services.ObsClient; import com.obs.services.ObsConfiguration; public class ObsFactory { private static final String ENDPOINT obs.cn-north-4.myhuaweicloud.com; private static final String AK System.getenv(OBS_AK); private static final String SK System.getenv(OBS_SK); public static ObsClient newClient() { ObsConfiguration config new ObsConfiguration(); config.setEndPoint(ENDPOINT); config.setConnectionTimeout(10000); config.setSocketTimeout(60000); return new ObsClient(AK, SK, config); } }这段代码有几个关键点要讲清楚。第一ObsClient是有状态的内部持有连接池和线程池不要在每次操作时都 new 一个正确的做法是做成单例在应用启动时创建、关闭时统一 close。第二ENDPOINT只填域名不要带https://SDK 会自己拼协议填带协议的地址不会报错但代理场景下很容易出现重复前缀问题。第三AK 和 SK 从环境变量取而不是写死在类里。OBS_AK、OBS_SK这个命名是我自己的习惯你用环境变量名可以随意但原则只有一个不要把密钥提交进 Git。如果你是用临时凭证做联邦认证比如从 STS 服务换来的 tokenObsClient 构造器还支持传入 token。那个场景下要额外注意有效期token 过期后连接池里的连接并不会自动换必须重新创建 ObsClient。所以建议把 ObsClient 的创建包一层并且不要缓存超过 token 过期时间。3. 上传下载与列举删除核心对象操作的代码级拆解ObsClient 创建好之后最常用的四类操作就是这个章节的内容。我把生产环境常见的写法组合整理出来代码均以 3.20.3 的 API 为准。3.1 上传对象putObject 与文件、流两种重载上传是使用频率最高的操作。putObject 有两个常用重载直接传File对象适合明确知道文件路径的场景传InputStream适合处理请求体、临时文件等不落盘的场景。以下代码同时演示两种ObsClient client ObsFactory.newClient(); try { // 方式一文件上传SDK 内部读取文件并计算内容校验值 client.putObject(my-bucket, backup/2024-05-01.tar.gz, new File(/data/backup/2024-05-01.tar.gz)); // 方式二流式上传可附带对象元数据 try (InputStream in new FileInputStream(/data/app.log)) { ObjectMetadata metadata new ObjectMetadata(); metadata.setContentType(text/plain); metadata.setContentLength(new File(/data/app.log).length()); client.putObject(my-bucket, logs/app.log, in, metadata); } } finally { client.close(); }区别在哪里方式一里SDK 在请求时会自己读文件、填 ContentLength、算 MD5你不需要管理流的生命周期。方式二里ContentLength 必须和实际流长度一致不一致会直接报错或导致服务端接收不完整数据。如果你不知道流的长度还可以用PutObjectRequest把流包一层让 SDK 用 Expect: 100-continue 协商但这种做法在代理环境下容易多等一个 RTT不推荐内部接口使用。对象 key 的命名也要养成习惯。不要用以/开头的绝对路径也不要写./前缀这些符号会成为对象名的一部分未来在控制台里排查时会看到一堆诡异的对象。统一用目录/文件名这种不带前导斜杠的写法。注意client.close()放在 finally 里是底线操作。有的同事图省事直接不关在低并发下看不出问题一旦 QPS 上来连接池被占满应用就卡死在上传环节。ObsClient 本身实现了 Closeable强烈建议用 try-with-resources 包裹整个业务操作块。3.2 下载对象getObject 返回的是流记得关下载的 API 看起来简单却最容易泄漏连接。getObject 返回的ObsObject里保存了对象元数据和内容流内容流不关闭底层 HTTP 连接就无法归还连接池。以下是安全写法ObsObject obj client.getObject(my-bucket, logs/app.log); try (InputStream in obj.getObjectContent(); FileOutputStream out new FileOutputStream(/data/restore/app.log)) { byte[] buffer new byte[8192]; int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } } catch (IOException e) { // 记录对象 key、桶名、错误码方便事后重试 throw new RuntimeException(download failed: logs/app.log, e); }getObjectContent()拿到的是网络流读取完必须关闭而且要在读取的同一个 try 块里关闭。如果把流交给别的线程异步读取连接归还时机就会失控。大文件下载建议直接用getObject加Range参数分段拉取而不是一次性读进堆内存后面第六章会提到对应进阶写法。另一个我踩过的坑对象不存在时getObject 抛出的不是普通 IOException而是 OBS 服务端错误里面带着 HTTP 状态码 404 和 OBS 错误码。不要只打 exception.getMessage()要把e.getErrorCode()和e.getErrorMessage()都记录下来排查 NoSuchKey 时这两个字段能直接告诉你答案。3.3 列举与删除分页参数和批量删除技巧控制台里能看到的对象列表在 SDK 里是一页一页拉的。listObjects 默认每次最多返回 1000 个对象当你需要全量扫描时必须处理分页游标。以下是我常用的分页列举代码ListObjectsRequest request new ListObjectsRequest(my-bucket); request.setPrefix(backup/); request.setMaxKeys(1000); ObjectListing listing; do { listing client.listObjects(request); for (ObsObject obj : listing.getObjects()) { System.out.println(obj.getObjectKey() - obj.getMetadata().getContentLength()); } request.setMarker(listing.getNextMarker()); } while (listing.isTruncated());setMaxKeys官方上限就是 1000设置超过上限服务端也会截断。isTruncated()告诉你当前这页有没有截断getNextMarker()是下一页的起点。这里容易理解错的是prefix它不是目录而是对象 key 的前缀匹配。你想列backup/下所有对象prefix 必须带末尾斜杠否则会误匹配backup-2024这种名字。删除操作有单删和批量删两种。单删用deleteObject(bucket, key)就够了批量删除我这样写DeleteObjectsRequest delRequest new DeleteObjectsRequest(my-bucket); ListKeyAndVersion keyList new ArrayList(); keyList.add(new KeyAndVersion(backup/2024-05-01.tar.gz)); keyList.add(new KeyAndVersion(backup/2024-05-02.tar.gz)); delRequest.setKeyAndVersions(keyList); // 静默模式只返回失败项减少响应体大小 delRequest.setQuiet(true); DeleteObjectsResult delResult client.deleteObjects(delRequest); if (delResult.getErrorResults() ! null) { for (DeleteObjectsResult.DeleteObjectResult error : delResult.getErrorResults()) { System.out.println(failed: error.getObjectKey()); } }批量删除单次最多 1000 个对象超过必须分批。setQuiet(true)让服务端只返回失败列表省流量也省解析时间。如果你要删除的对象数量级在百万级不要用这个方法逐个分页删直接走生命周期策略给桶配一条过期规则让 OBS 后台自己清成本和时间都更优。4. 必调的 ObsConfiguration 参数Endpoint、超时、重试与代理很多工程师把 SDK 集成跑通就认为完事了直到线上出现批量超时才开始翻配置。这一章把 ObsConfiguration 里最值得先动的几个参数讲清楚并给出起步值。4.1 Endpoint 选不对网络链路差别很大Endpoint 决定你的请求从哪条链路走。公网 endpoint 适合本地开发、混合云场景内网 endpoint 适合部署在华为云同一 Region 的 ECS 上访问 OBS。它们的域名规律一般是obs.region-id.myhuaweicloud.com内网 endpoint 的 region-id 要特意确认因为同 Region 内网访问不产生流量费用公网则按流量计费。我见过最典型的选型错误ECS 在北京四区代码里配的却是上海一的 endpoint。请求绕了大半个网络延迟从 5ms 变成 50ms大文件上传直接喂满了公网带宽月底账单吓人。正确做法是生产环境跟着 ECS 所在 Region 走测试环境再单独区分。如果你的服务要跨云访问比如部署在自建机房那就不要硬连公网 endpoint优先走专线或公网加速域名。这个决策无法从 SDK 本身看出来得靠你现有网络架构判断。SDK 只负责把请求发到 endpoint不负责选路。4.2 超时、重试与连接池先记住这张表ObsConfiguration 里值得先调的参数我按优先级列一张表参数起步值说明connectionTimeout10000 ms建立 TCP 连接的超时时间。跨地域访问时太短会频繁失败socketTimeout60000 ms等待响应的超时时间。大对象下载期间太久没收到数据才会触发不是总耗时maxConnections500连接池上限。单机高并发上传时默认值很可能不够maxIdleConnections50空闲连接数保留太多会占端口资源重试次数SDK 内部管理连接异常和部分 5xx 会重试幂等操作才能放心开高重试authTypeOBSSignature华为云 OBS 必须保持默认的签名类型改其他值会直接鉴权失败表格里的参数用config.setConnectionTimeout(10000)这样调用即可。我说说为什么 socketTimeout 不能随便设成 3 秒。有人为了故障快速失败把 socketTimeout 调低结果下载 2GB 大文件时网络稍有抖动就断SDK 重试又重头下载实际更慢。socketTimeout 只在 IO 空闲时计时所以给到 60 秒相当安全。连接池数值不是越大越好。500 个连接意味着你的文件描述符占用也会跟着上去ECS 默认 ulimit 不够时会出现 Cannot assign requested address。调整前先ulimit -n看一眼上限。另外要注意ObsClient 是多线程安全的并发上传时不要为每个线程建一个 client共享同一个 client 就能复用连接池。重试这块尤其要小心。OBS 是幂等语义吗PUT 覆盖是幂等的但如果你用追加写对象appendObject重试可能会重复追加。所以生产环境里写操作的重试逻辑最好自己控制而 SDK 自带的自动重试我只信任它在 GET 和 DELETE 场景下的表现。4.3 代理、HTTPS 与 AK/SK 的安全存放内网边界通常有安全代理。ObsConfiguration 支持代理设置config.setProxy(proxy.company.internal, 8080);代理配置只对 HTTP 连接生效如果代理要求认证SDK 也提供了对应的用户名密码设置项。这里最容易出问题的是代理的 CONNECT 隧道处理高并发连接代理时代理端连接数限制也会变成瓶颈。排查方法很简单出现大量 Connection timed out 时先去掉代理直连 OBS 试试如果立刻恢复说明问题在代理而非 SDK。AK/SK 的存放是我每次代码审查必看的地方。不要写进 properties、不要写进启动参数、更不要提交到 Git。我习惯做成环境变量配合公司的密钥管理服务在启动时注入。临时凭证用户还要注意ObsClient 内部缓存了令牌状态令牌刷新机制要自己实现SDK 不会帮你自动续期。HTTPS 证书这块若是自建代理或内网测试环境用了自签证书SDK 的 HTTP 客户端会验证证书链。不要为了省事全局信任所有证书正确做法是把自签名证书导入到 JVM 的 cacerts 或者用单独的 SSLContext 注入。全局信任会让内网抓包排查变成摆设也会引入中间人攻击面。5. 用 esdk-obs-java 常见问题排查从 AccessDenied 到内存泄漏在项目实施中我把高频故障整理成五类每一条都是遇到过的真实场景。按现象 → 原因 → 解决的顺序写方便你直接对号入座。5.1 上传一直 403 AccessDeniedAK/SK 却确认无误现象putObject 报 AccessDeniedAK/SK 在控制台能登录在代码里却鉴权失败。原因有这么几种最常见的是服务器系统时间与标准时间偏差超过 15 分钟。OBS 签名里带了时间戳服务端校验时发现时间偏离直接拒绝其次是该 AK/SK 对应的用户没有这个桶的写权限控制台登录不代表 API 权限再就是 ObsConfiguration 里 authType 被改动过签名算法与桶侧不匹配。解决先date -u看服务器时间偏差大就用 ntpdate 或 chrony 同步。时间没问题再查 IAM 权限策略最后检查 ObsConfiguration 是否显式设置了 authType。我建议在初始化代码里打一行日志输出当前时间和 endpoint出现问题时先对齐这两个事实能省很多排查时间。5.2 高并发时连接池耗尽上传大面积超时现象并发 200 个上传任务时部分任务报Timeout waiting for connection应用日志里出现大量连接获取失败。原因最常见的不是连接数上限小而是业务代码里每次请求都新建 ObsClient用完不 close连接池里的连接被垃圾回收器连带释放但释放前并没有归还给池子导致池内可用连接不断减少。解决ObsClient 全局单例化所有线程共用上传完关闭的应该是 IO 流而不是 ObsClient若单机上线索确实大把 maxConnections 调到 500同时检查 ECS 的连接跟踪表是否爆掉。跑批任务结束后调用一次client.close()释放全部连接比放任不管稳妥。5.3 小文件没问题传大文件时 JVM 堆被撑爆现象传 10MB 文件正常传 3GB 文件时堆内存飙升Full GC 频繁最后 OOM。原因调用方用了Files.readAllBytes()后再 putObject文件全部进堆或者用了流式上传但没指定 ContentLengthSDK 为了算长度会把流缓冲到内存同样是灾难。解决直接用 putObject 的 File 重载SDK 会按固定缓冲区流式读取非要传 InputStream必须显式设置 ContentLength 或使用 chunked 编码。超过 100MB 的文件不要手动分片用第六章的 uploadFile 接口SDK 内部才会做真正的磁盘流式分片上传堆内存峰值能控制在十几 MB。5.4 刚上传成功立刻 getObject 报 NoSuchKey现象putObject 返回 200同一把 key 去 getObject报 404 NoSuchKey。原因对象不存在通常只有一个解释——上传时用的 key 和读取时用的 key 不一致。常见于 key 里带了隐藏字符、前导斜杠、或者上传时用了相对目录而读取时拼接了绝对路径。另一个可能性是列举时用了不同前缀看似同名对象其实是两个 key。解决把上传和下载的 key 都打日志肉眼比对字符不要手工拼 key封装一个方法统一生成对象路径内部拼接目录和文件名避免一个地方多一个斜杠。如果你用了临时 token还要检查 STS 临时凭证的 scope 是否覆盖该桶否则上传路径被拒绝后会静默退化成匿名权限。5.5 内网 ECS 无法从公网 endpoint 下载大对象现象ECS 在外网 endpoint 下载 1GB 文件时反复断连时延大且流量账单异常。原因同 Region 的 ECS 访问 OBS 应该走内网 endpoint。公网链路会经过 NAT 和防火墙大流量长时间传输容易被安全策略中断同时公网流量按 GB 计费成本翻几倍。解决把 endpoint 换成对应 Region 的内网地址代码逻辑不用动安全组确认放行到 OBS 的 TCP 443 出方向。这里有个验证技巧配置改完先下载一个小对象看耗时如果延迟从 30ms 降到 5ms 以内基本就对了。内网 endpoint 只在云内网可达本地开发调试时不要用它本地连不上是正常的。6. 断点续传与临时 URL 签名两个值得先学的进阶用法解决了基础操作和坑接下来这两个 API 是生产环境最常用的进阶功能大文件断点续传和免暴露密钥的临时下载链接。6.1 大文件断点续传uploadFile 与 checkpoint 参数对象存储的单个对象上限是 5TB但一次 HTTP 请求传大文件既不现实也不稳定。uploadFile 接口把文件切成多个分片并发上传并且支持 checkpoint 记录进度失败后断点续传避免了从头再来。以下是基础用法UploadFileRequest request new UploadFileRequest(my-bucket, backup/nodelete.img); request.setUploadFile(/data/backup/nodelete.img); request.setPartSize(10 * 1024 * 1024); // 每个分片 10MB request.setTaskNum(5); // 并发上传 5 个分片 request.setEnableCheckpoint(true); // 开启断点续传记录 UploadFileResult result client.uploadFile(request);partSize建议在 5MB 到 50MB 之间分片太小会导致请求次数过多分片太大会让单分片失败重试的代价变大。taskNum对应线程数我这里给 5你可以根据 ECS 的 CPU 核数和带宽调整到 8 或 10。enableCheckpoint为 true 时SDK 会把上传进度写到本地文件失败后重新调用同参数的 uploadFile 即可从断点继续。注意 checkpoint 文件本身要放到有磁盘写权限的目录并且与大文件不在同一路径下最好避免误删。如果业务允许我会对超过 500MB 的文件强制走 uploadFile不让同事用 putObject 一把梭。6.2 临时 URL分享下载链接不暴露 AK/SK给前端直接生成下载链接时你的目标是不暴露密钥同时控制访问时效。临时 URL 是 OBS 签好一个带有效期的 URL过期即失效。生成代码如下TemporarySignatureRequest request new TemporarySignatureRequest(); request.setMethod(HttpMethodEnum.GET); request.setBucketName(my-bucket); request.setObjectKey(report/2024-05.pdf); request.setExpires(3600); // 有效期 3600 秒 TemporarySignatureResponse response client.createTemporarySignature(request); String signedUrl response.getSignedUrl(); // 把 signedUrl 下发给前端而不是把 AK/SK 发给前端signedUrl直接拼了一串签名参数前端拿到后放在 img 标签或 a 标签里就能下载。expires我习惯按业务需要设置内部附件下载给 10 分钟面向外部用户的报表给 24 小时。临时 URL 生成不消耗 OBS 流量只有真正下载时才计费。这里的坑有两个临时 URL 的权限范围是生成时指定的操作想覆盖上传就要用 PUT 方法重新签名二是临时 URL 是基于密钥签的密钥轮换后之前签发的 URL 会全部失效。所以密钥轮换计划里要提前一天收紧有效时长别让业务侧在老密钥过期后还拿着 7 天有效的 URL。6.3 打开 SDK 日志验证请求细节排查问题时光看异常消息往往不够。SDK 本身基于日志框架输出 HTTP 请求细节把相关包的日志级别调到 DEBUG 或 TRACE能看到请求行、状态码和响应头。我一般把com.obs.services这个包的日志级别单独调高不影响其他业务日志的噪音。日志里最值钱的字段是 requestId它对应 OBS 服务端一次完整请求的追踪编号。跟华为云提工单或自查时提供 requestId 能直接定位到服务端处理记录。带 requestId 的日志要保留至少一周等线上问题复现时再开日志就晚了。另一个习惯是每次批量上传前打印一个批次 ID把 requestId 和业务文件路径关联上事后分析时能快速过滤出该批次的所有请求。这些年用对象存储 SDK我最深的教训是把 AK/SK 硬编码在测试类里提交一天后被公司扫描工具抓到第二天全员轮换密钥所有线上服务重启。从那以后密钥一律从环境变量注入代码评审里出现new ObsClient(开头的行直接打回。这套 SDK 很容易上手真正拉开差距的是使用习惯和参数边界。断点续传、临时 URL、连接池管理、日志留痕这四个能力先用起来再回头看今天的踩坑清单你会发现大部分问题都能提前规避。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?