存算分离这个词,这几年在圈子里几乎成了大数据架构的标配话题。一聊到数据仓库、数据湖、弹性扩容,十个搞数据的工程师里至少八个会问一句:你们现在是不是存算分离了?我从零开始搭过一套HDFS存算一体的集群,也在一次真实的平台重建中完整经历了从“存算一体”切到“对象存储+弹性计算”这套架构的全过程。踩了不少坑,也沉淀了一批可以直接复用的配置和流程。这篇内容就是把这些实战经验摊开写出来,目标是让手里正好有类似需求、或者正在评估要不要上存算分离的朋友,少走一点弯路。
我默认你看这篇内容的时候,至少有基本的Hadoop、Spark使用经验,知道HDFS是干嘛的,也跑过几个Hive或者Spark任务。没有也没关系,我会把每个关键节点的原理讲清楚,你照着抄配置也能先跑起来,后面再慢慢补细节。
1. 从一个真实痛点说起:为什么要搞存算分离
1.1 存算一体的瓶颈在哪里
先回忆一下传统大数据平台的经典形态:十几台服务器,每台机器上既放DataNode又是NodeManager,HDFS数据直接落在本地磁盘,MapReduce或Spark任务也在这些机器上跑。这种“数据在哪算力就在哪”的设计,最早是为了解决大数据计算最关键的本地性问题——计算任务尽量读取本机磁盘,避免网络传输。
但跑了两三年之后,问题会一点点暴露出来。
最头疼的是扩容时绑手绑脚。业务增长带来的往往只有计算压力,比如临时要跑一批大查询,或者月底报表集中生成,这时候CPU和内存不够用。但存算一体的集群里,你加计算节点就必然要加磁盘,等于为了一点点算力,把暂时用不到的存储也一起买回来了。反过来,如果数据量涨了而计算资源够用,你也只能连带扩容一堆算力。这种“算力按存储配比”的耦合关系,在规模上来之后会非常浪费。
其次是存储成本。HDFS默认三副本,一份数据存三份。假设你有1PB有效数据,实际占用的物理空间接近3PB。再加上常规的一年内数据不淘汰、冷热不分离,大部分存储空间都在为“以防万一”买单。本地盘损坏、节点替换、数据均衡之类的运维操作也逃不掉。几十台节点的小集群可能还好,到了几百台的规模,数据均衡和恢复任务经常能把网络带宽吃掉一大截,直接影响线上任务的运行。
还有一个容易被忽略但很致命的问题:混部署模式下,计算节点的生命周期和数据强绑定。你想把一套计算环境整体释放掉,比如夜间分析集群用完就关机,在存算一体的架构下基本做不到——因为机器上还躺着数据。这种“算力无法随业务脸色随时变化”的僵硬感,在业务高峰和低谷非常明显的场景里几乎不可接受。
1.2 存算分离到底解决了什么问题
存算分离的核心动作,就是把数据和计算彻底拆开。数据统一放到独立的存储层,通常是对象存储或者共享文件存储;计算层变成一个个独立的、无状态的集群,需要时拉起来跑,跑完整个释放掉。计算和存储之间通过网络协议访问,而不是依赖本地磁盘路径。
这样做最直接的价值是打破了扩容的捆绑关系。存储不够了扩存储,算力不够了加计算节点,互不拖累。从成本上去看,对象存储通常用纠删码或多副本策略,实际存储开销比HDFS三副本低不少,冷数据还可以再降一档存储等级。
弹性是另一大优势。计算集群释放后,计算资源可以归零,只保留存储账单;下次有任务再启动几十台节点,按小时计费。这种模式放到自建机房同样适用——K8s上随时拉起计算Pod,数据本体放在远端,Pod销毁也无所谓。很多公司选择存算分离的初衷并不是“技术时髦”,而是预算和运维压力逼着他们找一条更灵活的路。
数据共享也是很多人忽视的收益。存算一体时代,一份HDFS数据只能被一个计算引擎直接读取,想给另一个引擎用就得复制一份。存算分离之后,所有引擎连同一个数据源,Spark能做批处理,Trino做即席查询,Flink做实时计算,全部直接读同一个桶里的数据。数据只有一份,不会出现“ODS库里两套账”这种事。
1.3 哪些场景真正适合存算分离
先说结论:不是所有平台都适合切存算分离。如果你是一个稳定运行多年的几十节点中小集群,业务模式比较固定,数据量一年也涨不了多少,其实没必要折腾,继续用存算一体也能活得很舒服。存算分离的收益在特定场景下才会被放大。
第一类是数据量很大但计算波动明显的场景。比如白天在线的报表查询不算多,但晚上有大量批处理任务,某些月份还有集中性的数据分析周。这种场景下计算集群可以设定成定时伸缩,数据本身放在对象存储不动,成本优势非常明显。
第二类是数据湖或者湖仓一体架构的搭建。数据湖天然提倡“一份数据,多种引擎”,对象存储作为底座,Iceberg或Hudi这类表格式承接ACID和快照语义,Spark/Trino/Flink共享一套数据。这个方向几乎必然走向存算分离。
第三类是机器学习平台需要统一访问数据。训练集群和数仓集群不是一个体系,但又要用同一批数据。数据放在对象存储里,训练框架直接通过S3协议读,两边互不干扰。
如果你的场景一个都不沾,那存算分离对你来说更多是“将来时”。可以从现在开始做好数据分层和存储规划,但没必要立刻把集群推翻重建。
2. 平台整体架构设计与组件选型
2.1 存储底座:对象存储与HDFS的取舍
存储底座选型是整个平台最关键的一步,没有之一。计算引擎可以换,调度框架可以换,但存储底座一旦定下来,想改的成本极高,所以这块要慎重。
我的建议很直接:除非有硬性合规要求或者团队对HDFS极其熟练,否则新平台一律优先考虑对象存储。自建机房推荐用MinIO或Ceph RGW,公有云上就直接用云厂商的对象存储服务,因为S3协议已经成了大数据生态的事实标准。Spark、Trino、Flink全部原生支持S3兼容接口,配置几行就能接入,生态完全不用额外养。
有人会问:HDFS不是也能做存算分离吗?把NameNode留着,DataNode单独部署,计算层不落数据不就行了?技术上确实可以,但HDFS毕竟是文件系统,它的强项是强一致性和大文件顺序读,弱项是海量小文件、目录列表性能、成本控制。对象存储在这些方面要灵活得多,而且对象存储的存储类、生命周期管理、桶策略这些能力,HDFS是没有的。
这里分享一个选型判断逻辑:如果团队里HDFS运维经验丰富、存储压力不大、也不想引入额外的对象存储组件,那用独立HDFS集群做数据层完全没问题;如果从零开始,或者正好在做技术栈升级,建议直接上对象存储。后续如果要接数据湖表格式,对象存储的兼容性也会更省心。
2.2 计算引擎:Spark + Trino + 可选的Flink
计算层我给了一套务实组合:批处理和ETL用Spark,交互式SQL查询用Trino,实时计算按需引入Flink。三个引擎共享同一份对象存储数据,元数据统一走Hive Metastore。
Spark是离线批处理的主力。它对对象存储的支持已经很成熟,S3A连接器跑大数据量的读写没有太大问题。不要被网上“S3读得慢”的旧观点劝退,配合列式文件格式、合理的文件大小和缓存策略,Spark在对象存储上的表现已经足够承担核心ETL链路。
Trino(原PrestoSQL)负责的是即席查询和多表关联。它跑在独立集群上,不存任何数据,通过连接器直连对象存储和Hive元数据。它的优势是纯内存计算,适合秒级到分钟级的交互分析。很多团队用Spark做定时加工,用Trino给分析师做即席查询,恰好互补。
Flink要不要上取决于你是否有实时需求,比如实时同步、实时指标、事件驱动等。有的话就把它也作为计算层的一员,同样通过S3连接器读写对象存储。这个组合的好处是生态统一,三种引擎写数据时都通过Iceberg或Hudi的事务接口,不会出现并发写脏数据的问题。
2.3 元数据服务与数据湖表格式
很多人搭存算分离平台,只把存储换成对象存储就完了,表结构还像HDFS时代一样裸挂在路径上。这样做能用,但很快就踩到坑:修改列名要全链路改脚本,分区信息靠人工维护,多个引擎同时写一张表容易互相踩踏。
解决这个问题的关键是引入元数据服务和高性能表格式。元数据服务我仍然推荐Hive Metastore,用MySQL存放元数据表结构。它的生态兼容性最好,Spark/Trino/Flink都能无缝对接。在此基础上,紧跟一步引入Iceberg或者Hudi这类数据湖表格式,让表拥有事务能力,表结构自己演进,分区信息不依赖手工管理。
说人话就是:Hive Metastore管“表是什么”,Iceberg管“表怎么变”。Iceberg最重要的能力是快照隔离和ACID——Spark写入一个快照,Trino正在读老快照,两边谁也不影响谁,写失败也不产生垃圾数据。这种能力在存算分离架构里几乎是刚需,因为对象存储本身不支持跨文件的原子事务。
具体选Iceberg还是Hudi,我的观点是:没有历史包袱就选Iceberg,它的表结构更简洁,社区活力和Spark的集成度都更高,Trino对它的支持也完善。如果你们有比较重的实时更新需求,Hudi的Copy On Write和Merge On Read可能在部分场景下更顺手一点。
2.4 网络规划和数据路径:别让网络成为瓶颈
存算分离之后,计算和存储之间所有数据流都走网络,网络规划的重要性甚至高于计算节点本身的规格。网络带宽不足,配置再好的CPU也没有意义,因为任务大部分时间都在等数据。
计算节点和存储节点之间的网络,强烈建议至少上25GbE,如果预算允许直接干到100GbE。这里给个粗略估算方法:假设你经常跑100GB量级的Join任务,Scan阶段要拉60GB数据,万兆网络理论耗时60秒,实际加上任务调度、shuffle和磁盘开销,耗时很容易超过五分钟。如果换成25GbE,数据拉取时间直接砍掉一半多。整体链路体验的差距是非常直观的。
另外要注意对象存储访问的路径模式。S3A连接器默认用的是Virtual Host模式,也就是URL里的bucket是域名的一部分。自建MinIO默认是Path Style模式,Spark里如果不配置hs.s3a.path.style.access=true,经常出现无法访问到bucket的问题。这个细节我见过太多人卡了半小时起步,后面部署配置部分还会再强调。
网络层面还有一个容易被忽略的点:不要把所有计算任务的并发扫描都怼到网络上限。我一开始给Spark开了高并发读,每条task都在拉大文件,结果存储节点的网卡被打满,单个任务反而拖慢整个集群。后面加了流量控制和读取并发上限,整体吞吐反而上去了。
3. 从0到1部署实操:核心环节拆解
3.1 组件版本与部署形态规划
这里先给出一份可以直接参考的组件清单和版本组合。我没有选择最新版本,因为大数据组件追新会带来兼容性风险,稳定性和社区使用面更重要。
| 组件 | 我用到的版本 | 说明 |
|---|---|---|
| MinIO | RELEASE.2024-xx | 存储底座,s3兼容 |
| Spark | 3.5.x | 离线批处理 |
| Trino | 4xx | 即席查询引擎 |
| Hive Metastore | 3.1.x | 元数据服务 |
| Iceberg | 1.4+ | 数据湖表格式 |
| Flink(可选) | 1.18+ | 实时计算 |
部署形态上,我建议把三类角色物理分开:存储节点一组,元数据节点一组,计算节点一组。如果资源紧张,至少也要保证存储和计算的物理隔离,否则一个跑重任务,另一个的IO毛刺会相互影响。
计算集群建议直接跑在K8s上,用Helm部署Spark Operator和Trino,利用K8s的弹性伸缩能力实现计算资源的按需启停。没有K8s整套体系也没关系,裸机部署Spark Standalone或YARN也可以走通存算分离,只不过弹性会弱一些。我自己是从裸机切到K8s的,中间确实多花了些时间,但后期扩容和故障恢复的省心程度远超预期。
3.2 对象存储侧配置实践
MinIO的部署本身不复杂,生产环境建议至少4节点起步,开启纠删码模式,存储效率比三副本高,同时保留一定的数据冗余能力。以4节点、每节点8块盘为例,数据在16块盘上打散,任何单盘损坏不影响数据读写,换盘后自动恢复。
踩过的一个坑是MinIO的访问密钥管理。默认生成的root账号权限太大,建议创建独立用户,按bucket粒度分配只读或读写权限。具体做法是先建一个bucket,再建一个group,把用户挂进group,最后用policy绑定group对bucket的权限。这样即使某个计算集群的密钥泄露了,影响范围也限制在指定的bucket里,不会一把梭全平台。
对象存储的桶规划同样重要。我按数据的生命周期和访问频率拆了几个桶:raw_data、warehouse、temp、archive。热数据最近几个月频繁读,放在标准存储;归档数据放低频存储类;临时数据任务跑完就标记清理。这套目录和桶的设计要提前想好,因为后续所有表和任务的路径都会跟着它走。桶和目录一旦定下来,后期改造非常痛苦。
3.3 计算引擎连接对象存储的关键配置
接入环节,我把Spark和Trino连接对象存储时最重要的配置贴出来,直接用。
Spark侧核心配置写进spark-defaults.conf,代码语言标注properties:
spark.hadoop.fs.s3a.endpoint=http://minio-host:9000 spark.hadoop.fs.s3a.access.key=your-access-key spark.hadoop.fs.s3a.secret.key=your-secret-key spark.hadoop.fs.s3a.path.style.access=true spark.hadoop.fs.s3a.connection.maximum=128 spark.hadoop.fs.s3a.attempts.maximum=10 spark.hadoop.fs.s3a.connection.timeout=60000 spark.hadoop.fs.s3a.fast.upload=true spark.hadoop.fs.s3a.multipart.size=128M这里的path.style.access务必设为true,这是对接自建MinIO的硬性要求。连接数上限我一开始用的是默认值,结果高并发任务频繁出现连接超时,调大后稳定很多。
Trino侧配置建一个s3.properties的Catalog文件:
connector.name=iceberg iceberg.catalog.type=hive hive.metastore.uri=thrift://hms-host:9083 hive.s3.endpoint=http://minio-host:9000 hive.s3.aws-access-key=your-access-key hive.s3.aws-secret-key=your-secret-key hive.s3.path-style-access=true这里有一个重要的选择:Trino直接走Iceberg连接器,而不是老式的Hive连接器。因为Iceberg连接器能把快照管理、表演进这些能力带给Trino查询。Trino节点本身不落数据,每次查询直接从MinIO拉数据进内存。首次启动查询前确保测试一下连接器是否能正确读取表的元数据,别等到生产查询才发现minio的endpoint写错。
3.4 元数据服务对接
Hive Metastore部署好之后,还需要把Spark的Catalog指过去。这里我建议不要在Spark侧用Hive默认的Metastore,而是通过Iceberg的HiveCatalog方式统一管理。简单说,Spark读表的时候,先通过Metastore拿到表的元数据信息,其中包括数据文件在MinIO上的路径,然后Spark再通过S3A直接读取文件。
配置示例:
spark.sql.catalog.spark_catalog=org.apache.iceberg.spark.SparkCatalog spark.sql.catalog.spark_catalog.type=hive spark.sql.catalog.spark_catalog.uri=thrift://hms-host:9083 spark.sql.catalog.spark_catalog.warehouse=s3a://warehouse/这里的warehouse指定的是Iceberg表在对象存储上的根目录,所有表默认建在这个桶下面。强烈建议把Iceberg的warehouse单独放到一个桶,不要和临时目录混在一起,这样后续做生命周期管理和数据备份会很清爽。
元数据服务的高可用也要提前考虑。Metastore虽然不存数据文件,但它挂了整个平台就瘫痪了。我部署的是Metastore双节点,后端MySQL做主从,Thrift端口做负载均衡。实际线上运行半年,MySQL切换过一次,Metastore本身基本没出现过单点故障。
3.5 历史数据迁移:把HDFS数据搬到对象存储
从HDFS存量集群切换到对象存储,数据迁移是最艰巨的环节,也是最容易翻车的环节。我的经验是分成三步走:全量迁移、增量追平、增量双跑。
全量迁移阶段,直接用Hadoop自带的distcp,把HDFS的数据拷到S3A路径:
hadoop distcp \ -D fs.s3a.endpoint=http://minio-host:9000 \ -D fs.s3a.access.key=your-access-key \ -D fs.s3a.secret.key=your-secret-key \ -D fs.s3a.path.style.access=true \ hdfs://namenode:8020/data/warehouse \ s3a://warehouse/data/warehouse迁移前务必先做小文件统计。HDFS时代很多老表都是攒小文件的:几百万个小文件分散在几千个分区里,直接拷贝到对象存储后,后续查询的性能会惨不忍睹。所以我在迁移的同时启动了一个Iceberg小文件合并任务,把200MB以下的数据文件重写合并,目标压缩到256MB左右一个文件。这一步非常费时间和计算资源,但只要是文件大小合理的表,查询提速非常明显。
增量追平阶段,每天定时跑增量同步,把HDFS上新写的数据同步到对象存储。这里要注意一点,不要重复全量迁移期间的数据,否则会造成数据冗余。增量双跑阶段,业务任务先在HDFS上正常跑,另一批加工任务在对象存储上跑同一份逻辑,验证输出结果一致后,再把调度从HDFS切换到对象存储。
切换完成后,不要立刻把HDFS集群销毁。保留一到两周,等业务跑稳了再下线。万一新链路有问题还有退路,这个保险非常值得买。
4. 性能调优,让“远程读”不再慢
4.1 缓存层:计算侧IO加速
迁移完成之后,最容易听到的抱怨就是:查询变慢了。原因在于对象存储的访问延迟比本地磁盘高至少一个数量级。本地IO是微秒级,对象存储网络IO直接在几毫秒到几十毫秒之间,完全靠裸读肯定不行。
最直接的解法是给计算侧加缓存层。第一种选择是引入Alluxio或类似组件,在计算节点本地挂一层分布式缓存,热点数据读一遍之后,后续任务直接走缓存,命中率高了之后查询体感会非常接近本地读。第二种轻量方案是直接用对象存储的读缓存功能,比如Spark的S3A连接器本身有readahead和缓存机制,但需要配置得当。
我实际生产中采用的是两层策略:热数据表用Iceberg的表属性设置为JOIN用Prefetch模式,在Spark SQL里开spark.sql.adaptive.enabled和spark.sql.adaptive.coalescePartitions.enabled。这个组合比较轻量,不用额外维护缓存集群,对日常报表场景命中率已经超过六成。如果后续要支撑更重的高并发查询,再上Alluxio也不迟。
4.2 文件布局:给小文件做“瘦身”
对象存储对小文件非常不友好,文件列表本身的性能上限决定了它不可能像HDFS那样轻松处理几百万个小文件。这个问题的根子在数据写入阶段,不在查询阶段。
我定了一个硬性规范:所有写入对象存储的表,单文件大小尽量控制在128MB到512MB之间;低于64MB的文件统统视为需要治理。Spark写数据时,通过参数控制并行度和单文件大小:
spark.sql.adaptive.coalescePartitions.enabled=true spark.sql.adaptive.coalescePartitions.minPartitionSize=128M spark.sql.adaptive.coalescePartitions.targetPostShuffleInputSize=512M对于Iceberg表,写完之后跑一次rewrite_data_files,把小文件聚合:
CALL spark_catalog.system.rewrite_data_files( table => 'db.log_events', strategy => 'binpack', options => map('target-file-size-bytes','268435456') );binpack策略适合简单合并,不改变数据内容,只改变文件布局。遇到经常要过滤某个字段的表,还可以用sort策略在合并时排序,把查询过滤效率也一起提上来。
4.3 下推优化与查询引擎参数
对象存储本身没法做索引,所以查询性能很大程度上依赖“尽量少读数据”这个原则。谓词下推和列裁剪是每一类查询都必须吃透的点。
Iceberg表和Hive表不一样的地方在于,Iceberg自带分区统计信息,Spark和Trino可以通过Manifest文件直接跳过不需要的分区,这个能力比Hive的目录扫描要优雅很多。我见过有人把Iceberg表还是按Hive的习惯写全分区扫描SQL,明明可以用date字段过滤却非要用函数处理后过滤,导致统计信息失效,扫描了全表。这类细节平时很难察觉到,但大数据量下差距可能差几十倍。
Trino侧的几个参数也值得调:调整task concurrency、设置更大的query memory配置、开起dynamic filtering。动态过滤在Trino上很有效,大表Join小表时,小表会先生成一个过滤集,大表扫描时直接跳过不匹配的行组,这种优化在对象存储架构下价值更大,因为减少的不是磁盘IO而是昂贵的网络IO。
4.4 一套可以直接抄的调优参数表
汇总一份我实际用起来效果比较稳的参数清单,每一项都标注了适用场景,你直接抄到生产环境,再按自己的资源量微调:
| 参数 | 推荐值 | 场景说明 |
|---|---|---|
| spark.sql.adaptive.enabled | true | 开启自适应查询执行 |
| spark.sql.adaptive.coalescePartitions.enabled | true | 动态合并小分区 |
| spark.sql.shuffle.partitions | 按核数*2~3配置 | shuffle后分区数 |
| fs.s3a.connection.maximum | 128~256 | 并发连接数,高并发调大 |
| fs.s3a.threads.max | 32 | 每个task的最大线程数 |
| fs.s3a.fast.upload | true | 启用分片上传 |
| fs.s3a.multipart.size | 128MB | 分片大小 |
| parquet.block.size | 256MB | Parquet行组大小,影响压缩率 |
| spark.sql.parquet.compression.codec | zstd | 压缩比和速度平衡好 |
调优的过程更像做实验,不要一次把所有参数都改了。我习惯一次只改两三个参数,跑同一套业务SQL对比执行时间,改完后记录基线,慢慢迭代出适合自己数据特征的最佳配置。记住,没有放之四海皆准的参数组合,只有最适合你自己数据特征的组合。
5. 常见问题与排查纪实
5.1 权限认证 403/401 类问题
存算分离平台刚上线那段时间,我遇到的最高频问题就是S3A报403 AccessDenied或者401 InvalidAccessKeyId。
排查步骤非常简单:先用MinIO客户端直接访问,确认密钥有效;再用curl通过预签名URL确认网络链路通;最后再检查Spark配置。我自己遇到过一个比较隐蔽的场景:MinIO服务端更新过secret key,但Trino的连接器没有做热更新,重启Trino之后才生效。所以遇到这类问题,第一步一定是确认密钥在服务端有效,根本不用急着怀疑连不上。
还有一个很容易漏掉的情况:对象存储的时钟漂移。S3签名会对请求时间做校验,如果计算节点的时间和存储节点偏差超过几分钟,就算密钥正确也会报403。时钟同步服务在云上一般默认配置好了,自建机房就要多留个心眼。我们曾经排查了一个下午的403,最后发现是两台计算节点NTP服务失效。
5.2 写入性能差与零字节文件
任务跑完了,但数据没落盘的场景也遇到过。Spark任务显示成功,结果目标路径下只有一堆零字节文件或者完全没有数据。第一次遇到我以为是Spark的commit机制问题,后来发现是S3A的写缓存设置不当。
Spark写S3A的时候,数据先写到本地临时目录,然后分片上传。如果临时目录空间不足或路径权限不对,任务就会静默失败。解决方法是显式设置本地临时目录,并确保有足够的磁盘空间:
spark.local.dir=/data/spark-tmp spark.hadoop.fs.s3a.buffer.dir=/data/spark-tmp另一个写入慢的原因是没开fast upload,默认的单文件上传在大数据量下非常拉胯。开启之后,配合multipart.size的设置,写入吞吐会明显改善。这一点值得每个新接对象存储的团队都先检查一下。
零字节文件的另一类来源是Iceberg表写提交失败产生的孤儿文件。Iceberg本身有清理机制,但默认不自动执行,需要定期跑expire_snapshots和remove_orphan_files两段SQL来清理,否则时间一长,对象存储里会积累大量无效文件,光列表就够让人头疼的。
5.3 元数据服务导致的慢查询
有一次Trino查询特别慢,慢到几乎像卡死,排查了SQL半天没发现问题。后来看Metastore监控,发现是底层MySQL一个慢SQL把整个元数据服务拖住了,所有查询都在等元数据响应。
这个问题的根子在于Metastore对Iceberg的Manifest文件做解析时频繁访问数据库。解法有几个层次:把Metastore后端的MySQL参数调优,增大连接数和缓存;给Metastore本身加JVM堆内存,让它能缓存更多表的元数据;更彻底的方案是控制单表的数据文件数量,文件数越少,元数据解析越快。我后来把天天跑的大表做了一次重分区合并,文件数从数十万降到几千,元数据查询从数十秒降到一秒以内,效果立竿见影。
另外,Metastore数据库和元数据服务最好放在同一机房或者同一可用区,避免跨地域访问,跨地域延迟基本都会让所有表的元数据加载时间变长。
5.4 存量HDFS集群平滑过渡
很多团队不会选择全量推翻重来,而是让HDFS集群和对象存储并存一段时间。并存期常见的问题是业务任务一条链路读HDFS,另一条读对象存储,两边的数据处理逻辑稍有不同,最终产出对不上。
我的经验是尽量把并存期缩短,并存期内所有加工口径只改存储路径,不改业务逻辑。你甚至可以写一层数据访问层,把表路径抽象成逻辑名,底层指向HDFS还是对象存储用配置开关控制。这样切换时只需要把开关拨到对象存储,业务代码几乎不用动。
要特别提醒的是,不要在并存期做复杂的格式转换。比如把HDFS上的Parquet转成对象存储的ORC,这种同时引入存储和格式两个变量的操作,一旦验证失败,很难判断是存储层还是格式导致的差异。先只切存储、不换格式,跑稳一小段时间后再考虑要不要换文件格式,风险会降低很多。
6. 踩坑心得:哪些钱不值得花
最后聊几句实际体验层面的东西,算是给想动手的团队一个提醒。
第一个心得是:不要为了“技术先进”而上存算分离。如果你的集群规模几十个节点,计算压力稳定,HDFS用了很多年也没有痛感,那存算分离带来的改造成本可能远大于收益。这玩意儿真正解决的是成本和弹性问题,不是查询性能问题。如果业务上查询不快,存算分离解决不了,该优化SQL还是要优化SQL。
第二个心得是:小文件治理这件事,越早做越划算。对象存储上的小文件治理不比HDFS轻松,甚至更困难。数据写入链路里就把文件大小控制好,远比事后跑合并任务省资源。我自己那条血泪教训是,迁移阶段为了赶进度跳过了一部分老表的小文件合并,结果这些表后来每次查询都拖后腿,只能再花几倍时间返工。
第三个心得是:监控和可观测性在存算分离架构下更值得重视。以前在HDFS时代,数据本地化让性能问题相对容易定位。现在所有读写都走网络,一旦出现性能劣化,可能是对象存储慢、网络抖动、缓存失效多种原因叠加。建议至少从组件侧白屏化三个方面指标:对象存储的请求延迟和吞吐、计算集群的网络IO、查询引擎的慢SQL和缓存命中率。把这三块指标覆盖到位,日常排查问题的效率至少提升一倍。
存算分离不是银弹,但它确实是当前大数据架构演进里最值得投入的方向之一。只要在架构选型、数据治理、网络规划这几个关键点上下足功夫,整个平台的弹性和成本结构都会有质的变化。上面这些配置、参数和流程都是我亲自跑过一遍验证过的,希望你能在这个基础上少踩坑,把精力花在真正需要创造力的业务问题上。