☰
esdk-obs-java实战:华为云OBS上传下载与断点续传指南
2026/10/8 19:57:01 网站建设 项目流程

简介:华为对象存储服务(OBS)的 Java 开发工具包 3.20.3 完整资源包,面向需要在 Java 应用中接入华为云存储的开发者,适合正在使用或计划使用对象存储的初中级 Java 工程师,解决数据上传、下载、桶的创建与删除、权限管理等场景的集成需求。压缩包共 766 个文件,约 7.31MB,主要包含网页格式的接口文档、示例源码、配置文件和依赖库,兼有日志配置、脚本及许可说明;其中接口文档便于离线查阅类与方法说明,示例源码覆盖上传、下载、列举桶等高频操作。目前已有 1833 人学习下载。资源内含核心客户端类的详细接口文档、可运行示例、构建依赖配置、日志方案与第三方库说明,能帮助开发者快速搭建环境,掌握对象上传下载、桶的列举与删除、异常处理、预签名链接及数据完整性校验等关键实践,还可参考并发参数设置与安全策略,减少上线后的对象存储故障,适合从入门到生产排错全流程使用。

1. esdk-obs-java-3.20.3.zip:拿到手第一步该做什么

接手一个数据归档项目,甲方丢过来一个esdk-obs-java-3.20.3.zip,说“把这包里的能力接进我们的 Java 服务”。这个 zip 是华为云对象存储服务 OBS 的官方 Java SDK 发布包,esdk 是华为云 SDK 的命名前缀,obs 对应对象存储服务,java 说明语言生态,3.20.3 是版本号。它的价值很直接:让 Java 应用通过几行代码完成文件上传、下载、列举、删除、断点续传和临时授权,不用自己封装 HTTP 签名和鉴权逻辑。适合正在做数据备份、日志归档、网盘类应用或者从自建存储往云上迁移的 Java 工程师。先说清楚一件事:这个 OBS 是对象存储,不是直播推流用的 OBS Studio,两者除了缩写一样没有任何关系,搜索资料时注意区分。

2. 引入 esdk-obs-java 依赖与初始化 ObsClient:三个容易翻车的配置点

2.1 Maven 坐标与本地 jar 两种引入方式

拿到 zip 之后先别急着解压扔进 lib 目录。这个发布包内部结构一般是esdk-obs-java-3.20.3.jar加一堆依赖 jar,以及文档和示例代码。如果你的项目是 Maven 管理,优先用坐标引入,让 Maven 帮你处理传递依赖,避免手动拷 jar 时漏掉依赖导致运行期NoClassDefFoundError。在 pom.xml 里加上:

<dependency> <groupId>com.huaweicloud</groupId> <artifactId>esdk-obs-java</artifactId> <version>3.20.3</version> </dependency>

如果项目在内网环境,Maven 中央仓库拉不下来,再把 zip 里的 jar 安装到本地仓库:

mvn install:install-file -Dfile=esdk-obs-java-3.20.3.jar \ -DgroupId=com.huaweicloud \ -DartifactId=esdk-obs-java \ -Dversion=3.20.3 \ -Dpackaging=jar

说明:esdk-obs-java这个 SDK 对 JDK 版本有最低要求,3.20.x 系列一般要求 JDK 8 以上,建议直接跑在 JDK 8 或 11 上,太新的 JDK 版本反而可能遇到反射权限问题。安装到本地仓库后,pom 里的坐标照写即可。这一步常见的翻车点是:只拷了主 jar 没拷依赖,启动时抛ClassNotFoundException: com.fasterxml.jackson.core.JsonProcessingException,因为 SDK 内部用 Jackson 做 JSON 序列化,日志和响应解析都依赖它。zip 里带的dependencies目录或lib目录下的 jar 一个都不能少。

2.2 endpoint、AK/SK 的正确姿势

初始化 ObsClient 是第一个真正的门槛。网上很多示例代码让人填 endpoint 时直接填https://obs.cn-north-4.myhuaweicloud.com,但 SDK 里 endpoint 只要填不带协议的主机名和端口,默认走 HTTPS。填错的表现很诡异:有的版本直接报IllegalArgumentException,有的版本连接超时,还有的能连上但 TLS 握手失败。正确写法:

