1. 存算分离到底拆的是什么?先把这个概念讲清楚
大数据圈子里这两年有一个词被反复提起:存算分离。说它是老技术吧,确实十多年前就有分布式存储和计算分开的思路;说它是新技术吧,近几年云厂商、开源社区又全都在重提。我做数据平台相关工作这些年,最大的感受是:很多人对存算分离的理解停留在"存储和计算分开放"这个字面意思上,但真到了选型、排障、设计架构的时候,又容易踩坑。这篇文章我就把大数据领域里存算分离的典型应用场景、底层逻辑、落地细节、常见问题一次讲透。
先说清楚它到底拆的是什么。传统的大数据架构,尤其是Hadoop生态早期那套,计算节点和存储节点是绑在一起的。每个DataNode既负责存数据块,也负责跑计算任务。数据本地性(Data Locality)是这种架构的核心优势,任务调度器会尽量把计算调度到数据所在的那台机器上,避免数据跨网络传输。但问题也随之而来:存储扩容和计算扩容被绑死了。你的集群如果是因为存储不够所以要加机器,那加的机器同时也带来了计算资源,但这些计算资源如果不跑任务就闲置了;反过来,计算不够用的时候加机器,机器自带的磁盘又会带来多余存储。这种耦合在数据量小的时候不觉得,数据量一上来,成本浪费非常扎眼。
存算分离的出发点就是把这两件事拆开:数据统一放在独立的存储层,可以是对象存储、分布式文件系统,也可以是云上的托管存储服务;计算层按需拉起,用完可以释放。存储层和计算层各自按自己的节奏扩容,互不拖累。这个思路听起来简单,但真正落地的时候牵涉到非常多的技术细节,比如计算引擎怎么感知远端数据、缓存怎么做、元数据访问性能怎么保证、小文件怎么治理等等,这些后面我会逐一展开。
从我的实际观察来看,存算分离这几年被讨论得越来越多,还有个很重要的背景是数据湖和AI训练这两类负载的兴起。数据湖要求多个计算引擎(Spark、Flink、Presto、Hive等)能共享同一份数据,如果数据散落在各自集群的本地盘上,共享就无从谈起;AI训练的场景里,样本数据可能上百TB,训练集群是GPU机器,非常贵,不可能让GPU节点同时承担存储职责。两个趋势一叠加,存算分离几乎是必然选择。
那是不是所有场景都应该存算分离?并不是。这就要看下面这张对比表里列出来的几个维度:
| 对比维度 | 存算一体架构 | 存算分离架构 |
|---|---|---|
| 存储成本 | 需要为计算节点配置大量磁盘,成本偏高 | 可使用低成本存储介质,按量付费 |
| 资源利用率 | 存储计算绑定,容易互相牵制 | 各自弹性伸缩,利用率更高 |
| 计算弹性 | 扩缩容要连带考虑存储,动作笨重 | 计算集群可快速拉起或释放 |
| 数据共享 | 数据被集群独占,多引擎访问困难 | 多计算引擎可共享同一份底层数据 |
| 网络开销 | 计算尽量本地读,网络压力小 | 每次读取可能跨网络,对带宽压力大 |
| 运维复杂度 | 要同时运维存储和计算,机器规模大 | 存储统一托管,计算集群轻量化 |
这个表格是评估架构选择时的参考框架,但不是万能答案。比如你的业务如果以超高并发点查为主、每次查询都是毫秒级返回,那存算分离的反向网络开销和元数据瓶颈可能会让你很难受;但如果是分析型负载、海量历史数据、周期性跑批,那存算分离的收益就会非常明显。
2. 哪些业务场景最适合用存算分离?
场景这件事不能泛泛而谈,我说几个自己接触过、也看到行业里反复验证过的典型方向,每个方向都对应不同的技术组合和注意事项。
2.1 离线分析 / 数据湖场景
这是存算分离应用最成熟的场景。数据源如业务库binlog、埋点日志、第三方接口数据,经过采集通道统一落到对象存储或分布式文件系统,形成原始数据层。下游的Spark批任务、Hive数仓任务、Presto即席查询,都通过计算集群直接读取远端存储。
我见过一个典型的存量大数据平台改造案例:原来一套Hadoop集群跑了两百多个周期任务,数据总量大概纯用户行为日志就有几十TB,存储水位一直在告警,而计算资源在凌晨跑批之外的时间里又大量闲置。后来把历史数据分层,低频访问的数据全部迁到对象存储,高频访问的热数据暂时留在本地盘,计算集群只保留必要规模,高峰期弹性扩容。改造完以后,存储成本下降了大概四成,任务运行时间没有明显劣化,因为大部分任务跑的还是近期的热数据,冷数据计算频率低,多花一点网络开销可以接受。
这种场景的技术核心是:计算引擎要能原生对接对象存储。现在Spark、Flink、Hive都提供了成熟的连接器,配置好Endpoint、桶名、认证信息之后,SQL里可以直接读写OSS、S3这类存储的数据。但要注意,底层文件格式最好统一用Parquet或ORC这类列式存储格式,既能压缩空间,也能减少远端扫描的数据量,对网络开销是非常友好的。
2.2 机器学习训练中的特征与样本共享
AI训练场景是存算分离近两年增长最快的领域。训练集群是昂贵的GPU机器,正常情况下没有任何人会把训练数据副本直接放在GPU节点的本地盘上,那样既浪费GPU的存储空间,也很难做数据版本管理。更常见的做法是:特征数据和样本集统一放在对象存储,训练任务启动时,训练框架从远端拉取数据,配合缓存和预取机制做数据装载。
我之前参与过一个推荐场景的特征平台建设,特征数据每天全量快照写入对象存储,各个算法团队根据自己的特征需求做投影和抽样,然后提交训练任务。不同团队的训练任务跑的模型不一样、数据范围不一样,但底层都是同一份特征快照。如果还是传统的存算一体架构,每个团队各拉一份数据到自己集群里,光是存储冗余和文件拷贝就够头疼了。换成存算分离之后,数据只有一份,按需读,训练任务结束GPU集群就可以释放。
这个场景里比较关键的参数是数据读取的吞吐和预取策略。GPU训练任务的数据读取往往是Pipeline式的,如果模型每个Epoch都重新从对象存储拉一遍全量数据,IO会成为明显瓶颈。实操中一般会加一个分布式缓存层(比如Alluxio)或者用训练框架自带的缓存机制,把高频访问的特征数据缓存在计算节点本地,只在样本集有更新时才重新装载。
2.3 实时数仓与OLAP分析
实时数仓场景下,存算分离解决的核心问题是写入和查询的隔离。数据写入侧通常是Flink或Kafka Connect,把流式数据落到消息队列或者直接落到存储层;查询侧是Doris、ClickHouse、Presto这类OLAP引擎。写入和查询如果共用一套资源,很容易出现写入高峰拖垮查询性能的互相干扰。
存算分离之后,写入侧只负责写存储,查询侧只负责算,中间通过存储层解耦。现在很多OLAP引擎也推出了自己的存算分离形态,底层存储用对象存储,计算节点可以秒级扩缩容。我实际测下来,这种形态对"查询频率波动大"的业务特别友好,典型的就是数据可视化大屏场景。大屏什么时候看的人多?业务汇报的时候、大促期间,流量波峰很陡,波谷又很闲。如果用传统集群,你得按峰值准备资源,日常一大半资源白交钱;用存算分离,波峰时多拉几个计算节点,波谷时缩掉,账单差别非常大。
2.4 海量多媒体与遥感影像类数据
这个场景可能很多人不熟悉,但它是存算分离价值非常直观的领域。遥感卫星影像、医疗影像、自动驾驶路测数据,每一类都是PB级别的非结构化数据,而且处理流程通常是"数据先落地、算法再跑"。存算分离架构下,原始影像数据统一存在对象存储里,多个团队可以同时启动各自的处理任务,比如一个团队做几何校正,一个团队做目标检测,另一个团队做镶嵌成图,大家读的是同一份底图,但各算各的,互不干扰。
这类场景还有一个特点就是突发性计算很强。一次自然灾害应急响应,可能需要短时间内把某个区域的影像全量重处理一遍,计算量是平时的几十倍。存算一体的集群根本不可能为这种突发场景做储备,存算分离配合弹性计算则可以做到"平时小集群维持,应急时大规模扩展"。这种能力背后涉及的并行调度、数据分片读取、任务优先级等技术,本质上都是围绕存储与计算解耦来设计的。
2.5 多集群隔离与租户共享
最后一个高频场景是平台层面的:当你的数据平台要服务多个业务线、多个租户的时候,存算分离几乎是必须的。每个业务团队都想用自己的计算资源跑任务,但底层数据希望能统一管理、统一做权限控制。存算分离之后,数据统一在存储层做权限和血缘管理,计算层为各团队建独立的虚拟集群,按各自的配额弹性伸缩。
这里有个容易忽略的点:所谓权限控制不能只在计算层做,因为有能力的人完全可以绕过计算引擎直接用存储客户端去读数据。所以存储层本身要做细粒度的授权,比如对象存储的桶策略、目录级的读写权限、临时凭证机制。很多公司在存算分离改造中踩过权限的坑,这里我建议安全设计要前置,别等数据都迁上去了再补权限。
3. 一份真实的存算分离落地实录:从Hadoop迁移到对象存储+弹性计算
前面讲了很多"场景",这一节我完整还原一次真实的存算分离改造过程。案例背景是某个中型互联网公司的用户行为分析平台,原始架构是一套自建的Hadoop集群,上面跑着埋点日志的清洗、数仓ETL、日常报表查询。问题出在数据增长太快,HDFS的存储水位常年超过80%,DataNode磁盘不够用,但加节点又带来CPU和内存冗余。整个集群两百多台机器,真正忙的时间段每天只有几个小时,其他时间是纯闲置。管理层对成本意见很大。
这个案例里的方案不算复杂,但每一步都有实际的坑。我按流程拆开讲。
3.1 数据分层与迁移策略
第一步不是迁移,而是盘点。我们把HDFS上的数据按访问频率分了三层:热数据(最近7天)、温数据(最近90天)、冷数据(90天以前)。热数据留在本地HDFS,保证近实时报表的性能;温数据和冷数据迁移到对象存储。这个分层策略很关键,如果一刀切全部迁到远端,每天的ETL任务读取性能会明显变差,业务那边肯定炸。
迁移工具当时对比了几种方案,最后选择了分布式数据同步工具配合对象存储的批量导入功能。迁移过程中要注意的是保持文件目录结构和分区规则不变,比如原始路径是/warehouse/event_log/dt=2024-06-01,迁移后对象存储里也保持同样的目录层级,这样Hive或者Spark的元数据表只需要修改location指向即可,不需要改SQL逻辑。
这里有一个迁移前必须做的事:小文件合并。原来的HDFS集群因为历史原因,有大量几十KB到几MB的小文件,这些文件如果直接原样搬到对象存储,后续查询的性能会很难看。为什么?因为对象存储是按请求计费、按对象粒度做元数据管理的,访问一个小文件和访问一个1GB大文件,request次数是一样的,但小文件需要发起的请求数量指数级增长。我们当时用Spark任务对历史分区做了一次重写,把小文件按分区合并成128MB左右的Parquet文件,再批量上传。这一步耗时了两天,但换来了后面查询性能的稳定。
3.2 计算引擎对接与缓存设计
数据迁到对象存储之后,第二步是让计算引擎能够正常读取。Spark任务通过spark.hadoop.fs.oss.impl这类配置指向对应的文件系统实现,Hive则要修改hive.metastore.warehouse.dir或者表的location。这里踩过的最大的坑是:由于对象存储的list操作比HDFS慢一个数量级,Spark在启动阶段如果需要对一个大目录做全量list,任务启动时间会从几秒变成几十秒甚至几分钟。
解决办法有几个:一是尽量用分区裁剪,查询条件里带上分区字段,让Spark不要扫描全目录;二是利用存储的清单(manifest)机制,预先将分区路径清单生成好,任务启动时直接读取清单文件;三是对高频访问的元数据做缓存,比如用Alluxio或者JindoFS的Namespace服务。我们对绝大多数日报任务做了分区裁剪治理之后,启动耗时的问题基本不再出现。
缓存层面,我们当时的策略是按需开启。ODS层的清洗任务每天只跑一次,读的是当天的增量数据,缓存意义不大;但报表团队的即席查询经常反复扫描某些维度表,这类任务开缓存收益非常明显。Alluxio的Local Cache可以配置在计算节点本地盘上,命中率高的场景下查询响应时间和原来本地读差距很小。
3.3 任务调度调整与成本优化
存算分离改造不是迁完数据就结束了,任务调度层面也要调整。原来的调度策略都假设数据在本地,任务启动后直接就近读,时间敏感度相对宽松。改到远端读之后,"本地性"这个概念不存在了,任务调度器要考虑的是怎么让计算节点和数据之间的网络路径最优。
我们当时用了弹性伸缩组,白天常规任务跑在固定规模的常驻集群上,晚上大任务和补数任务高峰期自动扩容一批临时节点,跑完自动释放。这里涉及到一个很实在的成本参数:对象存储的请求费用和流量费用。如果任务写得不好,频繁list目录、反复读相同的数据,请求费用可能比存储费用还高。我们做过一次优化之后,把每日的存储访问请求次数降了一个量级,主要是做了三件事:压缩读取量、增加缓存命中、减少list操作。
3.4 踩过的坑与排查实录
如果只讲方案不讲坑,等于没讲。说几个记忆深刻的:
第一个坑是认证配置遗漏。对象存储的访问凭证如果只配在了Spark的core-site里,而HiveServer2走的是另一套配置,那么Hive任务起来后你会看到一堆莫名其妙的PermissionDenied错误。排查了整整一个下午,最后发现是HiveServer2的aux jar里没有带上新的存储凭证。这个问题的通用教训是:多引擎共用数据层时,凭证和配置要集中管理,别散落各引擎自己的配置里。
第二个坑是rename操作。Hive的某些写操作会先写临时目录再rename到正式目录,HDFS的rename是原子的,但对象存储的rename代价非常高,甚至有些对象存储直接把rename实现为copy+delete,文件大的时候慢到不可接受。我们当时把Hive的中间结果目录都改成了写临时目录+直接覆盖写入的方式,绕开rename。
第三个坑是网络带宽抢用。存算分离之后,大查询如果同时读取远端大量数据,很容易打满网络带宽,影响其他在线服务的延迟。后来我们在计算集群上做了网络限速配置,并且把大查询的调度时间错峰,才压住这个问题。
整体来说,这次改造从决定到基本稳定花了大约一个半月,其中迁移和验证占了大半。收益也很清晰:机器数量从两百多台降到六十多台常驻加上高峰期临时扩容,存储从三副本的HDFS变成对象存储的低冗余存储,整体成本降了一半以上,而核心报表的延迟没有明显变化。
4. 选型之前,先想明白这四件事
我不是劝所有人都马上做存算分离。每次有人问我要不要改,我都先问四个问题,这里也分享出来。
4.1 你的计算负载是什么类型?
存算分离最适合的是分析型负载、批处理、周期性任务、弹性明显的场景。如果你的业务是超高并发的在线查询,比如类似用户维度的实时查询API,每次查询都是毫秒级甚至微秒级响应,那存算分离的远端读取延迟大概率是扛不住的。这种业务更适合用本地存储的OLTP/OLAP引擎,或者加一层高性能缓存来兜底。
4.2 你的数据访问频率和热冷分布如何?
数据如果每天都在被高频读取和更新,说明它是热数据,放远端存储不划算。比如交易系统的主数据库,要保证强一致性和低延迟,它就不适合做存算分离。但交易库产生的历史流水、日志、归档数据,放到对象存储里做离线和分析,就非常合适。判断热冷分布的一个简单标准是:这份数据如果超过30天没有被秒级访问的需求,就可以往低成本存储层放。
4.3 你的成本结构更偏向存储还是计算?
存算分离的价值体现在"存储便宜、计算弹性"。如果你的业务成本大头在计算,而存储数据量并不大,那么存算分离的收益就不明显。反过来,如果你大部分成本都花在了存储副本和维护存储节点上,那迁移到对象存储的收益会非常可观。这个判断要做量化,别拍脑袋。把当前的存储成本、计算成本、闲置资源消耗都算一遍,再对比迁移后的资源账单,数字会告诉你答案。
4.4 你的团队有没有能力处理网络和元数据的问题?
存算分离对网络带宽、元数据服务、缓存体系的要求比存算一体高。小团队如果运维能力有限,我更推荐直接使用云上托管的产品,减少自建带来的复杂度。自建Alluxio这类缓存层虽然灵活,但意味着你要多运维一套分布式系统,它的稳定性一样需要人负责。凡是多一套系统,就多一份故障面,这个账也要算进去。
5. 存算分离的下一步:影响范围比想象中更大
最后聊一聊我对这个趋势走向的观察。存算分离不会停留在"存储和计算分开"这个层面,它正在改变整个大数据技术栈的形态。
对象存储本身在快速进化,比如冷热数据分层、生命周期管理、智能缓存等能力越来越成熟。过去对象存储主要被认为是归档层,但现在很多平台已经把对象存储作为主存储来用,配合高性能缓存层做到接近本地存储的访问体验。这个演进对存算分离架构的落地非常关键,因为它直接拉高了"远端读"的性能上限。
Lakehouse(湖仓一体)的兴起也是存算分离思想的一种自然延伸。湖仓一体的核心诉求就是"一份数据,多种引擎共享",没有存算分离,这个诉求基本无法实现。所以你会看到主流的湖仓格式(Iceberg、Hudi、Delta Lake)在设计上都天然支持对象存储作为底层存储,表的元数据独立于文件存储,计算引擎随便换。从这个角度看,存算分离已经成了数据平台架构的一个默认前提,而不是可选项。
另一个明显方向是Serverless化。计算层可以做到按需拉起、按量计费,存储层独立存在,这其实就是存算分离在云原生时代的最终形态。用户不再关心集群有多大、节点有多少,只管写SQL和交钱。我见过不少公司已经在用这种方式跑临时分析任务,用的时候创建、不用的时候销毁,成本核算清晰到每次查询。
如果你正在准备大数据相关的毕设或者面试,我建议多关注这类真实架构决策背后的细节。面试官问存算分离,不是想听你背概念,而是想看你能不能说出"什么场景适合、什么场景不适合、网络开销怎么控制、元数据性能怎么解决"这些实操层面的东西。毕设如果能做一个简单的存算分离Demo,比如用对象存储存数据、用容器化方式拉起Spark集群分析同一份数据,并从成本和性能两个维度做对比,这种项目放在简历上是很有说服力的。
最后分享一个实用的计算方式,你可以在选型时直接套用:估算一下每年花在存储和计算上的总成本,然后把数据增长率和业务峰值波动周期标出来。如果数据增长率明显大于计算增长率,或者计算波动幅度很大,那么存算分离大概率值得做。我用这个简单的判断帮好几个团队做了架构上的取舍,方向基本没跑偏过。