华为云OBS使用踩坑记:从崩溃到稳定的实战之路
搞对象存储这事儿,我一开始是真没当回事。
去年团队要上一套新的日志归档系统,数据量不算夸张但增长很猛,每天几个TB的冷数据需要长期留存。技术选型的时候,大家几乎没怎么争论就定了华为云OBS——毕竟在对象存储这个品类里,它算是国内梯队里非常能打的选手,价格合理、生态完善、API兼容性也不错。结果谁能想到,光是一个“把文件传上去”的简单需求,就让我在两周时间里反复崩溃,翻了无数次文档,踩了一堆文档里根本不会写的坑。
今天这篇不聊官方宣传稿里的漂亮话,就聊聊我从“OBS用得想摔键盘”到“OBS稳定跑了大半年没出过问题”这中间走过的弯路和最终沉淀下来的方案。如果你是刚接触对象存储,或者正被各种上传失败、权限报错、性能瓶颈折磨,这篇应该能帮你省下不少时间。
先说清楚,这篇文章围绕的核心关键词就是华为云OBS。它的适用范围很广——静态网站托管、数据备份归档、大数据分析的数据湖底座、图片视频存储分发,核心逻辑都是同一套。我会重点讲实战过程中反复踩到的几个大坑:权限配置、SDK使用、性能调优、异常处理,每个问题都会给出完整的排查思路和最终解决方案,而不是只给一句“请参考官方文档”这种废话。
适合谁来读呢?第一类是后端开发工程师,尤其是负责存储、备份、数据处理相关模块的朋友;第二类是运维和架构师,做容量规划和技术选型时可以避坑;第三类是刚上手对象存储的初学者,我把很多基础概念也用白话解释了一遍,不会让你看得一头雾水。
- 内容整体设计与思路拆解
先说说我最初的设计方案,这样可以更清楚后面每个坑是怎么来的。
当时的需求很简单:业务服务器每天会产生大量日志文件,我需要写一个定时任务,把本地文件上传到OBS,并且在OBS端按照日期和日志类型分目录存放。考虑到文件数量多、单个文件大小不大(几百KB到几十MB不等),我没有走单个文件逐一上传的笨办法,而是设计了一个批量归档的流程:
- 本地先按小时粒度聚合日志,压缩成tar包;
- 定时任务扫描压缩包目录,调用OBS SDK上传;
- 上传成功后删除本地文件,释放磁盘空间;
- 如果上传失败则保留本地文件,等待下一轮重试。
这个流程看起来没有任何问题,对吧?实际上它把所有OBS使用中的经典陷阱全部踩中了:权限策略配置错误、SDK配置不当、并发参数设置不合理、超大对象(其实也不大)上传超时、本地文件误删等等。每一个问题都让我的定时任务在凌晨三点准时崩溃,然后第二天早上被同事拉去“友好交流”。
1.1 为什么选择华为云OBS而不是自建存储服务
我记得在设计评审的时候,有同事提议过自建MinIO或者Ceph。这想法本身没错,尤其是如果你们公司已经有成熟的Kubernetes集群,运维能力又强,自建确实是成本更低的选择。但我们最终选OBS的原因有几个,我列出来你们感受一下,这些也是对象存储相对自建的通用优势:
- 不需要操心容量规划。自建存储你得预估未来一年的增长量,买机器、扩磁盘、做副本策略,等真的暴增了还得熬夜扩容。OBS这边的逻辑就简单很多,你只管往上丢数据,容量几乎无限。
- 数据持久性有保障。华为云官方给出的设计持久性是99.999999999%(11个9),意味着丢数据的概率极低。自建方案要达到这个级别,至少得三副本加定期校验,光运维成本就够呛。
- 生态完善。OBS和华为云的CDN、数据处理服务、大数据组件都有原生集成,后续如果要做图片处理或者数据分析,不需要额外开发。
选择OBS本质上是一个“拿钱换时间、拿依赖换稳定性”的决策。对于大多数中小团队来说,这比自建要理性得多。
1.2 整体架构怎么搭才不会走弯路
过了选型这关后,我画的架构图非常朴素:
业务服务器产生日志 -> 定时压缩 -> OBS SDK上传 -> OBS桶
就这么简单。但正因为简单,让人容易掉以轻心。我后来才意识到,一个“看似简单”的云服务接入,需要考虑的东西远不止“调个API把文件发上去”这么简单。至少要覆盖下面这几层:
- 认证鉴权:用什么方式访问OBS?是长期密钥AK/SK,还是临时凭证?权限范围怎么控制?
- 网络链路:业务服务器和OBS之间的网络是否打通?是否需要配置内网域名?有没有代理或防火墙拦截?
- SDK配置:超时时间、重试策略、并发连接数、断点续传,这些参数默认值真的适合你的场景吗?
- 异常处理:上传失败、网络抖动、服务端限流,你的代码能不能优雅地处理这些情况而不导致数据丢失?
- 监控告警:上传成功率、延迟、失败原因分布,这些指标你有没有可视化地监控起来?
这些问题我当时一个都没想清楚就直接开干,结果就是上线后两天内收到十几条告警短信,每一条都在提醒我:你的定时任务又失败了。
- 核心细节解析与实操要点
这块我打算把几个核心环节拆开讲。每一个都是我实际踩过坑、最后确定下来的用法,按照官方文档的逻辑是推不出来的。
2.1 桶的权限策略:私有桶是安全底线
我一开始创建桶的时候,为了方便测试,直接把桶权限设置成了公共读。结果就是上传倒是没任何问题,但没过多久就被人刷流量,账单直接给我上了一课。
公共读桶意味着任何知道URL的人都可以读取桶内对象,如果你的对象名是日期加日志类型的组合模式,比如log/2025-06-01/app.tar.gz,那别人完全可以遍历下载。虽然日志本身不一定是机密,但被薅流量造成的经济损失是实实在在的。
这里给出一个明确的方案:除非是做静态网站托管且明确需要公网访问,否则一律使用私有桶,通过临时签名URL或IAM授权的方式控制访问。
创建私有桶的方式很简单,控制台创建的时候选择“私有”权限即可。如果已经创建了公共读桶,可以在桶的权限策略里修改。但我建议直接删掉重建更干净,因为桶策略和对象策略的覆盖关系很容易让人产生误解。
2.2 访问控制:IAM用户与临时凭证
说完桶权限,再说访问控制。我一开始图省事,直接在代码里硬编码了账号的AK/SK。这是最危险的做法,因为账号AK/SK拥有账号下的全部权限,一旦泄露,整个账号的资源都暴露了。
正确的做法是创建一个IAM子用户,只授予这个子用户访问指定OBS桶的权限。比如你的业务归档任务只需要往obs-bucket-a上传和删除对象,那就给这个子用户挂一个自定义策略,只允许操作obs-bucket-a这个桶。
这里我给一个最小权限策略示例,你们可以参考:
{ "Version": "1.0", "Statement": [ { "Effect": "Allow", "Action": [ "obs:PutObject", "obs:DeleteObject" ], "Resource": [ "obs:*:*:*:bucket:your-bucket-name/*" ] } ] }Action里只列了上传和删除两个动作,Resource限定到了具体的桶下的所有对象。这样即使AK/SK泄露,影响范围也仅限于这个桶,不至于被拖到整个账号。
如果安全性要求更高,推荐使用临时凭证(通过SecurityTokenService获取),有效期内自动过期,不需要在代码里管理长期密钥。不过临时凭证的获取本身也需要长期密钥去换,所以代码里还是得有一个安全的密钥管理方案,比如用环境变量、KMS加密存储,而不是直接写死在配置文件里。
2.3 SDK选择与初始化参数
华为云OBS的官方SDK覆盖了Java、Python、Go、Node.js、.NET等主流语言,我在生产环境用的是Java版本,本地测试时用Python比较多。
SDK的初始化有几个参数非常关键,不是用默认值就行的:
// 设置Endpoint和认证信息 String endPoint = "https://obs.cn-north-4.myhuaweicloud.com"; String ak = System.getenv("OBS_AK"); String sk = System.getenv("OBS_SK"); // 创建ObsClient实例 ObsClient obsClient = ObsClientBuilder.custom() .endPoint(endPoint) .credentialProvider(new DefaultCredentialProvider(ak, sk)) .connectionTimeout(30000) // 连接超时 .socketTimeout(30000) // Socket超时 .maxConnections(100) // 最大连接数 .build();connectionTimeout和socketTimeout是我第一个踩坑的地方。
OBS默认超时时间比较短,如果你上传的文件比较大,或者网络链路质量一般,很容易出现SocketTimeoutException。一开始我没设置超时时间,结果大文件上传时频繁报错,任务重试后又给服务端造成额外压力。
我的建议是:
- 连接超时:建议10秒到30秒之间,太短遇到网络波动就容易失败;
- Socket超时:根据你的文件大小和带宽估算,一般来说30秒起步,大文件建议60秒以上;
- 最大连接数:默认值通常偏低,如果你的服务是高频读写场景,建议调到200以上,但也要注意线程池的配合。
2.4 断点续传:大文件上传的保命符
日志压缩包虽然单个不算大,但偶尔也会遇到单个文件超过1GB的情况。这时候用普通的PutObject接口非常不稳,网络一旦抖动,整个文件就要重新传。
好用的方案是使用断点续传上传接口,OBS的SDK里已经封装好了,只需要调一个方法就可以:
// 断点续传上传 UploadFileRequest request = new UploadFileRequest("your-bucket-name", "path/to/object", "localFile.tar.gz"); request.setTaskNum(5); // 分段并发数 request.setPartSize(10 * 1024 * 1024); // 每段10MB obsClient.uploadFile(request);分段上传的精髓是:大文件被切成多个小段,每段独立上传,全部完成后服务端合并。即使某一段上传失败,只需要重传失败的那一段,不需要从头再来。断点续传会把上传进度记录在本地,进程重启后还能从上次的进度继续。
taskNum和partSize的选择是有讲究的,这两个参数共同决定了上传的并发度和吞吐量。我的经验是,partSize设置为10MB到20MB之间比较合适,太小会导致请求次数过多,太大则分段上传的优势不明显。taskNum可以设置为5到10之间,太大容易触发服务端限流。
这里还要注意一个点:断点续传需要一个本地磁盘目录来存放进度文件,如果你在容器里运行,务必要给这个目录挂载持久化存储。不然Pod一重启,断点进度就丢了,等于没续传。
- 实操过程与核心环节实现
接下来我完整走一遍实操流程,从创建桶开始,到一个稳定的归档任务跑起来。整个过程我尽量把每一步的原因和可能遇到的问题都交代清楚。
3.1 创建桶与配置IAM权限
登录华为云控制台,进入OBS服务页面,点击“创建桶”。这一步有几个选项需要特别注意:
- 区域:选择和你业务服务器相同的区域。跨区域访问会增加延迟和流量费用,比如业务在华北区,桶就选华北区。
- 桶名称:全局唯一,命名要有业务语义和分区逻辑,比如your-company-log-archive。后续通过API访问时,桶名会体现在URL里,所以不要起一些毫无意义的名字。
- 存储类别:日志归档这类低频访问数据,选择“低频访问存储”或者“归档存储”可以显著降低成本。但如果你的业务会频繁读取,标准存储更合适。我这边因为日志基本不会回看,选了低频访问存储。
- 策略:选择私有。
创建完成后,进入“权限管理 -> IAM项目”,创建一个IAM子用户,下载AK/SK,然后给这个子用户绑定我们前面定义的策略。这里有一个小技巧:如果你有不止一个业务都需要访问OBS,建议为每个业务创建独立的IAM用户和独立的桶。这样后续做审计和排障的时候,可以很清楚地看到哪个用户操作了哪个桶,不会把多个业务混在一起。
3.2 本地日志压缩与归档脚本
桶准备好了,接下来写本地的上传脚本。我先用Python写了一个最基础的版本,目的是验证全流程能跑通,踩坑之后再逐步加功能。
import os import tarfile from datetime import datetime, timedelta from obs import ObsClient # 初始化ObsClient obs_client = ObsClient( access_key_id=os.getenv("OBS_AK"), secret_access_key=os.getenv("OBS_SK"), server="https://obs.cn-north-4.myhuaweicloud.com" ) def compress_logs(date_str, source_dir, output_dir): """把指定日期的日志压缩为tar包""" output_file = os.path.join(output_dir, f"logs-{date_str}.tar.gz") with tarfile.open(output_file, "w:gz") as tar: for root, dirs, files in os.walk(source_dir): for file in files: # 只压缩指定日期的日志文件 if date_str in file or date_str in root: file_path = os.path.join(root, file) tar.add(file_path, arcname=os.path.relpath(file_path, source_dir)) return output_file def upload_to_obs(local_file, bucket_name, object_key): """上传文件到OBS""" resp = obs_client.putFile(bucketName=bucket_name, objectKey=object_key, filePath=local_file) if resp.status < 300: print(f"上传成功: {object_key}") return True else: print(f"上传失败: {resp.status}, {resp.errorCode}, {resp.errorMessage}") return False if __name__ == "__main__": # 归档昨天的日志 yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") local_tar = compress_logs(yesterday, "/var/log/myapp", "/tmp/archive") object_key = f"logs/{yesterday}/app-logs.tar.gz" success = upload_to_obs(local_tar, "your-company-log-archive", object_key) if success: os.remove(local_tar) print("本地临时文件已清理") else: print("上传失败,保留本地文件等待下次重试")这个脚本的核心逻辑很简单,但里面有几个隐藏问题我稍后会说。先说它好用的地方:压缩和上传分离,失败时不会删本地文件,下次执行会自动重试(因为脚本只处理指定日期的日志,如果上次失败了,这次会重新压缩上传,不用担心漏数据)。
3.3 对象命名规范与生命周期管理
上传之前还有一个点容易被忽略:对象命名规范。
OBS的对象存储是扁平的,并没有真正的文件夹概念,所谓的“文件夹”只是对象名前缀相同的逻辑分组。比如你上传logs/2025-06-01/app-logs.tar.gz,实际的对象名就是logs/2025-06-01/app-logs.tar.gz,控制台展示的时候按前缀模拟了文件夹。
合理的命名规范对后续的检索和生命周期管理非常重要。我的建议是:
- 时间维度放前面:便于按时间范围批量管理,比如logs/2025/06/01/比logs/2025-06-01/更适合做生命周期规则匹配。
- 应用维度放前面:如果你的归档数据来自多个应用,logs/app-a/2025/06/01/比logs/2025-06-01/app-a/更好用。因为生命周期规则是按前缀匹配的,你可以很容易地为某个应用单独设置保留天数。
- 文件类型维度放前面:如果除了日志还有数据库备份,db-backup/和logs/分开更容易管理不同存储类别。
我最终使用的命名规范是:archive/app-name/yyyy/mm/dd/file-name.tar.gz。然后配置生命周期规则:
- 对于logs前缀的对象,90天后转为低频访问存储,180天后转为归档存储,365天后删除;
- 对于db-backup前缀的对象,30天后转为归档存储,180天后删除。
这样做好处是成本可控,坏处是你需要提前规划命名规范,后面改就麻烦了。所以命名规范一定要在上传脚本写死之前定下来,这是我最想强调的一点。
3.4 全流程联调与验证
脚本写好后,先在测试桶上跑一遍全流程。我当时犯了一个错误:图省事直接用生产桶做联调,结果一堆垃圾测试文件留在了桶里,后续清理还要花时间。
正确的做法是准备一个test桶,放几条数据,跑完整的压缩上传删除流程,确认对象能正常上传、能下载、能删除,再去生产环境。
联调时重点检查这几个方面:
- 文件上传后对象大小是否和本地一致(用MD5值比对最准);
- 对象能否正常下载并解压;
- 权限策略是否生效(用一个不该有权限的AK/SK验证是否能被拒绝);
- 断点续传和失败重试是否工作;
- 上传成功的文件是否真的被本地清理了。
这些验证都通过之后,再挂到定时任务里跑一个晚上,第二天检查结果。
- 常见问题与排查技巧实录
这个部分我打算把前面提到的坑集中总结一下,做成一个“问题现象 -> 排查思路 -> 解决方案”的结构,方便大家以后直接对号入座。
4.1 上传慢:并发参数与网络链路优化
问题现象:单个文件几MB到几十MB,上传速度却只有几百KB/s,一个20MB的文件要传好几分钟。
排查思路:
第一步,先排除网络问题。用curl测试到OBS Endpoint的延迟和带宽,排除本地出口带宽限制。如果业务服务器在华为云ECS上,确认是不是走的公网Endpoint。公网链路不稳定,而且还会产生流量费用。正确做法是使用OBS的内网Endpoint,也就是在EndPoint里把域名从obs.cn-north-4.myhuaweicloud.com改成obs.cn-north-4.myhuaweicloud.com的内网版本,在华为云ECS上解析会自动指向内网IP。
第二步,看SDK配置。如果你用的是putFile这种单连接上传,速度肯定上不去。换成uploadFile断点续传,设置合适的taskNum和partSize,速度会快很多。
第三步,看服务端是否限流。如果上传请求频繁触发限流,SDK会返回503或SlowDown错误。这种情况可以通过降低并发、增加退避重试时间来解决。
最终我的配置是:
- 使用断点续传接口;
- partSize设置为20MB;
- taskNum设置为8;
- 超时时间调大到60秒。
实际测下来,单文件从原来的一分多钟缩短到十几秒,提升非常明显。
4.2 上传失败但本地文件被误删:重试逻辑设计不当
问题现象:凌晨的定时任务报错,但本地日志文件已经被删除了,导致数据永久丢失。
这个坑特别隐蔽。最初我的脚本逻辑是:上传完成即删除本地文件。但“上传完成”的判断条件是“SDK接口返回成功”,而SDK返回成功后又发生了某种异常——比如删除本地文件时抛出了异常——实际对象并没有完全上传完整。更常见的情况是,我用了“上传成功后返回对象版本号或ETag”的判断,但业务代码对返回值处理不当,导致把“部分失败”当成“全部成功”。
解决方案是“先验证,后删除”:
def upload_and_verify(local_file, bucket_name, object_key): # 上传 resp = obs_client.putFile(...) if resp.status >= 300: raise Exception(f"上传失败: {resp.errorCode} {resp.errorMessage}") # 校验:比较本地文件和云端对象的ETag head_resp = obs_client.headObject(bucket_name, object_key) local_etag = calculate_local_etag(local_file) if head_resp.etag != local_etag: raise Exception("上传完整性校验失败") # 校验通过后才删除本地文件 os.remove(local_file)ETag在OBS里就是对象内容的MD5(对于简单上传而言)。本地算一下MD5,云端对象的ETag对比一下,一致才说明上传完整。这个逻辑在并发量不高、对象文件不大的场景下完全没有性能问题,但能极大提高数据安全性。
如果你怕麻烦,也可以简化成“上传后延迟删除”,比如把本地文件移动到备份目录,保留7天后再清理。磁盘成本换数据安全,非常值。
4.3 403 Forbidden:权限策略的坑
问题现象:上传时返回403 Forbidden,明明AK/SK是正确的,IAM用户也有OBS权限,但就是操作不了。
排查思路:
第一步,检查Endpoint是否和桶的区域一致。比如桶在华北区,但是SDK配置的Endpoint是华东区的,请求会被路由到错误的区域,返回403或404。
第二步,检查IAM策略中的Resource是否正确。很多人会忘了在Resource里加桶名,或者把桶名路径写错。一个常见的错误写法是把Resource写成obs::::bucket:your-bucket-name,却忘了加/。这样策略会匹配“桶本身”的权限,而不是“桶里的对象”的权限,上传对象时依然被拒绝。
第三步,检查是否使用了错误的认证方式。临时凭证方式需要同时传SecurityToken,如果漏掉了就会鉴权失败。
第四步,如果确认代码没问题,去控制台的事件追踪里查看具体的拒绝原因。OBS的日志会告诉你具体是哪条策略拒绝了请求,比对着文档猜要高效得多。
4.4 断点续传没有生效:进度文件目录不可写
问题现象:调用SDK的uploadFile接口,发现大文件上传失败后重试,依然从零开始,断点续传好像没起作用。
排查思路:断点续传依赖本地保存上传进度。SDK默认的进度文件存放路径可能在你运行的目录下,这个目录可能是只读的。我遇到的具体场景是把脚本丢到Docker容器里,容器的工作目录没有持久化,Pod重建后进度就丢了。
解决方案是在初始化UploadFileRequest时,显式设置checkpoint文件路径:
request.setCheckpointFile("/data/checkpoint/upload_checkpoint");并且确保这个路径挂载了持久化存储。这个问题不常见,但一旦遇到会让人非常困惑,因为上传功能本身没问题,只是“续传”没生效。
4.5 成本失控:公共读桶被刷流量
前面提过公共读桶被刷流量的案例,这里再细说下。公共读桶一旦对象名可预测,坏人就能遍历下载你的所有文件。即使对象内容不敏感,流量和请求次数也会产生大量费用。
如果你确实需要公开访问某些对象,正确的做法是:
- 使用私有桶,需要访问时用“临时签名URL”;
- 在签名URL设置有效期,比如十分钟或一个小时,过期自动失效;
- 给整个桶或者特定对象前缀设置访问控制列表(ACL),最小化暴露面。
临时签名URL的生成代码很简单:
from obs import ObsClient obs_client = ObsClient(...) url = obs_client.createSignedUrl("PUT", "your-bucket-name", "path/to/object", expires=3600)这个URL有效期内,任何人都可以上传到指定对象位置。适合用于“外部用户直接上传文件到你的桶”的场景。生成方式简单,还能精准控制有效期,比公共读桶安全得多。
- 监控告警与稳定性建设
把基础功能跑通之后,后面做的一件事让我在后续大半年省心了很多——给OBS接入监控告警。
很多人在使用对象存储时,只关注“传没传上去”这个结果,却忽略了过程指标。一旦半夜定时任务失败,等到早上发现时已经是几个小时之后,数据积压带来的连锁反应非常麻烦。
5.1 关键监控指标
我重点监控以下几个指标:
- 上传成功率:每半小时统计一次,成功率低于99%触发告警;
- 上传失败分布:按错误码聚合,比如AccessDenied、Timeout、ServerError,出现新错误码时重点排查;
- 上传耗时:p99耗时如果明显上涨,说明网络或服务端可能有问题;
- 桶容量增长:帮助做容量预测和成本规划。
监控数据我通过华为云的云监控服务(Cloud Eye)来采集,也可以自己写脚本把OBS的SDK返回数据吐到Prometheus。关键是你得知道你上传任务的状态,而不是等业务方来反馈。
5.2 告警通知渠道
华为云支持通过SMN(Simple Message Notification)发送告警通知,可以绑定邮件、短信、企业微信/钉钉机器人等。我实际用下来最推荐的是绑定到企业微信机器人,告警消息实时到达,比邮件快,比短信成本低。
5.3 运行时自我保护
除了外部监控,在应用内部也做了一个“熔断”机制:
- 连续失败超过10次,停止自动重试,进入“暂停上传”状态;
- 暂停期间保留本地文件,同时发出告警,等待人工介入;
- 人工确认问题修复后,手动恢复任务,从断点继续上传。
这么设计的原因是,如果服务端出了问题,你的重试不仅不会成功,反而会加剧压力。与其死磕,不如停下来养精蓄锐,等确认没问题了再跑。
- 后续优化方向与个人经验总结
在我们这套日志归档系统稳定运行了半年之后,我陆续做了几个优化,这里也一并分享出来。这些方向不一定适合所有人,但有参考价值。
6.1 从定时批量上传改为实时流式上传
定时批量上传的问题在于,日志落盘和上传之间有延迟,一旦服务器崩溃,这段时间内的日志就丢了。实时性要求高的场景,建议改用流式上传,日志产生后立刻写入OBS,不需要在本地落盘再定期清理。
OBS SDK支持流式上传,直接把InputStream传给客户端就行。但要注意流式上传对网络要求更高,断点续传之类的能力也不适用于纯流式场景。取舍看业务需求。
6.2 引入CDN加速
如果你的用户需要频繁下载OBS里的文件,建议在OBS前面加CDN。CDN会缓存热点文件,减轻OBS的访问压力,同时也能显著降低用户侧的下行速度。流量成本可能更低,因为CDN流量单价通常低于OBS直接下行流量。
6.3 跨区域复制
如果业务有容灾需求,可以开启OBS的跨区域复制功能,把数据从一个区域自动复制到另一个区域。这个功能需要额外费用,但比起自己开发双写逻辑,成本低得多。注意跨区域复制是异步的,极端情况下可能有秒级延迟。
6.4 数据生命周期管理自动化
我上面提到过用生命周期规则管理日志保留时间。还可以配合对象版本控制,防止误删或恶意删除。开启版本控制后,删除对象其实是新增一个“删除标记”,历史版本仍然保留,需要时可以恢复。
最后再分享一个小技巧:如果你用Java SDK,一定要记得在应用退出时调用obsClient.close(),释放连接池和后台线程。不然在频繁创建ObsClient实例的代码里,很容易出现连接泄漏,最终把文件句柄耗尽。我就在生产环境里踩过这个坑——任务每次运行新建一个客户端,跑完不关闭,一周后Tomcat直接报Too many open files。排查了半天才发现是OBS客户端没释放。
这一点官方文档有提,但位置比较隐蔽。代码层面加上try-finally或者用try-with-resources就是一句话的事,但忘了它,后果就是线上服务莫名其妙崩溃。
在我自己实践的收尾阶段,现在的OBS归档任务已经非常安静了,每天凌晨定时跑,只有上传失败或者成功率异常时才会发告警,基本做到了“无人值守”。整个过程从“崩溃”到“稳定”,最大的收获是学会了一条原则:云服务不是调通API就结束了,围绕它的一整套权限模型、失败处理、监控告警和成本管理,才是真正拉开普通开发者和资深工程师差距的地方。
希望这篇文章能帮你少走点弯路。如果你也在华为云OBS上遇到过什么奇葩问题,欢迎在评论区聊聊,大家一起排坑。