import com.obs.services.ObsClient; import com.obs.services.exception.ObsException; // endpoint 只填主机名,不要加 https:// String endpoint = "obs.cn-north-4.myhuaweicloud.com"; String ak = System.getenv("OBS_AK"); String sk = System.getenv("OBS_SK"); ObsClient obsClient = new ObsClient(ak, sk, endpoint); System.out.println("ObsClient initialized");

参数说明:ak 和 sk 是华为云访问密钥,IAM 用户可以在控制台创建。硬编码在代码里是生产环境大忌,Git 仓库扫描工具能直接识别出 AK/SK 模式并告警,我通常从环境变量或配置中心读取。endpoint 必须和桶所在区域匹配,华北北京四是obs.cn-north-4.myhuaweicloud.com,华东上海一是obs.cn-east-3.myhuaweicloud.com,桶在哪个区就必须用哪个区的 endpoint,跨区访问会报 403 或 301 重定向错误。这个错误信息本身有迷惑性,它可能提示BucketNotLocated,很多人以为是权限问题,实际上是 endpoint 区域不匹配。

2.3 ObsClient 生命周期与线程安全

ObsClient 是线程安全的,可以复用,不要每次操作都 new 一个。这个类的设计是内部维护连接池,频繁创建销毁会耗尽 TCP 连接,典型的症状是跑一段时间后报Connection pool shut down或者Timeout waiting for idle object。正确做法是应用启动时初始化一个全局实例,Spring 项目里把它配成单例 Bean,应用关闭时调用obsClient.close()释放资源。

import javax.annotation.PreDestroy; import org.springframework.stereotype.Component; @Component public class ObsClientHolder { private ObsClient obsClient; public ObsClient getClient() { if (obsClient == null) { synchronized (this) { if (obsClient == null) { String endpoint = "obs.cn-north-4.myhuaweicloud.com"; String ak = System.getenv("OBS_AK"); String sk = System.getenv("OBS_SK"); obsClient = new ObsClient(ak, sk, endpoint); } } } return obsClient; } @PreDestroy public void close() { if (obsClient != null) { obsClient.close(); } } }

说明:双重检查锁在单机场景足够了,分布式场景也不需要担心,ObsClient 内部连接池本身就支持并发。有一点要注意:close()之后这个实例不能再用,Spring 容器销毁时如果还有其他线程正在上传,会抛IllegalStateException。生产环境建议在 close 之前先做优雅停机,等正在执行的任务结束或超时再关闭。这个类的线程安全边界是:所有读写操作都线程安全,但配置属性如超时时间、连接池大小在运行期修改不保证生效,需要调整就重新 new 一个实例。

3. 用 esdk-obs-java 跑通上传下载:最小可运行代码与参数调优

3.1 上传:putObject 与流式上传的选择

SDK 里上传接口看着简单,但选错方法会在生产环境付出代价。putObject有多个重载:传File、传InputStream、传字符串。最常见的错误是把一个几百 MB 的文件new FileInputStream后丢给putObject,结果 OOM。原因在于 SDK 为了计算 Content-MD5 或做重试,可能需要在内存里缓存流。我的经验是:小文件(小于 100 MB)直接用File重载,大文件走第四章的断点续传接口。小文件上传代码:

import com.obs.services.model.PutObjectRequest; import com.obs.services.model.ObjectMetadata; import com.obs.services.model.PutObjectResult; import java.io.File; ObsClient obsClient = getObsClient(); PutObjectRequest request = new PutObjectRequest(); request.setBucketName("my-backup-bucket"); request.setObjectKey("logs/app-2025-01-01.log"); request.setFile(new File("/data/logs/app-2025-01-01.log")); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType("text/plain"); metadata.setContentEncoding("gzip"); metadata.addUserMetadata("project", "billing"); request.setMetadata(metadata); PutObjectResult result = obsClient.putObject(request); System.out.println("ETag: " + result.getEtag());

