入到斷點續(xù)傳實踐)
簡介面向 Java 開發(fā)者的華為云 OBS 對象存儲服務(wù) SDK 資源包版本為 3.20.3可幫助快速實現(xiàn)文件上傳、下載、桶管理及對象生命周期管理等云存儲操作也適合作為企業(yè)項目二次開發(fā)的基線工具。壓縮包共 766 個文件整體僅 7.31MB其中包含 449 個 HTML 格式的 API 文檔頁面、286 個 Java 示例與源碼文件、10 個 XML 工程及日志配置、9 個 JAR 依賴庫另有 LICENSE、MD 說明等配套文件目錄結(jié)構(gòu)清晰便于按需查閱。資源內(nèi)置完整 SDK 庫、Maven 依賴描述、可直接運行的示例代碼以及官方 javadoc 文檔開發(fā)者可據(jù)此搭建 Maven 工程理解核心客戶端接口的調(diào)用方式并參考示例完成上傳、下載、列舉桶、刪除對象等典型操作日志配置還支持靈活調(diào)整輸出級別便于聯(lián)調(diào)階段問題定位。已有 1833 人學(xué)習(xí)下載適合剛接觸華為云 OBS 的入門者以及需要深度集成 OBS 服務(wù)的后端工程師參考使用。1. 華為 OBS SDK 3.20.3Java 接入對象存儲時的離線首選包上個月給一個內(nèi)網(wǎng)客戶做數(shù)據(jù)遷移安全策略禁止開發(fā)機訪問 Maven 中央倉庫所有第三方 jar 要人工審核后進內(nèi)部制品庫。我把申請遞上去時順手下載了官方 esdk-obs-java-3.20.3.zip解壓后主包、依賴、源碼和中文文檔齊了離線導(dǎo)入一次通過。這個 zip 就是華為 OBS SDK 的 Java 發(fā)行包3.20.3 版本提供 ObsClient 全套能力覆蓋桶操作、上傳下載、斷點續(xù)傳和管理策略。對于不能實時拉取依賴的維護項目手工導(dǎo)入 zip 反而比 Maven 更可控適合內(nèi)網(wǎng)部署、遺留系統(tǒng)升級、需要審計依賴的 Java 工程師新手按包內(nèi)示例也能跑通第一行上傳代碼。2. 解包 esdk-obs-java-3.20.3jar 清單、依賴取舍與本地倉庫導(dǎo)入2.1 拆開 zip 先別急著全量引入先認目錄拿到 zip 后很多人直接把里面所有 jar 拖進項目 libs結(jié)果啟動時 log4j 或者 okhttp 沖突。我拆過幾個 esdk-obs-java 發(fā)行包目錄結(jié)構(gòu)大同小異核心是 esdk-obs-java-3.20.3.jar運行期依賴在 lib 或 third_party 目錄下另外會附 source 包、README、API 文檔和依賴清單。下面這張表是我一般會先核對的幾項。文件/目錄內(nèi)容離線審核時看什么esdk-obs-java-3.20.3.jar主庫ObsClient、PutObjectRequest 等核心類都在里面確認版本號與文檔一致lib/okhttp-.jar、gson-.jarHTTP 和 JSON 依賴看版本是否和項目現(xiàn)有 jar 沖突esdk-obs-java-3.20.3-source.jar源碼包排查問題時反查實現(xiàn)doc/中文 API 手冊與示例先查支持的 Operationsdependency-list.txt依賴清單人工審計時逐項打勾先讀依賴清單再決定引哪些 jar。OBS SDK 3.x 的 HTTP 層基于 OkHttp序列化用了 Gson日志用 Log4j2這三類是沖突高發(fā)區(qū)。如果你項目里已經(jīng)有 okhttp 3.14 以上版本不要直接覆蓋先看 SDK 內(nèi)置版本與你的代碼有沒有調(diào)用差異。提示離線審核時把 dependency-list.txt 和 source jar 一起歸檔后續(xù)等保檢查或制品溯源都要用。2.2 從 libs 走向內(nèi)部 Maven兩種導(dǎo)入方式對比第一種方式IDE 項目直接把 jar 放入 libs 目錄并加入 module 依賴。速度快但每個項目都要重復(fù)手工步驟構(gòu)建產(chǎn)物也不統(tǒng)一我一般只用來做本地驗證。第二種方式更可靠把 SDK 主包和第三方依賴統(tǒng)一安裝到內(nèi)部 Maven 倉庫pom 里只寫一個坐標(biāo)。# 安裝 SDK 主包 mvn install:install-file \ -Dfileesdk-obs-java-3.20.3.jar \ -DgroupIdcom.huaweicloud \ -DartifactIdesdk-obs-java \ -Dversion3.20.3 \ -Dpackagingjar # 如果有源碼包需要一起發(fā)布再加 -Dclassifiersources mvn install:install-file \ -Dfileesdk-obs-java-3.20.3-source.jar \ -DgroupIdcom.huaweicloud \ -DartifactIdesdk-obs-java \ -Dversion3.20.3 \ -Dpackagingjar \ -Dclassifiersourcesinstall-file會安裝一個 jar相當(dāng)于把本地文件“發(fā)布”到本地倉庫。-DgroupId/-DartifactId/-Dversion必須和后續(xù) pom 坐標(biāo)一致否則構(gòu)建時找不到。我不建議把版本改寫成 3.20.3-RELEASE 這類自定義版本問題定位時對不上原始包會很難受。之后項目 pom 里聲明依賴dependency groupIdcom.huaweicloud/groupId artifactIdesdk-obs-java/artifactId version3.20.3/version /dependency如果內(nèi)部 Maven 倉庫還沒有 okhttp、gson 這些第三方依賴同樣需要先把它們安裝進倉庫否則 Maven 構(gòu)建時依舊解析失敗。此時可以執(zhí)行mvn dependency:tree看 SDK 的傳遞依賴再決定逐個 install 還是直接用項目內(nèi)已有版本覆蓋。2.3 依賴沖突高發(fā)點OkHttp 與 Log4j2 的取舍我在給老 Spring Boot 項目接 OBS SDK 時遇到最多的是NoSuchMethodError: okhttp3.RequestBody.create。原因很簡單系統(tǒng)里已有舊版 okhttpOBS SDK 編譯時用了新版的靜態(tài)方法運行時加載了老 class直接炸。解決方式是先從依賴?yán)锱懦?SDK 自帶的 okhttp統(tǒng)一走項目里的版本。dependency groupIdcom.huaweicloud/groupId artifactIdesdk-obs-java/artifactId version3.20.3/version exclusions exclusion groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId /exclusion /exclusions /dependency排除前要確認項目現(xiàn)有 okhttp 版本不被 SDK 調(diào)用的新 API 影響否則會出現(xiàn)暗坑。Log4j2 沖突也一樣如果項目已經(jīng)有 log4j2需要檢查版本是否高于 SDK 要求實在無法統(tǒng)一時可以在部署層做 shiro/日志橋接但那是最后手段。更穩(wěn)妥的做法是先跑一遍mvn dependency:tree -Dincludescom.squareup.okhttp3看清依賴來源再決定排除哪一端。除了 Maven老 Web 項目常直接拷貝 jar 到WEB-INF/lib。這時我建議只拷貝 dependency-list.txt 里標(biāo)為 compile 的 jar并按主包在前、第三方在后的順序加入。主包是否完整可以用jar tf快速驗證jar tf esdk-obs-java-3.20.3.jar | grep ObsClient能看到com/obs/services/ObsClient.class基本沒問題。這一步能攔住絕大多數(shù)“jar 包損壞”或“下載不完整”的翻車。3. 初始化 ObsClientAK/SK、Endpoint 與連接參數(shù)的一次配好3.1 創(chuàng)建 ObsClient最小可用代碼與線程安全說明接入 OBS 的第一步是拿到ObsClient。最小代碼如下import com.obs.services.ObsClient; public class ObsConnection { public static void main(String[] args) throws Exception { String endpoint https://obs.cn-north-4.myhuaweicloud.com; String ak System.getenv(OBS_AK); String sk System.getenv(OBS_SK); ObsClient client new ObsClient(ak, sk, endpoint); System.out.println(client.listBuckets()); client.close(); } }ObsClient封裝了 HTTP 調(diào)用、簽名計算、連接池和重試。構(gòu)造函數(shù)只做參數(shù)初始化不會立刻聯(lián)網(wǎng)上方代碼里的listBuckets()會真實調(diào)用一次 OBS 服務(wù)用來驗證 AK/SK 是否有效。ObsClient 是線程安全的應(yīng)用啟動時創(chuàng)建一個全局單例即可不要每次上傳都 new。每 new 一個都會占住一個連接池和對應(yīng)線程資源調(diào)用頻率高時很容易把 JVM 的句柄數(shù)跑上去。如果想控制超時、連接池大小和重試次數(shù)要先用ObsConfigurationObsConfiguration config new ObsConfiguration(); config.setEndPoint(https://obs.cn-north-4.myhuaweicloud.com); config.setConnectionTimeout(10000); config.setSocketTimeout(30000); config.setMaxConnections(500); config.setMaxErrorRetry(3); ObsClient client new ObsClient(ak, sk, config);這里注意方法名是setEndPoint別寫成setEndpointIDE 提示時容易看走眼。connectionTimeout是建立 TCP 連接的超時socketTimeout是每次讀響應(yīng)的超時maxConnections是連接池上限maxErrorRetry是網(wǎng)絡(luò)層錯誤的重試次數(shù)。重試次數(shù)不是越大越好接口冪等性差時重試會放大臟數(shù)據(jù)風(fēng)險。3.2 AK/SK、Endpoint 與 Region 的關(guān)系Endpoint 決定了你的請求發(fā)到哪個區(qū)域的 OBS 服務(wù)端。常見區(qū)域?qū)?yīng)關(guān)系如下區(qū)域Endpoint華北-北京四obs.cn-north-4.myhuaweicloud.com華南-廣州obs.cn-south-1.myhuaweicloud.com華東-上海一obs.cn-east-3.myhuaweicloud.comEndpoint 不要帶桶名也不要帶端口號。錯誤寫法是https://my-bucket.obs.cn-north-4.myhuaweicloud.com這樣簽名時 Host 會多一層服務(wù)端解析后大概率報 301 或 NoSuchBucket。如果 VPC 內(nèi)網(wǎng)訪問應(yīng)該用對應(yīng)區(qū)域的內(nèi)網(wǎng)域名AK/SK 不需要變化。另外桶是有區(qū)域?qū)傩缘?。用北京四?Endpoint 去訪問華南廣州的桶請求會被重定向或直接拒絕。我應(yīng)該把區(qū)域配置放到統(tǒng)一配置中心而不是寫死在代碼里??鐓^(qū)域訪問場景還要考慮流量費用Endpoint 只是技術(shù)入口不代表業(yè)務(wù)上可以隨意跨區(qū)拉文件。3.3 AK/SK 不要寫死在代碼里環(huán)境變量與臨時憑證最常見的錯誤是把 AK/SK 直接粘貼在代碼里然后代碼被傳到 Git 倉庫。我習(xí)慣先從環(huán)境變量讀取String ak System.getenv(OBS_AK); String sk System.getenv(OBS_SK); if (ak null || sk null) { throw new IllegalStateException(missing OBS_AK or OBS_SK in env); }在本地開發(fā)時可以把密鑰放在~/.obs/credentials這類外部文件生產(chǎn)環(huán)境在華為云 CCE/ECS 上運行時優(yōu)先使用委托方式獲取臨時憑證這樣即使容器被攻破拿到的也是短期憑證而不是永久 AK/SK。臨時憑證方案一般涉及華為云 IAM 的 STS 服務(wù)SDK 里對應(yīng)ObsClient的 credentials provider 參數(shù)。換憑證時不需要重建 ObsClient只需要確認新憑證有對應(yīng)的 obs:Object:* 權(quán)限。如果使用獨立的配置中心讀取后也要保證進程內(nèi)存中只有一份不要反復(fù)讀取。密鑰明文落盤的風(fēng)險比代碼硬編碼更難發(fā)現(xiàn)尤其是配置文件被鏡像打包時很容易一并在鏡像里泄露出去。4. 上傳下載實戰(zhàn)PutObject、GetObject 與斷點續(xù)傳的參數(shù)選擇4.1 簡單上傳與下載流式處理必須記住關(guān)閉響應(yīng)體普通小文件直接使用PutObjectRequest最省事。以字符串上傳為例PutObjectRequest putRequest new PutObjectRequest( my-bucket, logs/app-2025-01.log, 2025-01-01 00:00:00); obsClient.putObject(putRequest);這個構(gòu)造函數(shù)里填的是桶名、對象名、字符串內(nèi)容。對象名即 keylogs/只是模擬目錄層級OBS 并沒有真實目錄概念。如果上傳本地文件代碼變成File file new File(app-2025-01.log); PutObjectRequest putRequest new PutObjectRequest(my-bucket, logs/ file.getName(), file); ObjectMetadata metadata new ObjectMetadata(); metadata.setContentType(text/plain); metadata.setContentLength(file.length()); putRequest.setMetadata(metadata); obsClient.putObject(putRequest);metadata.setContentType顯式聲明文件類型避免瀏覽器下載.log文件時被當(dāng)成純文本展示。setContentLength一般會自動計算手動設(shè)置時要注意和文件實際長度一致否則可能出現(xiàn)請求 body 和服務(wù)端長度對不上。下載時服務(wù)端返回的是ObsObject真正的數(shù)據(jù)在getObjectContent()流里GetObjectRequest getRequest new GetObjectRequest(my-bucket, logs/app-2025-01.log); ObsObject object obsClient.getObject(getRequest); try (InputStream in object.getObjectContent()) { Files.copy(in, Paths.get(downloads/app-2025-01.log), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { throw new RuntimeException(download failed, e); }getObject不會把整個文件讀進內(nèi)存而是返回一個可讀流。這個流必須被讀取并關(guān)閉否則 TCP 連接無法回到連接池。程序跑幾天后連接數(shù)和文件句柄會肉眼可見地增長。這里用 try-with-resources 是最穩(wěn)妥的關(guān)閉方式不要在 finally 里再寫一遍close()容易重復(fù)關(guān)閉。4.2 大文件與弱網(wǎng)uploadFile 和 downloadFile 斷點續(xù)傳超過 100MB 的文件不建議用putObject(File)。這個接口適合小對象網(wǎng)絡(luò)抖動一次就要重來。OBS Java SDK 提供了專門的大文件接口示例代碼UploadFileRequest request new UploadFileRequest( my-bucket, archives/backup.zip, D:/data/backup.zip); request.setTaskNum(5); request.setPartSize(10 * 1024 * 1024); request.setEnableCheckpoint(true); obsClient.uploadFile(request);對應(yīng)的下載DownloadFileRequest request new DownloadFileRequest( my-bucket, archives/backup.zip, D:/data/download/backup.zip); request.setTaskNum(8); request.setEnableCheckpoint(true); request.setPartSize(10 * 1024 * 1024); obsClient.downloadFile(request);taskNum是并發(fā)分片數(shù)partSize是單個分片大小。兩者共同決定內(nèi)存占用和并發(fā)壓力。我一般把partSize設(shè)在 5~10MBtaskNum不超過 10弱網(wǎng)環(huán)境下 taskNum 太大反而加劇超時因為每個分片都在搶帶寬。enableCheckpoint開啟后會在本地生成分片進度記錄中斷后再次調(diào)用會從斷點繼續(xù)。但要注意如果業(yè)務(wù)文件內(nèi)容變了必須清掉舊 checkpoint 記錄否則可能把不同版本的分片拼接在一起得到臟數(shù)據(jù)。4.3 怎么選上傳方式簡單上傳、流式上傳、斷點續(xù)傳的邊界方式適用場景注意點putObject(File)小于 100MB 的普通文件簡單直接不能斷點續(xù)傳putObject(InputStream)請求流、管道數(shù)據(jù)需要自己處理異常關(guān)閉uploadFile大文件、弱網(wǎng)、需要重試分片數(shù)不能超過 10000partSize 影響并發(fā)追加上傳 AppendObject日志順序追加不支持并發(fā)追加追加次數(shù)有限制選擇依據(jù)主要看文件大小和網(wǎng)絡(luò)穩(wěn)定性。小文件在弱網(wǎng)環(huán)境下直接重試幾次成本也不高大文件必須用uploadFile它內(nèi)部會做分片并發(fā)和進度持久化。如果你在代碼里給多個線程分別調(diào)putObject(File)上傳同一個大文件那不是斷點續(xù)傳只會多占內(nèi)存和帶寬。另外uploadFile底層走的是 OBS 的 multipart upload對同一對象發(fā)起的分片上傳任務(wù)數(shù)量有限制。如果長時間只傳不合并或者頻繁取消服務(wù)端會積累未完成的分片產(chǎn)生額外存儲費用。生產(chǎn)環(huán)境要定期清理未完成的分片任務(wù)或者監(jiān)控桶里的 multipart 數(shù)量。5. 避坑與常見問題排查OBS Java SDK 最常翻車的五個細節(jié)5.1 桶名帶上了 Endpoint301/404 反復(fù)出現(xiàn)現(xiàn)象請求返回 301或者桶明明存在卻報 NoSuchBucket。原因Endpoint 寫成了“桶名 域名”。SDK 的 endpoint 只接受區(qū)域域名桶名是請求參數(shù)里的獨立項把桶名拼進去后 Host 頭就多了一層簽名驗證自然失敗。解決Endpoint 統(tǒng)一填https://obs.cn-north-4.myhuaweicloud.com桶名只寫在PutObjectRequest/GetObjectRequest的構(gòu)造參數(shù)里。CDN 或自定義域名場景需要單獨配置 path style不應(yīng)該用普通 endpoint 硬拼。5.2 AK/SK 正確但還是 AccessDenied現(xiàn)象本地用同樣 AK/SK 能訪問部署到客戶環(huán)境后總是 AccessDenied。原因IAM 用戶權(quán)限不夠、臨時憑證作用域不對或者客戶端系統(tǒng)時間與服務(wù)器偏差過大導(dǎo)致簽名過期。也可能桶策略限制了來源 IP 或指定了請求頭。解決先檢查 IAM 策略是否包含obs:Object:PutObject、obs:Object:GetObject。如果用的是臨時憑證確認角色策略是否完整。然后看服務(wù)器系統(tǒng)時間偏差超過 15 分鐘就先做時間同步。OBS 簽名用的是 UTC 時間不會自動跟隨本地時區(qū)時間偏移是最隱蔽的 AccessDenied 原因。5.3 大文件上傳內(nèi)存爆掉現(xiàn)象用putObject(new File(...))上傳 500MB 文件JVM 內(nèi)存飆升并出現(xiàn) OOM加-Xmx4g也沒用。原因簡單上傳接口在構(gòu)造請求時可能會額外加載整個文件或較大的緩沖區(qū)域同時大文件本身也增加了 IO 壓力堆內(nèi)存很快被占滿。解決改用UploadFileRequest分片上傳partSize控制在 5~10MBtaskNum控制在 2~8。同時在ObsConfiguration里調(diào)整maxConnections避免每個分片線程都持有大緩沖。如果業(yè)務(wù)上必須用簡單上傳就用InputStream并顯式控制讀緩沖不要直接把整個 File 交給 SDK。5.4 代理環(huán)境下連接池被占滿現(xiàn)象內(nèi)網(wǎng)請求走 HTTP 代理剛開始正常一段時間后所有請求 SocketTimeout。原因ObsClient 默認使用 okhttp 連接池maxConnections 默認值不夠大。代理環(huán)境下每次請求都要額外握手長連接失效后大量建連互相爭搶最終連接池被占滿。解決在ObsConfiguration中顯式配置代理和連接池參數(shù)config.setProxyHost(proxy.internal); config.setProxyPort(8080); config.setMaxConnections(200);maxConnections 建議按業(yè)務(wù)峰值的 1.2 倍左右設(shè)置。同時觀察 okhttp 的 eviction 日志確認代理本身沒有并發(fā)連接數(shù)限制。如果代理還做了 TLS 攔截還需要把相應(yīng)證書導(dǎo)入到 JVM 的 trustStore否則會報 SSLHandshakeException。5.5 依賴沖突導(dǎo)致 NoSuchMethodError現(xiàn)象項目啟動后調(diào)用 OBS SDK 報NoSuchMethodError: okhttp3.RequestBody.create(...)。原因項目里另一個依賴帶了舊版 okhttp把 OBS SDK 依賴的 okhttp 版本覆蓋了運行時加載的類缺少新版方法。解決在 pom 里排除 OBS SDK 自帶的 okhttp統(tǒng)一用項目內(nèi)較新的版本如果項目內(nèi)版本過舊就反過來升級項目依賴。查看依賴樹是關(guān)鍵mvn dependency:tree -Dincludescom.squareup.okhttp3看到重復(fù)的 okhttp 版本后再決定排除哪一邊。日志沖突同理優(yōu)先保證 log4j2 高版本被加載否則 SDK 的日志輸出會靜默失敗排查問題時看不到任何請求信息。6. 進階用法并發(fā)上傳與日志驗證讓 SDK 吞吐量真正跑起來6.1 線程池并發(fā)上傳配合連接池和日志驗證ObsClient 是線程安全的所以并發(fā)上傳小文件時可以直接用固定線程池。常見做法是ExecutorService pool Executors.newFixedThreadPool(8); ListPath files ...; for (Path path : files) { pool.submit(() - { obsClient.putObject(my-bucket, path.getFileName().toString(), path.toFile()); }); } pool.shutdown(); pool.awaitTermination(30, TimeUnit.MINUTES);線程數(shù)不要盲目往上漲。ObsConfiguration 里的maxConnections要跟著調(diào)大否則所有線程都在等連接池整體吞吐反而下降。對于大文件UploadFileRequest內(nèi)部已經(jīng)做了分片并發(fā)不要再在外面套一層多線程去并發(fā)刷同一個文件分片競爭會拖慢速度。上線前我習(xí)慣把日志調(diào)出來看一眼OBS SDK 內(nèi)部使用 log4j2logger 名稱一般是com.obs.services。把這一級調(diào)到 DEBUG可以看到簽名的 Host、請求路徑和響應(yīng)狀態(tài)碼。確認請求路徑里沒有多余的桶名再放開并發(fā)壓測。驗證上傳是否成功不一定要下載回來。可以讀對象元數(shù)據(jù)做一致性判斷ObjectMetadata meta obsClient.getObjectMetadata(my-bucket, logs/app.log); System.out.println(meta.getContentLength());把元數(shù)據(jù)里的 contentLength 和本地文件大小比對能快速發(fā)現(xiàn)未傳完或傳錯對象的問題。再用v2: obs://bucket/key這類方式做一次下載校驗比較穩(wěn)妥。之前我為了追求并發(fā)把線程池開到 32連接池沒調(diào)壓測到第 20 分鐘就開始大片超時后來把 maxConnections 和線程數(shù)一起控制在 16實測吞吐才穩(wěn)定下來。從那以后我每次接 OBS SDK 都強制做一遍 20 分鐘并發(fā)壓測確認 CPU、句柄和超時三個指標(biāo)沒有異常才敢上線。希望幫到你。本文還有配套的精品資源點擊獲取