从 HDFS 到对象存储:计算存储分离如何重塑云原生大数据底座
最近几年,我明显感受到身边越来越多团队在讨论一个话题:能不能把 Hadoop 里的 HDFS 干掉?做大数据平台的同学都知道,HDFS 这套存储从诞生到现在陪我们打了十多年仗,稳定、可靠、生态完善,可一旦碰上云原生改造、容器化调度、资源弹性伸缩这些新需求,它就开始显得笨重了。我自己在带着团队把一套基于 CDH 的大数据平台迁到 Kubernetes 上的过程中,踩了一堆坑,也把 HDFS 的读写链路、元数据机制、扩容方式翻来覆去想了个透。今天这篇就围绕"从 HDFS 到对象存储、计算存储分离"这条主线,把技术选型、迁移实操、架构改造和个人踩坑记录一并分享出来。
先说一个核心判断:对象存储不是来替代 HDFS 的,它是来替我们卸下存储负担的。HDFS 擅长的是"大文件一次性写入、流式读取、吞吐优先",但它把数据副本散落在计算节点本地磁盘上,导致计算和存储被牢牢绑死。对象存储则把数据统一放在远端、按桶组织、通过 HTTP 协议访问,天然适合云原生环境里"计算和存储各自独立扩缩容"的诉求。这篇文章适合正在做大数据平台云原生改造的架构师、运维工程师,也包括刚接触大数据生态、想搞清楚 HDFS 读写流程和对象存储差异的初学者。
- 为什么 HDFS 成了云原生路上的阻力
1.1 HDFS 的架构本质决定了它的边界
理解 HDFS 为什么在云原生场景下不好使,得先回到它的设计初衷。HDFS 是模仿 Google GFS 实现的分布式文件系统,核心是 NameNode 管理元数据、DataNode 存数据块,一个文件被切成若干 128MB 或 256MB 的 block,每个 block 默认三副本,分布在不同的 DataNode 上。写数据时客户端向 NameNode 申请 block 位置列表,再按 pipeline 方式把数据依次写入几个 DataNode。读数据时客户端同样先问 NameNode 拿到 block 的位置,再就近读取。
这个设计在物理机时代非常合理,因为那时候网络带宽宝贵、本地磁盘便宜,把计算挪到数据旁边能大幅减少网络传输。但随着集群规模变大,问题也来了:NameNode 要维护全量文件、目录和 block 的映射关系,元数据只能放在内存里。单台 NameNode 的内存上限,基本就决定了整个集群能存多少文件。我们之前有个集群,NameNode 堆内存调到 64GB,Inode 数量还是逼近千万级别,每天凌晨跑全量同步任务的时候,NameNode 的 GC 停顿肉眼可见,直接拖慢所有 RPC 请求。
1.2 计算与存储耦合带来了一大堆连锁反应
HDFS 把数据存在 DataNode 的本地磁盘上,这个"本地"就是问题的根源。集群扩容的时候,你不可能只加计算不存盘,也不可能只扩存储不加 CPU,因为每一台节点上既有磁盘又有计算资源。业务高峰来了想快速扩充计算能力,结果新加的节点没有数据,任务调度上去之后还是得从老节点跨网络拉数据,性能自然打折。
这种耦合还影响到了 Kubernetes 化改造。在大数据平台容器化的过程中,我们想用 K8s 的调度能力动态申请 Pod 跑 Spark 或 Flink 作业,但 HDFS 要求计算节点和 DataNode 网络拓扑尽量近,否则数据本地性变成空话。一旦 Pod 被调度到没有 DataNode 的节点上,Spark 的 Task 只能远程读数据,吞吐量直线下降。我们实测过,远程读和本地读的吞吐差距能到 3 到 5 倍。这个数字直接浇灭了很多团队"无脑容器化"的热情。
还有一个容易忽视的点:数据副本机制在云上变成了浪费。云厂商的磁盘本身已经是三副本存储了,你在云主机上用 HDFS 再写三副本,等于底层存了三份、上层又存了三份,存储成本乘了九倍。这不是夸张,是实际的账单教育了我们——存 1TB 数据,最终计费的其实是 9TB 的空间成本。
1.3 小文件问题是压垮 NameNode 的最后一根稻草
HDFS 对小文件极不友好,这是所有大数据老兵的共识。文件小于 block 大小不会占满一个 block 的空间,但会在 NameNode 内存里占一条完整元数据记录。比如你有 1 亿个小文件,NameNode 至少要管理 1 亿条 Inode 和对应的 block 映射,光元数据就得几十 GB 内存。而且 Spark 写分区数据时,如果每批次生成的文件特别多,NameNode 的 RPC 处理能力会被打满,出现大量"NameNode is in safe mode"或者 RPC 超时。
我们用对象存储之后,小文件的处理逻辑彻底变了。对象存储没有 Inode 概念,文件即对象,桶里的对象数量可以做到海量,元数据压力不在客户端侧的目录树上,而在服务端的索引里。S3 官方宣称单桶对象数无上限,虽然实际会有一些性能拐点,但比 HDFS 那个内存上限宽松太多了。做一个不算严谨但直观的对比:HDFS 单集群文件数在千万级别就需要非常小心地调优,而对象存储上亿对象仍然可以靠分页列举正常管理。
- 对象存储凭什么能扛起大数据底座
2.1 从文件系统的思维转变成"桶+对象"的思维
刚开始接触对象存储的人最容易犯一个错:还把它当文件系统用。对象存储没有目录树,所谓的"目录"只是对象 key 里的公共前缀。比如你上传一个对象,key 是logs/2025/06/01/app.log,在对象存储的底层实现里,它就是一个完整的字符串 key,并没有真正的logs目录实体。你可以列举前缀为logs/2025/06/的所有对象,但你不可以像在 HDFS 里那样对一个目录执行 rename 或 chmod。
这个差异看起来小,影响却很大。Hadoop 生态的很多组件默认依赖文件系统的 rename、list、mkdir 等语义,比如 Spark 的_temporary目录机制、Hive 的分区目录切换、Flink 的 checkpoint 恢复。迁到对象存储之后,这些操作要么走服务端的 copy 接口,要么需要在业务代码里规避。以 OZONE 或者 S3A Connector 的实现来看,社区已经做了不少适配,但你在使用时还是得心中有数:哪些操作是廉价的,哪些操作会触发真实的网络拷贝。
对象存储的另一个核心特点是读写模型简单:PUT、GET、DELETE、LIST。没有 append 操作,没有随机写,对象一旦写入就是不可变的。要更新一个对象,本质上是重新上传一个同名对象覆盖它。这和大数据场景里常见的"写一次、读多次"模式天然匹配,反而规避了 HDFS 在并发写、文件锁上的复杂性。
2.2 S3 协议成了事实标准,各厂商的兼容性百花齐放
对象存储的协议实现中,AWS S3 协议算得上行业事实标准。国内云厂商的 OSS、COS、OBS 也都兼容 S3 的大部分接口,同时提供自己特有的 SDK 和扩展头。这个局面带来了一个好处:我们可以基于 S3 协议做一套统一的数据访问层,底层存储根据成本、合规、性能需求灵活切换。之前我们用过 MinIO 做私有化部署,也用过公有云的对象存储,切换时基本只需要改 endpoint 和认证信息,业务代码无感。
当然兼容性不是百分之百完美的。以 Hadoop 生态接入为例,S3A Connector 是 Apache Hadoop 官方提供的访问 S3 协议存储的客户端,它支持s3a://bucket/path这种 URI。但在处理目录标记(directory marker)、MPU 分片上传、ETag 弱一致性校验等方面,不同对象存储的细节处理会有差异。MinIO 对 S3 的兼容度很高,但它在 list 性能上对极深前缀的支持不如公有云服务好;公有云对象存储大多没有 list 延迟焦点的烦恼,但可能会在请求频率上做限流。选型时要结合自己的访问模式,不要只看"兼容 S3"这几个字。
2.3 数据本地性不再重要,计算拓扑回归简单
对象存储对计算侧最大的解脱是:数据在哪里已经不重要了。所有计算节点通过同一个 endpoint 访问数据,吞吐量不再依赖节点间的物理距离,而是取决于网络的带宽和延迟。这意味着 Spark、Flink 跑在任何一台有网络可达性的节点上,性能表现是一致的。
这一点对 Kubernetes 环境尤其重要。你可以放心地把 Spark Driver 和 Executor 调度到任意 Pod 上,不用再关心这个 Pod 是否和 DataNode 在同一个物理机架。我们也因此在 K8s 上使用 Spark Operator 时,可以大胆地设置 executor 的 request 和 limit,让它随意漂移而不影响数据读取性能。计算存储分离之后,"任务跟着数据跑"变成了"数据在远处等着计算来取",虽然单次读的网络开销大了,但整体调度能力和资源利用率显著提升。在大规模批处理场景中,实测作业耗时并没有明显变长,很多任务反而因为资源充足、并行度拉满而变得更快。
- 迁移实操:从 HDFS 到对象存储的完整路径
3.1 盘点现状:到底有什么、该迁什么、先迁什么
迁移的第一步不是摸工具,而是摸数据。我们当时对集群里的所有目录做了分层盘点,按数据的重要程度和访问频率把数据分为三类:
- 核心资产数据:包括经过 ETL 加工的主题表数据、用户画像标签、风控特征数据等,这类数据是业务的命脉,必须保证迁移零丢失且要做到新旧双写一段时间的校验。
- 中间临时数据:比如 SQL 任务里的临时表、Spark 作业的 shuffle 中间结果、测试环境的临时文件,这类数据可以大胆放弃,直接重跑任务生成,不用迁。
- 日志和原始数据:埋点日志、业务原始表这类数据体量大、访问频率低,适合做生命周期管理,可以先迁到低频存储甚至归档存储里。
我们最终的策略是:临时数据一律不迁,跑任务时自动重新生成;核心数据搬迁移;原始日志数据采用先双写、再批量回填的方式处理。这个分类思路帮我避免了很多无效迁移工作。你得记住一个原则:迁移的最终目标是让业务正常跑,不是为了把 HDFS 上的所有字节都搬到新地方。
3.2 工具选型:distcp 为主、自研脚本为辅
HDFS 到对象存储的迁移工具,第一选择永远是 distcp。它是 Hadoop 自带的分布式拷贝工具,天然支持在集群内发起 MapReduce 作业来完成海量数据转移。使用 distcp 迁到 S3 或 OSS 时,基本原理是:在集群的一个节点上提交一个 MapReduce 作业,让多个 mapper 同时读取源 HDFS 上的文件,然后写入到目标对象存储的 bucket 里。
我推荐的核心命令大致长这样:
hadoop distcp \ -Dfs.s3a.access.key=your_access_key \ -Dfs.s3a.secret.key=your_secret_key \ -Dfs.s3a.endpoint=oss-cn-hangzhou.aliyuncs.com \ -Dfs.s3a.path.style.access=true \ -Dfs.s3a.connection.maximum=1000 \ -Dfs.s3a.multipart.size=128M \ -Dfs.s3a.fast.upload.buffer=disk \ -Dfs.s3a.threads.max=32 \ -DskipCRC \ -m 200 \ -pugp \ /data/core/* s3a://your-bucket/data/core/几个参数我要特别解释一下。-m 200表示同时跑 200 个 mapper,这个数字要根据集群可用资源来定,太大容易把集群资源打满影响线上业务,太小则迁移速度慢。-pugp表示保留用户、组和权限信息,这个对 Hive 和 Ranger 权限模型很重要。我一开始漏了这个参数,迁完后发现所有文件的 owner 都变成了跑 distcp 的用户,下游任务读取权限报错排查了半天。
另一个重要的参数是-Dfs.s3a.fast.upload.buffer=disk。对象存储的上传走的是 multipart upload,先把数据写到本地临时缓冲再分批传到远端。如果你的文件很大,缓冲模式用 memory 容易 OOM,改成 disk 会更稳。我们始终强调:迁移不是使劲调并发就能提速的,瓶颈往往在源端 NameNode 的 RPC 能力、目标端对象存储的单桶 QPS 上限、还有你所在网络的带宽。合理的做法是先小规模试跑,用 20 个 mapper 测出基准速度,再逐步加大。
3.3 元数据迁移和权限体系迁移
文件数据本身只是一部分,还有 Hive 表的元数据、HDFS 上的 ACL 权限、目录的 quota 配额这些"看不到的数据"要处理。Hive 表迁移的场景下,我们是在新集群上重建了 Hive 表结构,把 location 指向新的s3a://地址,然后用msck repair table或者直接跑一遍MSCK REPAIR TABLE table_name同步分区。如果分区特别多,用msck会比较慢,也可以自己写一个并发脚本扫描分区目录去批量添加分区。
权限这块要注意,对象存储的权限模型和 HDFS POSIX 权限模型完全不一样。HDFS 用 user/group/other 加 ACL,对象存储用 bucket policy 和 IAM 角色。我们当时对接 Ranger 做统一权限管理,通过 Ranger 的 S3 插件把 Hive 层面的表权限映射到底层 S3 路径的访问控制。如果你没有这套体系,迁完后要尽快在 OSS/COS 侧配置好 bucket 的访问控制策略,不然容易出现"数据都在、但谁都不敢动"的管理真空。
3.4 双跑验证:老平台和新平台并存一个周期
数据迁移完之后直接切流量是危险的。我们采用的做法是双跑验证,周期为两周。期间,旧的 HDFS 集群继续保持线上运行,新的对象存储集群作为影子链路同步跑关键作业。每天对比两份数据的表行数、关键字段的和值、分区数量,确认完全一致后才逐步切流量。
有同学会问:双跑是不是等于迁移期间要维护两套资源?对,成本确实会增加,但这是最稳妥的过渡方式。我们没有做"先迁完马上拆旧集群"这种激进操作,而是让新平台先承担非核心报表任务,等这些任务连续一周无差错后,再把核心任务切过来,最后才对旧集群进行下线操作。这个过程里,HiveServer2 的配置从指向 HDFS 的 location 平滑切换到指向对象存储的 location,Spark 作业里的输入路径也一样,靠的是配置中心统一管理,而不是逐个作业手工改。
- 云原生底座改造:计算存储分离的架构实践
4.1 从"数据不动计算动"到"计算随意动"
传统大数据集群里,Spark、Hive、Flink 跑在固定节点上,数据存在同一个节点的磁盘里。计算存储分离后,数据处理引擎和数据存储之间变成了纯粹的"网络上的服务调用"关系。这给基础设施层的改造带来了巨大的自由度。
我们的新架构里,底层是 K8s 集群,上面用 Spark Operator 管理 SparkApplication,用 Flink Kubernetes Operator 管理 FlinkDeployment。作业需要的数据全部从对象存储读取,计算资源池可以按业务优先级拆分,高峰期扩到几十个 Pod,低峰期缩到个位数,完全由 HPA 和自定义调度策略控制。这么做的一个副产品是:大数据平台的"环境"概念被弱化了。以前区分开发、测试、生产环境的成本很高,因为每个环境都要一套 HDFS。现在只用不同的 bucket 或不同前缀即可,计算资源是共享的,环境隔离成本几乎为零。
4.2 访问协议的选择:S3A 与 OSS 原生的取舍
在实际接入层设计上,我们遇到了一个选择:用 Hadoop 社区通用的 S3A Connector,还是用云厂商提供的高性能 SDK 如 Aliyun OSS SDK for Hadoop、腾讯云 CHDFS 协议等。
这里有一个很现实的考虑。S3A 的优点是生态通用性强,代码里写s3a://前缀,将来从一个云厂商迁到另一个云厂商,或者切换到 MinIO,改动成本低。但它也有弱点,就是某些性能优化可能不如云厂商自有的 SDK 激进。我们用 OSS 的场景里,如果走 S3A 兼容协议,遇到大量小文件高并发上传的场景,性能会比原生的 OSS SDK 差一些。反过来,如果你用的是云厂商 SDK,将来被绑定在某个特定云上的概率就增加了。
我们的折中方案是:所有数据写入走统一的 Data Hub 服务,该服务底层根据目标 bucket 的类型自动选择是否使用原生 SDK;而离线分析读路径统一走 S3A/OSS 的 Hadoop Connector。这样既保证写数据的性能,又保留读路径的统一性。如果你是一个小团队,没有那么多人手去维护多层抽象,直接用 HDFS 的s3a方案就够了,性能差异在多数离线场景下可以接受。
4.3 Spark 和 Flink 在对象存储上的性能调优
Spark 跑在对象存储上,第一个要调的是输出文件的提交方式。我强烈建议开启spark.sql.sources.commitProtocolClass=org.apache.spark.sql.execution.datasources.SQLHadoopMapReduceCommitProtocol的替代方案,或者用社区针对对象存储做的Iceberg、Hudi这类数据湖框架。直接用 Spark 默认的 FileOutputCommitter 在对象存储上会极其痛苦,因为它的 task commit 依赖 rename 操作,而对象存储的 rename 是 copy+delete,代价高昂。
我们在测试中对比过,同样的 TPC-DS 查询,在 HDFS 上跑和对象存储上跑,最核心的差距不在 scan 阶段,而在 shuffle 和 commit 阶段。shuffle 的中间数据默认写在本地磁盘这个问题不大,但 final task 写数据到对象存储时,如果任务数量特别多、并发 commit,对象存储的请求数会暴增,容易出现限流。解决办法是开启 Spark 3.x 引入的spark.sql.adaptive.enabled和动态 coalesce,让最终生成的输出文件数不要太多。比如目标文件大小控制在 256MB 到 512MB,一个 1TB 的表最终输出 2000 到 4000 个文件,commit 的压力就小得多。
Flink 的适配也有讲究。Flink 的 StreamingFileSink 和 FileSink 默认会写临时文件再在 checkpoint 完成时 rename 到最终路径。在对象存储上,这个 rename 变成了对远端对象的拷贝,频繁 checkpoint 时开销不小。我们的经验是稍微调大 checkpoint 间隔,比如从 1 分钟改成 5 分钟,并且开启 checkpoint 的 incremental 模式,避免每次快照全量提交。如果你用的是 Flink + Iceberg,那 Iceberg 本身会管理数据文件的可见性,Flink 只负责写数据文件,commit 由 Iceberg 的 metatable 控制,这样会舒服很多。
4.4 数据湖方案成了自然的演进方向
对象存储之上最顺理成章的大数据底座,就是数据湖方案。用 Iceberg 或 Hudi 这类表格式,在对象存储上维护一张支持 ACID 的表,同时兼容 Spark、Flink、Presto/Trino 的查询。这里不再有 HDFS 目录的"物理分区"概念,而是通过表的 manifest 文件管理数据文件列表和快照。
换句话说,HDFS 时代目录结构即元数据,分区目录需要手动维护和 repair;而在 Iceberg 表里,分区是一个隐藏的字段,写入时自动根据分区 spec 生成数据文件,并记录到 manifest 中。这大大减轻了运维负担,比如之前几天就要跑一次MSCK REPAIR TABLE的场景不复存在了。这也是我特别推荐迁移对象存储时顺势引入数据湖表格式的原因。
- 常见问题与排查技巧实录
5.1 认证和权限问题
迁移刚开始时我们遇到最多的是认证错误。HDFS 用 Kerberos,对象存储用 AK/SK 或临时 STS Token,Spark 作业提交到 YARN 上再转到 K8s 上,各种凭证传递链路非常容易弄错。常见错误是The AWS Access Key Id you provided does not exist,排查时可以按这个顺序检查:core-site.xml 里的fs.s3a.access.key和fs.s3a.secret.key是否正确;环境变量是否被某个进程额外覆盖;提交作业的机器上是否有~/.aws/credentials干扰;是否配置了fs.s3a.aws.credentials.provider,例如org.apache.hadoop.fs.s3a.TemporaryAWSCredentialsProvider时,还需要额外携带 session token。
5.2 小文件还是无处不在
对象存储对大文件友好,但如果在写入时还是沿用 HDFS 时代那种"每个 task 各自写一堆小文件"的习惯,对象存储的性能同样会崩。我们踩过的坑是:某张 ODS 表由 5000 个 task 并发写,每 task 写 1 到 2 个小文件,结果下游一次性 list 和读取时就出现严重的延迟,因为每个文件都要发一个 GET list 请求。后来我们强制在写入层做了合并策略,加一个REPARTITION(200)或者COALESCE(50),文件数立刻降下来,查询性能翻倍。
清理小文件的工具可以自己写,也可以依赖 Iceberg 的rewrite_data_files动作。Apache Iceberg 对文件数过多的情况可以用spark.action.rewriteDataFiles触发数据文件合并,这比我们自己写一个 scan-and-copy 脚本靠谱得多。
5.3 distcp 迁移中断后的续跑
数据量一大,distcp 作业总会因为网络、源端 NameNode 抖动、目标端限流等原因失败。好在 distcp 支持断点续跑,同一个命令加一个-update参数就能跳过已拷贝且大小一致的文件。
hadoop distcp -update -append ...这个组合经常被误解。-update会检查源文件和目标文件的大小,不一致才重新拷贝;-append则是把源端新增的 block 追加到目标文件后面。但对象存储不支持 append 语义,所以-append对 S3A 目标端没有意义。我们实际操作时一般只加-update和-delete,先同步新增文件,删除目标端多余的文件,保证两边完全一致。这里要小心-delete的用法,它会删除目标端存在但源端不存在的文件,如果你目标端还有别的并发写入的数据文件,会被一并删掉,所以只在确认目标端没有其他写入时使用。
5.4 对象存储上的"目录"和 Hive 分区发现
对象存储的 list 操作是按前缀扫描的。Hive 在查询分区表时,Metastore 里的分区信息是独立的,不会主动列举目录;但如果你用 Spark 的insert into新建分区后忘了更新 Metastore,查询就会看不到新分区。解决办法有三种:一是跑msck repair table,二是在 Spark 写完后手动spark.sql("alter table xx add partition(...)"),三是使用 Iceberg 这类自带元数据管理的表格式,让分区管理自动化。
5.5 请求频率限制与成本意识
几乎所有的公有云对象存储都会对单桶的 QPS 做限制。比如一个 bucket 的 GET 请求限制可能在某些规格下是几千 QPS,看起来很多,但如果你在 Spark 里开了 200 个 executor,每个 executor 同时读 100 个文件,瞬间就打满了。解决方法是尽量让读取操作本地化使用大的 split,减少小请求数量。比如用 Spark 读 Parquet 时,把spark.sql.files.maxPartitionBytes调大到 1GB 或更多,避免一个文件被拆成七八个 partition 分别发请求。
成本也是一笔账。对象存储的请求费用虽然单价低,但海量小文件场景下请求费会占比很高。我们有一次某张表月请求量破亿,请求费占了存储总费用的三成。优化方案还是回到了合并文件、减少文件数这条路上。所以无论你是从 HDFS 迁移到对象存储,还是直接新建对象存储计算底座,都要记住一个核心原则:对象存储的数据布局,要以"少对象、大对象"为美。
- 迁移后的日常运维与架构演进心得
迁完之后,日常运维的维度也发生了变化。HDFS 时代,运维最关注的是 NameNode 堆内存使用率、DataNode 磁盘水位、block 副本健康状态、机架感知配置;对象存储时代,这些都不需要操心了,但这些能力变成了云厂商的 SLA 范围。反而新的关注点出现了:bucket 的生命周期策略、访问日志分析、跨区域复制、数据加密和合规审计。
生命周期管理是非常实用的一个功能。我们给不同的 bucket 设置了不同的规则,比如日志类数据 30 天后自动转低频存储,180 天后转归档存储,一年后自动删除。这套规则在 HDFS 时代实现起来要自己写定时任务扫描目录、调用 distcp 搬到冷节点或者直接 rm,稍微复杂和容易出错。对象存储把这些全部变成配置项,节省了大量人力。
容灾备份的思路也变了。以前 HDFS 做异地容灾,要搭一套 DistCp 定时同步任务,偶尔还要担心 Snapshot 机制和副本的完整性。对象存储天然支持跨区域复制,配置好源 bucket 和目标 bucket 的复制规则,数据就自动同步到异地。这个能力在大数据平台容灾架构中是加分项,但对 RPO 的要求要提前评估清楚,因为跨区域复制是异步的,有秒级到分钟级的延迟。
另外,在架构层面,如果你正打算从 HDFS 迁到对象存储,我建议你把目光放远一点:迁移本身不是终点,而是数据平台重塑的起点。把 Hive 表慢慢过渡到 Iceberg 表,把 Spark/Flink 作业都通过 K8s 动态调度起来,把数据权限全部收口到统一认证中心,这些才是真正意义上的云原生大数据底座。
我个人在实际操作中最大的体会是:技术选型和迁移方案不能追求一步到位,更不能照搬别人的架构图。每个团队的数据规模、业务特征、人员熟悉度都不一样。比如我们团队对 Hadoop 生态很熟,所以先走 distcp 迁移再谈表格式改造;而有的团队从零起步,直接在对象存储上用 Iceberg 建数仓,绕开了 HDFS 的重担。两种路径没有优劣之分,关键是搞清楚自己的存量包袱和增量诉求。
最后再分享一个小技巧:无论用什么工具做数据迁移,都建议写一份详细的迁移检查清单,把每一批次的数据表、文件数、总大小、校验结果、切换时间点记录清楚。我们当时用这套清单保证了上百张表大迁移过程零事故,排查问题时也能快速定位是哪个批次出了问题。迁移这种事,准备得越细,运行得越稳。