参数说明:bucketName是桶名,全局唯一,创建后不能改。objectKey是对象在桶里的完整路径,SDK 不会帮你自动创建“目录”,OBS 的对象存储本质是扁平 key-value,logs/这个前缀只是约定俗成的模拟目录。setContentType影响浏览器直接访问时的响应头,不设置的话 OBS 可能按二进制流处理,导致图片或文本文件预览变成下载。addUserMetadata可以自定义元数据,所有 key 会以x-obs-meta-前缀存到服务端,适合存业务标签。getEtag()返回的是对象内容的 MD5,可以用来做上传完整性校验,但注意 OBS 的 ETag 对分段上传不是整个文件的 MD5,而是各分段 MD5 的拼接结果,所以只在小文件上传场景拿它当 MD5 用。

3.2 下载与列举:getObject 和 listObjects 的坑

下载接口比上传更隐蔽。getObject返回ObsObject,它的getObjectContent()是一个InputStream,用完必须关闭,否则连接池里的连接会被耗尽。很多线上故障就是这么来的:下载线程一多,连接池被打满,所有新请求开始排队超时。最小代码:

import com.obs.services.model.ObsObject; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; ObsObject obj = obsClient.getObject("my-backup-bucket", "logs/app-2025-01-01.log"); System.out.println("Object size: " + obj.getMetadata().getContentLength()); try (BufferedReader reader = new BufferedReader( new InputStreamReader(obj.getObjectContent(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { // 处理每一行日志 System.out.println(line); } } catch (Exception e) { e.printStackTrace(); }

try-with-resources 确保流关闭。获取对象元数据不要调getObject再关流,那样浪费一次完整的数据传输,应该用obsClient.getObjectMetadata(bucketName, objectKey),它只取头信息。列举对象时注意分页机制,listObjects默认每次最多返回 1000 个对象,需要循环用marker翻页:

import com.obs.services.model.ListObjectsRequest; import com.obs.services.model.ObjectListing; import com.obs.services.model.ObsObjectSummary; ListObjectsRequest request = new ListObjectsRequest("my-backup-bucket"); request.setPrefix("logs/2025/"); request.setMaxKeys(500); ObjectListing listing; do { listing = obsClient.listObjects(request); for (ObsObjectSummary summary : listing.getObjects()) { System.out.println(summary.getKey() + " -> " + summary.getSize()); } request.setMarker(listing.getNextMarker()); } while (listing.isTruncated());

说明:isTruncated()为 true 表示还有下一页,getNextMarker()的值作为下一次请求的marker。很多人第一次写会忘记翻页,只处理第一页 1000 个对象就认为完事了,桶里超过 1000 个文件时数据悄悄丢失。这个接口还支持setDelimiter("/")做目录层级聚合,但返回的是getCommonPrefixes(),和getObjects()是两个列表,处理时容易漏。

3.3 object key 设计:目录感是假的

对象存储的 key 设计决定后续运维成本。常见误区是拿本地文件路径直接当 key,比如C:\data\logs\app.log里的反斜杠在 Linux 环境下处理会变成C:\data\logs\app.log整个字符串,控制台里看起来就是一个怪名字。跨平台项目要统一用正斜杠拼接 key:

String basePath = "logs/" + LocalDate.now().toString(); String key = basePath + "/" + fileName.replace("\\", "/");

key 的命名建议按“业务/日期/文件名”分层,这样用prefix前缀查询时可以高效地按时间范围或业务线筛选。OBS 控制台会把带/的 key 渲染成树状目录,但底层没有任何目录实体,删除“目录”实际上是按前缀批量列举再逐个删除。另一个经验:不要把敏感信息放进 key,比如用户手机号、身份证号,key 会出现在访问日志和 CDN 回源记录里,等于明文泄露。key 里只放业务 ID 和日期,敏感信息放对象内容里并用服务端加密。

4. 大文件断点续传与临时授权 URL:生产环境才用得上的能力

4.1 uploadFile 断点续传:checkpoint 文件机制

几百 MB 以上的文件不要用putObject,用uploadFile。这个接口自动做分段上传,默认分段大小是 5 MB,支持断点续传。它的实现机制是:第一次上传时生成一个 checkpoint 文件,记录各分段上传状态;上传中断后再次调用,SDK 读取 checkpoint 文件跳过已完成的段。代码:

import com.obs.services.model.UploadFileRequest; import com.obs.services.model.UploadFileResult; UploadFileRequest request = new UploadFileRequest("my-backup-bucket", "backup/mysql-2025-01-01.sql.gz"); request.setUploadFile("/data/backup/mysql-2025-01-01.sql.gz"); request.setPartSize(10 * 1024 * 1024); // 每个分段 10 MB request.setTaskNum(5); // 并发上传 5 个分段 request.setEnableCheckpoint(true); request.setCheckpointFile("/data/checkpoint/mysql-2025-01-01.cp"); UploadFileResult result = obsClient.uploadFile(request); System.out.println("Upload status: " + result.getStatus());

参数说明:partSize决定分段大小,10 MB 到 100 MB 之间是常见选择。分段太小会导致请求次数过多,网络往返耗时占比上升;分段太大则单段失败重试成本高。taskNum是并发上传的分段数,不是线程池总线程数,5 到 10 是平衡值。enableCheckpoint开启断点续传,checkpointFile指定 checkpoint 文件路径。这个文件在任务成功后会被清空,但目录要预先建好,否则第一次上传就报FileNotFoundException。另外注意 checkpoint 文件和待上传文件是绑定的,同 key 换了个新文件再上传,必须换 checkpoint 路径或删掉旧 checkpoint,否则 SDK 认为断点还在,上传完成后对象内容是旧文件和新文件的混合体,这个坑我踩过一次,最后靠比对 ETag 才定位到。

4.2 downloadFile 断点下载与进度条

大文件下载同样有断点续传接口downloadFile,处理方式对称:

import com.obs.services.model.DownloadFileRequest; import com.obs.services.model.DownloadFileResult; DownloadFileRequest request = new DownloadFileRequest("my-backup-bucket", "backup/mysql-2025-01-01.sql.gz"); request.setDownloadFile("/data/download/mysql-2025-01-01.sql.gz"); request.setPartSize(10 * 1024 * 1024); request.setTaskNum(5); request.setEnableCheckpoint(true); request.setCheckpointFile("/data/checkpoint/mysql-download.cp"); DownloadFileResult result = obsClient.downloadFile(request); System.out.println("Download status: " + result.getStatus());

需要进度条时实现ProgressListener接口:

import com.obs.services.model.ProgressListener; import com.obs.services.model.ProgressStatus; request.setProgressListener(new ProgressListener() { @Override public void progressChanged(ProgressStatus status) { long bytes = status.getTransferredBytes(); long total = status.getTotalBytes(); System.out.printf("Progress: %.2f%%%n", bytes * 100.0 / total); } });

说明:ProgressStatus的getTransferredBytes()是累计已传输字节数,不是本次回调的增量,计算百分比时直接用。进度回调频率很高,每传输一部分数据就触发一次,不要在回调里做耗时操作比如写数据库,否则上传速度被拖慢。生产环境做进度展示时用 AtomicLong 记录最终值,由定时任务刷新界面。

4.3 生成临时授权 URL 给第三方下载

业务系统经常需要把私有桶里的文件分享给外部用户,直接暴露 AK/SK 是灾难,正确方式是生成带签名和过期时间的临时 URL:

import com.obs.services.model.CreateTemporarySignatureRequest; import com.obs.services.model.CreateTemporarySignatureResponse; import com.obs.services.model.HttpMethodEnum; import java.util.Date; CreateTemporarySignatureRequest request = new CreateTemporarySignatureRequest(); request.setBucketName("private-backup-bucket"); request.setObjectKey("reports/2025-01-01.pdf"); request.setMethod(HttpMethodEnum.GET); Date expires = new Date(System.currentTimeMillis() + 60 * 60 * 1000); // 1 小时有效 request.setExpires(expires); CreateTemporarySignatureResponse response = obsClient.createTemporarySignature(request); System.out.println("Signed URL: " + response.getSignedUrl());

参数说明:setExpires设置过期时间,最长 7 天。临时 URL 的签名和expires绑定,修改 URL 里的任何参数都会导致服务端验签失败。这个 URL 可以直接拼在<a>标签的href里,也可以重定向给浏览器。生成时要确认桶的访问权限,桶如果是私有读写,这个 URL 是唯一合法的访问凭证,泄露等于授予临时访问权,所以过期时间尽量短。还有一个细节:setMethod不仅支持GET,还能生成PUT和DELETE的临时 URL,可以用来做前端直传,但 PUT 直传的签名 URL 需要正确处理 Content-Type 头,否则上传后元数据不对,实际项目中我很少用它,前端直传通常用 POST 表单方式实现,SDK 也提供了对应接口。

5. esdk-obs-java 避坑指南:现象、原因、解决

5.1 上传大文件 OOM

  • 现象:用putObject上传 500 MB 以上文件时,应用内存飙升,最终OutOfMemoryError: Java heap space。
  • 原因:putObject的InputStream重载在内部把整个流读入内存做完整性校验和重试缓存,文件越大占的内存越多。
  • 解决:小文件用File重载,大文件改用uploadFile接口。如果必须用自定义流(比如从数据库 Blob 读出的数据),先落盘成临时文件再上传,或者用支持重放的流,否则 SDK 重试机制会出问题。落地路径是:超过 100 MB 的文件统一走uploadFile,这个阈值在配置中心做成参数,后续调整不需要发版。

5.2 连接池耗尽导致间歇性超时

  • 现象:服务运行几小时后,上传下载开始超时,日志报Could not get a resource from the pool,重启后恢复,过几小时又复发。
  • 原因:ObsObject.getObjectContent()返回的流没有关闭,连接没有归还到连接池,池里的连接被耗尽。常见于把流返回给前端后忘记在 finally 里 close。
  • 解决:所有从getObject拿到的流,必须在 finally 块或 try-with-resources 中关闭。排查线上连接泄漏时,可以打印连接池统计信息,SDK 提供obsClient.getConnectionManagerStatus()查看当前活跃连接数,配合 Java 进程的线程 dump,找到持有连接但没释放的代码位置。另外把连接池最大连接数从默认值调大也有帮助,但治标不治本。

5.3 endpoint 填错导致 301 跳转

  • 现象:上传文件时偶发成功偶发失败,错误码是 301PermanentRedirect,响应头里Location指向另一个 endpoint。
  • 原因:桶创建在 A 区域,但代码里的 endpoint 写成了 B 区域。OBS 的机制是哈希定位桶所在区域,发现不匹配时返回 301 并告知正确地址,SDK 会自动重试到正确区域,但每次都多一次网络往返,重试逻辑在高并发下还会放大延迟。
  • 解决:endpoint必须和桶所在区域严格一致。排查方法:调用obsClient.headBucket(bucketName)看响应头里的x-obs-bucket-location字段。这个字段直接告诉你桶在哪个区域,对照区域 endpoint 表格修改配置,不用瞎猜。

5.4 checkpoint 文件冲突导致文件内容错乱

  • 现象:断点续传任务完成后,从 OBS 下载的对象 MD5 和本地源文件不一致,上传时也没有报错。
  • 原因:多个不同文件共用了同一个 checkpoint 文件路径,或者同 key 上传新文件前没有清理旧的 checkpoint。SDK 加载 checkpoint 时认为断点是连续的,把旧的分段状态应用到了新文件上,拼接出损坏对象。
  • 解决:checkpoint 文件路径必须包含源文件的唯一标识,比如文件 MD5 或路径哈希。上传新文件前检查 checkpoint 是否存在,存在就先删除。同 key 覆盖上传时,先调deleteObject删除旧对象再上传,或者把版本控制打开,避免旧 checkpoint 影响新对象。

5.5 JDK 版本与依赖冲突

  • 现象:项目启动时报NoSuchMethodError: com.fasterxml.jackson.databind.ObjectMapper.getFactory()或ClassNotFoundException。
  • 原因:esdk-obs-java 传递依赖里带了一定版本的 Jackson 和 OkHttp,项目里其他框架(比如 Spring Boot 自带的 Jackson)版本不一致,Maven 仲裁选了错误版本。
  • 解决:在 pom.xml 里用dependencyManagement锁定 Jackson 版本,或者用exclusions排除 SDK 传递依赖,改用你项目里已验证的版本。典型做法是:
<dependency> <groupId>com.huaweicloud</groupId> <artifactId>esdk-obs-java</artifactId> <version>3.20.3</version> <exclusions> <exclusion> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> </exclusion> </exclusions> </dependency>

说明:排除 OkHttp 后要保证主项目里有兼容的 OkHttp 版本,SDK 的 HTTP 通信依赖它,版本差异太大可能触发序列化协议不兼容。最稳妥的做法是让 Maven 的依赖树可视化,mvn dependency:tree -Dincludes=com.huaweicloud能看出 SDK 到底拉进来哪些库,逐个对照项目现有版本排查。这类问题和 Java 基础的关系不大,更多是 Maven 依赖仲裁的边界知识,遇到别慌,先看依赖树再动手。

6. 验证与性能调参:把上传速度压到极致

6.1 连接池与超时参数的取舍

HTTP 客户端性能调参优先动三个参数:最大连接数、每个路由的最大连接数、超时时间。我验证过一组参数:最大连接 200,每个路由 50,Socket 超时 300 秒,连接超时 10 秒,在 8 核 16 GB 的机器上能稳定跑满 500 Mbps 上行带宽。设置方法:

import com.obs.services.ObsConfiguration; ObsConfiguration config = new ObsConfiguration(); config.setEndPoint(endpoint); // 注意这里不要 https:// 前缀 config.setMaxConnections(200); config.setMaxConnectionsPerRoute(50); config.setConnectionTimeout(10000); config.setSocketTimeout(300000); config.setIdleConnectionTime(30000); ObsClient obsClient = new ObsClient(ak, sk, config);

说明:setIdleConnectionTime是空闲连接回收时间,默认 30 秒,太短会导致连接频繁重建,TCP 握手开销增加;太长则空闲连接占用服务端资源。setSocketTimeout是读超时,大文件上传时单次读数据可能超过默认 60 秒,尤其在网络抖动时,这个值要放宽到 300 秒。连接数不是越大越好,超过网络带宽承受能力后,线程等待和上下文切换反而拖慢速度,测试时用top看 CPU 占用,如果多核打满说明并发太高。

6.2 用 obsutil 做对照验证

SDK 有没有调对,最直接的验证方式是跟官方命令行工具 obsutil 对比。obsutil 是独立的二进制工具,同样支持上传下载。先手动执行:

obsutil cp /data/backup/mysql-2025-01-01.sql.gz obs://my-backup-bucket/backup/

记录这个命令的耗时和吞吐,再用同样的参数跑 SDK 的uploadFile,对比看有没有明显差距。如果 obsutil 能跑到 80 MB/s 而 SDK 只有 20 MB/s,优先检查并发参数taskNum是否生效,以及是不是走了内网 DNS 解析导致连接耗时异常。obsutil 还有一个参数-u可以显示上传进度和速率,拿它当性能基准很直观。验证完成后,检查 OBS 控制台里对象的 ETag 和大小与本地源文件是否完全一致,这是最终正确性的判定标准。我习惯在每次改完网络参数后,用 1 GB 的测试文件跑一遍完整上传下载流程,记录耗时和 ETag,形成一个基线表,后续参数调整有据可查。如果没有做这层验证,直接把参数上线,出了性能问题很难区分是代码问题还是网络问题。

这个 SDK 我自己从 3.19 用到 3.20 系列,最大的教训是升级版本后一定要跑一遍全链路回归,尤其是断点续传和临时签名 URL 这两个功能。有一次升了小版本,签名 URL 默认的签名版本变了,前端下载全部 403,排查半天才发现是 SDK 内部默认签名算法切换。现在我的做法是:新版本先在测试环境跑一周,每天定时任务上传下载比对数据完整性,确认没问题再发生产。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询