分布式计算集群搭建全指南:从Hadoop到Spark/Flink的部署实践
2026/9/7 18:31:55 网站建设 项目流程

在大数据领域摸爬滚打这几年,最常被问的一句话就是:“我要搭一套分布式计算的集群,该从哪下手?”问这话的人,一部分刚看完官方文档,WordCount 在伪分布式里刚跑通;另一部分干脆已经买好了三台服务器,但系统都没装;还有一小部分更惨——在虚拟机里把“集群”搭了三遍,一问才发现,三台机器上跑的是三个互相独立的伪分布式。

我可以把这类人的下一步踩的坑提前预演出来:不是把伪分布式重复装了三遍,就是在 HA 配置上纠结两天,或者被 YARN 的内存参数搞到任务连环 OOM。这篇就是给这些同学的一份完整实操参考,覆盖 Hadoop 3.x 集群、Spark on YARN、Flink on YARN 以及 K8s 部署策略,尽量把参数和架构选择背后的推理过程也写出来,而不是丢给你一份“照着抄”的配置清单。

1. 建集群前绕不开的四个灵魂拷问

1.1 你的数据规模配得上几台机器

很多人一开口就是“我要搭一个大数据集群”,但问他“准备跑多大体量的数据”,回答往往是“不知道,先搭起来再说”。这是最大的隐患——集群规模是设计出来的,不是拍脑袋拍出来的,也不是“越多越好”堆出来的。

我自己习惯用一套很简单的方法做粗算。假设你的业务每天新增 2TB 原始日志,保留 30 天做分析,那原始数据就是 60TB。接着乘两个系数:压缩比和副本数。日志类数据用 Snappy 或 Zstandard 压缩,压到原来的 1/3 很常见,也就是 20TB;HDFS 默认三副本,实际占用的物理空间就是 60TB。算完之后再看单节点可用存储,比如一台机器满配 12TB 硬盘,格式化、操作系统、预留缓冲之后可用空间大约 80%,也就是 9.6TB。60TB 除以 9.6TB,至少需要 7 个数据节点。

再补一个约束:数据节点太少,三副本的性能优势根本发挥不出来,所以低于 3 个 DataNode 的“完全分布式”更多是学习用途。如果是测试环境,规模按生产环境的 1/10 到 1/20 砍都行,但角色划分必须和生产保持一致,否则后面迁移到生产时还得重新踩一遍配置的坑。

1.2 伪分布式、完全分布式和高可用到底差在哪

这三者之间的区别是我面试里必问的基础题,也是很多人最初级的概念混淆点。

  • 伪分布式:一台机器上同时起 NameNode、DataNode、ResourceManager、NodeManager,所有进程都在一个 JVM 里或几个 JVM 里跑。它只是为了让你体验“配置文件生效”和“提交任务走流程”,不代表任何分布式能力。
  • 完全分布式:至少 3 台机器,不同角色分散到不同节点。此时 HDFS 的数据块才真正做到了跨节点存储,但 NameNode 仍然只有一个,它挂了整个文件系统就不可用。
  • 高可用(HA):在完全分布式基础上,部署 Active/Standby 两个 NameNode,配合 ZooKeeper 做自动故障转移,同时引入 JournalNode 同步元数据。这是生产环境的最低底线。

从完全分布式到 HA,不是简单“多配一个 NameNode”而已。涉及dfs.nameservicesdfs.ha.namenodesdfs.namenode.shared.edits.dir这些配置项,还要规划 JournalNode 的部署位置。很多教程带你把 HA 搭起来了,但从来没说清楚 JournalNode 为什么要奇数台——其实是为了满足基于多数派投票的一致性协议,3 台里挂 1 台还能继续服务,挂 2 台就脑裂风险极高了。

1.3 硬件选型:为什么堆 CPU 不如堆内存

大多数初学者选机器时,眼光全盯在 CPU 核数和 SSD 上,反而忽视了内存带宽。做分布式计算的集群,内存的重要性往往高于 CPU。

以 Spark 为例,一个 Executor 的 JVM 堆内内存加上堆外内存,动辄就是 4GB 起步;跑 Join 类的 Shuffle 操作时,内存不足会直接导致磁盘溢写,性能断崖式下降。这些开销不是靠 CPU 主频能补回来的。业界比较均衡的配比大约是:每 2 个物理核搭配 8GB~16GB 内存。如果是 YARN 节点,单机 64GB 内存配 16 核是比较舒服的起点,因为在 YARN 里还要给操作系统和外围服务留 10%~20% 的余量。

磁盘方面,我更推荐“多块 HDD + 一块 SSD 做系统盘”的方案,而不是全上 SSD。HDFS 顺序读写为主,机械硬盘在顺序场景下性价比很高;SSD 留给 NameNode 的元数据目录、ZooKeeper 的事务日志这类高随机 IO 场景,收益更明显。

1.4 先画一张部署拓扑图再动手

动手装系统之前,我强烈建议先画一张角色分配表。下面是我给一个典型 10 节点学习/测试集群做的分配,可以直接参考:

节点角色说明
hadoop01NameNode(Active), ResourceManager, ZooKeeper, JournalNode, Flink JobManager主控节点
hadoop02NameNode(Standby), ZooKeeper, JournalNode备主控节点
hadoop03ZooKeeper, JournalNode协调节点
hadoop04~hadoop10DataNode, NodeManager, Flink TaskManager数据与计算节点

注意一个原则:NameNode、ResourceManager、Flink JobManager 这类“领导角色”尽量不要和 DataNode 混部在同一个节点上。混部短期看省机器,长期看问题很多——数据节点 IO 占满时,NameNode 的 RPC 响应会被拖慢;ResourceManager 在做资源调度时也会受到其他进程干扰。

2. 底线工程:操作系统、JDK 与网络的三件套配置

2.1 系统版本和 JDK 版本的一个稳定组合

我见过太多人在集群没跑起来之前,先在装哪套系统、选哪个 JDK 版本上吵了半天。其实这里根本没有最优解,只有“已验证过”的组合。

到目前为止,我遇到最稳的组合还是 CentOS 7.x / Rocky Linux 8.x 配 OpenJDK 8。Hadoop 3.x 官方文档明确支持 Java 8 和 Java 11,Spark 3.x 也完全兼容 Java 8。虽然 Java 17 已经是很成熟的版本,但在一些老的 Hive 版本、Hadoop 生态组件里依然会碰到反射权限或者模块化限制的问题。

在生产环境里,团队如果已经统一用容器镜像管理运行时,那 JDK 版本可以更大胆一些;但如果你是在裸机上手工搭建,听我一句劝,用 OpenJDK 8,省下的时间够你多排查好几个问题。

系统层面需要关掉的两个东西:防火墙和 SELinux。在大数据集群的内部网络里,NameNode 的 9870 端口、DataNode 的 9864 端口、ResourceManager 的 8088 端口彼此都要互通,如果防火墙策略没有完全对齐,你会看到“节点明明活着但集群里看不到它”这种玄学问题。测试环境直接关掉,生产环境则需要把端口清单拉精准。

2.2 SSH 免密和主机名映射的细节

搭建 Hadoop 集群时,SSH 免密登录是必须配置的,否则你无法用start-dfs.sh一键启动所有节点上的进程。

具体操作很简单,三条命令:

# 在 hadoop01 上生成密钥对 ssh-keygen -t rsa -b 4096 -P '' -f ~/.ssh/id_rsa # 把公钥分发到所有节点 ssh-copy-id hadoop01 ssh-copy-id hadoop02 ssh-copy-id hadoop03 # ... 其余节点同理

这里有一个几乎所有教程都不提的坑:必须同时配置 hosts 文件,并且集群里的每台机器都要保持一致。如果你在每台机器上用的是不同 IP 映射,会出现很诡异的现象——用ssh hadoop02连过去没问题,但 Hadoop 内部进程互相注册时用的是 IP,最后你在 NameNode Web 界面看到的 DataNode 列表里全是裸 IP,排查问题时极为痛苦。

我的习惯是把主机名映射写在/etc/hosts里,同时修改/etc/hostname,然后在每台机器上hostnamectl set-hostname hadoop0X。别偷懒用 IP 直连,后面的日志排查会告诉你为什么主机名这么重要。

还有一个细节:集群时间同步。业务日志的时间戳、HDFS 的文件时间、YARN 的容器启动时间,跨节点不一致时会产生各种“看起来没毛病但就是对不上”的问题。生产环境用 NTP 或 chrony 指向统一时间源,测试环境至少保证手动ntpdate校准一次。

2.3 时钟同步与 swap 关闭为什么是隐性雷区

系统默认开启的 swap 对 Hadoop/Spark 这类内存敏感型组件危害极大。以 Spark Executor 为例,JVM 堆内存被换到磁盘上之后,Full GC 的时间会从毫秒级飙升到秒级,任务超时、心跳丢失接踵而至。更坑的是,这类故障在监控里很难一眼定位,因为 CPU 和内存曲线看起来都没有异常。

关闭 swap 的方法:

# 立即关闭 swapoff -a # 永久关闭,注释掉 /etc/fstab 中 swap 对应的行 # 然后重启验证

关闭之后还要在/etc/sysctl.conf里调整两个内核参数:

# 尽量不触发 swap vm.swappiness = 1 # 文件句柄和线程数上限调高 fs.file-max = 6815744 net.core.somaxconn = 32768

这些参数调整完,不重启不会立即生效,可以执行sysctl -p重载,然后ulimit -n确认一下。这些基础工作做完,集群的“地基”才算是稳了。

3. Hadoop 3.x 集群搭建:一步步把 HDFS 和 YARN 跑起来

3.1 三张核心配置文件的每一项解释

下载 Hadoop 3.3.x 版本后,解压到/opt/hadoop,配置的核心就是etc/hadoop目录下的四类文件:core-site.xmlhdfs-site.xmlyarn-site.xmlmapred-site.xml。我先说前三个。

core-site.xml里最关键的是fs.defaultFS,它决定了整个集群对外暴露的 HDFS 入口地址:

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://hadoop01:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/data/hadoop/tmp</value> </property> </configuration>

注意hadoop.tmp.dir这个参数,很多人的 NameNode 起不来,就是因为默认的/tmp/hadoop-${user}在系统重启后被动过,元数据丢了。自定义一个独立的目录,并且保证它有足够的磁盘空间,这是血的教训换来的经验。

hdfs-site.xml中最重要的三块:NameNode 元数据目录、DataNode 数据目录、副本数:

<configuration> <property> <name>dfs.namenode.name.dir</name> <value>/data/hdfs/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/data/hdfs/data1,/data/hdfs/data2</value> </property> <property> <name>dfs.replication</name> <value>3</value> </property> <property> <name>dfs.namenode.secondary.http-address</name> <value>hadoop02:9868</value> </property> </configuration>

如果 DataNode 上有多个磁盘目录,就用逗号分隔写在dfs.datanode.data.dir里,HDFS 会自动做负载均衡。这里有个细节:不同目录的磁盘容量不一致时,HDFS 不会做智能加权分配,所以最好保证各目录所在磁盘容量接近,否则写满小盘之后整块磁盘上的副本都会变成Under-Replicated

yarn-site.xml是后续跑 Spark/Flink 任务时的核心资源池配置,我给一份相对保守的初始值:

<configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop01</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>49152</value> </property> <property> <name>yarn.nodemanager.resource.cpu-vcores</name> <value>16</value> </property> <property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>1024</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>8192</value> </property> </configuration>

如果单机是 64GB 内存,yarn.nodemanager.resource.memory-mb不要写满 64GB,要留给操作系统和 HDFS 数据节点进程。我按 75% 的比例折算,设成 49152MB,也就是说 YARN 最多能从这个节点上分配出去 48GB 内存。

3.2 格式化 NameNode 的正确时机和禁忌

Hadoop 集群第一次启动之前,必须在 NameNode 上执行格式化操作:

hdfs namenode -format

这一步会清空并重新生成dfs.namenode.name.dir目录下的元数据。问题在于,很多新手在集群出故障后会条件反射般地执行namenode -format,结果把整个文件系统的元数据全部清空,而 DataNode 上还保留着旧的数据块。这样一来,NameNode 里没有任何数据块的映射关系,所有数据等于丢失。

所以必须记住一条铁律:格式化操作只能在集群从未初始化过、或者你明确要“抛弃所有旧数据”的时候执行。生产环境绝不允许在运行中的集群上随手-format。如果集群真的出了元数据问题,优先考虑从dfs.namenode.name.dir中的current目录找回元数据,或者从 SecondaryNameNode/JournalNode 里恢复。

3.3 一键启动脚本与手工启动的取舍

配置好workers文件(Hadoop 3.x 里已经从slaves改名为workers),把 hadoop04~hadoop10 的节点名写进去,然后在 hadoop01 上执行:

start-dfs.sh start-yarn.sh

如果你的 SSH 免密配置没问题,这两个脚本会自动把 DataNode 和 NodeManager 进程拉到所有workers节点上。这时候可以用jps命令在每台机器上检查进程:

  • hadoop01 上应该看到 NameNode、ResourceManager、SecondaryNameNode
  • hadoop02 上应该是 DataNode、NodeManager
  • hadoop03~hadoop10 上都是 DataNode、NodeManager

我个人的习惯是优先用脚本启动,进程挂掉排查时再手工拉起单个进程,这样能更快定位是配置问题还是环境问题。手工启动 DataNode 的命令是hdfs datanode,启动 NodeManager 是yarn nodemanager,注意要在对应节点上执行。

3.4 启动后第一件事:用网页和命令行双重验证

启动完成后,先不要急着提交任务,用两个方式确认集群健康。

网页端:

  • HDFS 管理界面:http://hadoop01:9870,进去后看 Live Nodes 数量是否等于 DataNode 数量,再看 HDFS 剩余空间。
  • YARN 资源管理界面:http://hadoop01:8088,看 Active Nodes 数量,以及每个节点的可用内存和核数。

命令行端:

hdfs dfsadmin -report hdfs dfs -mkdir -p /tmp hdfs dfs -put /opt/hadoop/etc/hadoop/core-site.xml /tmp/ hdfs dfs -cat /tmp/core-site.xml | head -20

如果dfsadmin -report能正常输出各 DataNode 的容量信息,说明 HDFS 数据链路没问题。再用yarn node -list确认 NodeManager 都正常向 ResourceManager 注册了。到这里,一套完全分布式的 Hadoop 集群就基本跑通了。

4. Spark on YARN:让计算资源真正被管起来

4.1 为什么生产环境不推荐 Standalone 模式

Spark 自带的 Standalone 模式非常容易上手,sbin/start-master.shsbin/start-worker.sh一启动就完事。但在生产环境里,我几乎不会推荐它作为主力部署方式,原因有三点:

  • 资源管理能力太弱。Standalone 没有队列概念,也没有多租户隔离,几个人同时提交任务时,资源就是先到先得,没法按业务重要程度动态分配。
  • 与 HDFS 的节点协调性差。YARN 是 Hadoop 生态统一的资源调度层,NodeManager 与 DataNode 同节点部署,可以实现数据本地性调度;Standalone 里的 Worker 和 HDFS 节点之间没有这种内建感知。
  • 运维体系割裂。如果 YARN 已经在管理一批任务,Spark 再单独起一套资源调度体系,监控、告警、队列配额都要两套配置,完全没有必要。

所以更常见的做法是把 Spark 接入 YARN,让 YARN 统一管理所有计算框架的资源。你只需要在spark-env.sh里把HADOOP_CONF_DIR指向 Hadoop 配置目录,Spark 就能自动从 YARN 申请资源了。

4.2 spark-submit 提交任务的参数推导过程

Spark 提交任务时,最让人头疼的是 Executor 数量和内存怎么定。我以一台 YARN 节点 48GB 可用内存、16 核为例,演示一遍推导过程。

假设每个 Executor 分配 4GB 内存、2 核。结合 YARN 的 overhead 机制,实际每个 Executor 在 YARN 里占用的内存要加上spark.executor.memoryOverhead,默认是 executor 内存的 10%,所以实际占用大约是 4.4GB。一个节点可以容下的 Executor 数为48 / 4.4 ≈ 10,但 YARN 还要给 ApplicationMaster 留资源,实际一个节点放 3~4 个 Executor 比较合理,留足余量。

如果集群有 7 个数据节点,每个节点放 3 个 Executor,总共 21 个 Executor,对应的提交命令大致是:

spark-submit \ --master yarn \ --deploy-mode cluster \ --driver-memory 2g \ --executor-memory 4g \ --executor-cores 2 \ --num-executors 18 \ --queue prod \ --conf spark.executor.memoryOverhead=512m \ --class com.example.Main \ /opt/apps/my-job.jar

这里--num-executors我没有写满 21,因为要留出一个 Executor 的余量给任务重试和系统波动。如果你首次跑一个任务,按这个参数能跑通,再逐步调大executor-memory观察 GC 时间,而不是一开始就贪多。

4.3 动态资源分配与队列隔离

Spark 支持动态资源分配,开启之后 Executor 会随任务负载自动伸缩,对多团队共享集群特别有用。在提交命令或spark-defaults.conf中加:

spark.dynamicAllocation.enabled true spark.dynamicAllocation.minExecutors 1 spark.dynamicAllocation.maxExecutors 30 spark.shuffle.service.enabled true

注意,spark.shuffle.service.enabled必须为true,否则 Executor 动态缩容时,已经写出的 Shuffle 数据可能无法被后续任务读取。这个 External Shuffle Service 要在每个 NodeManager 上单独启动,并把spark_shuffle相关 jar 放到 YARN 的 classpath 里。

资源队列隔离这块,生产环境会配置多个 YARN 队列。比如给实时数仓团队一个realtime队列,给离线任务一个batch队列,两者互不挤占。配置在capacity-scheduler.xml里,核心参数就是各队列的容量上限:

<property> <name>yarn.scheduler.capacity.root.queues</name> <value>batch,realtime</value> </property> <property> <name>yarn.scheduler.capacity.root.batch.capacity</name> <value>60</value> </property> <property> <name>yarn.scheduler.capacity.root.realtime.capacity</name> <value>40</value> </property>

调完需要执行yarn rmadmin -refreshQueues刷新队列配置,不用重启 YARN。

5. Flink on YARN 与状态后端:流计算集群的另一种搭法

5.1 Session、Per-Job、Application 三种模式怎么选

Flink 跑在 YARN 上,有三种部署模式,很多人一开始分不清。

  • Session Mode(会话模式):先在 YARN 上启动一个常驻 Flink 集群,后续任务都提交到这个集群上。优点是启动快,资源和集群启动开销可以摊薄到多个任务;缺点是任务之间共享资源,一个任务出问题可能影响整个会话。
  • Per-Job Mode(单任务模式):每个作业独占一个 Flink 集群,资源隔离更干净,作业结束集群也释放。但每次提交都要经历一次完整的集群启动过程,对短任务很不友好。
  • Application Mode(应用模式):是 Per-Job 的进化版,它会把用户代码的main方法放到 JobManager 里执行,避免客户端环境差异带来的问题。Flink 官方目前最推荐的就是这个模式。

在实际生产里,我用得最多的是 Application Mode。提交命令可以写成这样:

flink run-application \ -t yarn-application \ -Djobmanager.memory.process.size=2048m \ -Dtaskmanager.memory.process.size=4096m \ -Dtaskmanager.numberOfTaskSlots=4 \ -Dparallelism.default=8 \ -Dyarn.application.queue=realtime \ -c com.example.StreamJob \ /opt/apps/flink-job.jar

如果只是临时跑一个测试作业,用 Session Mode 会更快,但要记得及时关掉空闲会话,不然它一直占着 YARN 资源不释放。

5.2 RocksDB 状态后端与 Checkpoint 目录规划

Flink 和 Spark 的流处理最本质的区别之一是 Flink 有状态计算。状态后端的选择直接决定了状态存储的容量和性能。

如果状态量很小,几 GB 以内,用 HashMapStateBackend 放在堆内存里,性能最好。但生产环境的实时指标计算、窗口聚合、多流 join,状态量动辄几十 GB 甚至上百 GB,堆内存根本放不下。这时候用 RocksDBStateBackend 是更现实的选择,它把热数据放在内存、冷数据落盘,能支撑大规模状态。

RocksDB 的配置要点是两个参数,一个是状态后端类型,一个是 Checkpoint 存储目录:

state.backend: rocksdb state.checkpoints.dir: hdfs://hadoop01:9000/flink/checkpoints execution.checkpointing.interval: 60000 state.backend.incremental: true

Checkpoint 目录放在 HDFS 上是必须的。很多人图省事用本地路径,结果 TaskManager 宕机重启后,JobManager 根本找不到那台机器上的 Checkpoint 数据,作业只能从头恢复。另外,state.backend.incremental: true这个参数一定要开,它开启增量 Checkpoint,否则每次全量快照对 HDFS 的压力会非常大。

5.3 元数据库 MySQL MGR 在集群中的角色

大数据集群里有个容易被忽略的角色——元数据库。Hive 的 Metastore、Ranger 的审计库、Atlas 的图数据库元数据,甚至 Flink 的 Hive Catalog 都需要一个外部关系型数据库来存元信息。单机 MySQL 一旦宕机,整个集群的分析链路全断。

MySQL 8.0 的 MGR(组复制)是当前比较主流的高可用方案,它不像主从复制那样需要手动 failover,而是通过 Paxos 协议在组成员之间自动选主。搭建过程用 MySQL Shell 可以非常快:

# 在第一个实例上执行 dba.createCluster('mycluster') cluster = dba.getCluster('mycluster') cluster.addInstance('root@mysql02:3306') cluster.addInstance('root@mysql03:3306')

MGR 会自动选出一个主节点,客户端通过 Router 连接到集群时,主节点故障会自动切换到新的主节点。在大数据集群规划里,我通常把 MGR 的三个节点和 ZooKeeper 的三节点放到同一组物理机上,职责上互相隔离,但硬件资源可以复用。

6. 裸机、K8s 还是云主机:部署策略的横向对比

6.1 K8s 调度 Spark/Flink 的真实收益与代价

最近两三年,K8s 上跑 Spark 和 Flink 的呼声越来越高。它带来的收益是实实在在的:资源利用率高、扩缩容快、环境标准化。但代价也实实在在:网络和存储的复杂度被提高了一个量级。

以 Spark on K8s 为例,提交方式变成:

spark-submit \ --master k8s://https://k8s-api.example.com:6443 \ --deploy-mode cluster \ --conf spark.kubernetes.container.image=registry.example.com/spark:v3.4 \ --conf spark.kubernetes.namespace=bigdata \ --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \ --class com.example.Main \ local:///opt/apps/my-job.jar

这里需要先构建一个包含 Spark 运行时和作业 jar 的 Docker 镜像。相比 YARN,K8s 的一个优势是 Shuffle 数据可以借助外部存储(比如对象存储)来实现 Executor 的快速伸缩,不再依赖 External Shuffle Service。

但如果你对 K8s 的网络插件(CNI)、存储插件(CSI)不熟,我建议先在 YARN 上跑半年再说。K8s 下的 Pod 网络、Ingress、PV/PVC 三层网路概念叠在一起,排查“任务偶发超时”“Executor 之间连接失败”这类问题会非常痛苦。

6.2 网络方案和存储方案是容器化最大的两道坎

K8s 部署大数据组件时,最常掉进去的两个坑:

一个是网络方案选型。Flannel 简单,VXLAN 封装性能损耗在 10%~20%;Calico 用 BGP 直连,性能损耗低,但配置复杂,需要底层网络支持。如果是机房内网裸机部署 K8s,我优先推荐 Calico;如果是云主机,很多云厂商已经提供 VPC 原生网络方案,性能更好。

另一个是存储方案。Spark Executor 是临时 Pod,不涉及持久化;但 Flink 的 Checkpoint、HDFS DataNode 的数据目录都涉及持久化存储。用云厂商的云盘,性能稳定但成本高;用自建的 Ceph 或者直接用裸盘做 Local PV,性能好但运维复杂。

我的建议非常简单:第一套 K8s 大数据集群,存储不要走 CSI 动态供应,直接把宿主机目录以 hostPath 的方式挂给 HDFS DataNode 和 Flink TaskManager,跑通之后再逐步迁移到更成熟的存储方案

6.3 一套务实的中小规模集群部署策略

如果让我给一个预算有限的中小团队一个务实的建议,我会这么说:

  • 数据量 100TB 以内、并发任务不超过 50 个,直接裸机或云主机手工搭建 Hadoop + Spark on YARN + Flink on YARN,运维成本最低,能用最少的人数维持稳定。
  • 团队已经有 K8s 运维能力,且对资源利用率要求高,可以把 Spark、Flink 全部容器化,但依然建议保留独立的 HDFS 做存储层,不要急着把 HDFS 也容器化。
  • 如果是云上环境,优先用对象存储替代 HDFS 的冷数据存储,热数据用云盘,计算层再弹性伸缩。这种混搭模式在成本和弹性之间平衡得最好。

7. 集群跑起来之后,真正的坑才开始

7.1 NameNode 被格式化,数据能找回吗

这个场景我亲眼见过两次。一次是同事在排查节点故障时误执行了hdfs namenode -format,另一次是脚本里不小心带了这个操作。格式化之后,NameNode 的current目录被清空,但 DataNode 上的数据块文件依然还在。这时候的救命稻草是:如果曾经配置过 JournalNode 做 HA,或者有 SecondaryNameNode 定期合并的fsimage,马上停止所有写入,从备份里恢复fsimageedits文件,再启动 NameNode。

没有备份的话,基本只能认栽。这也是为什么我反复强调:NameNode 元数据目录所在的磁盘,一定要单独挂载并做定期快照或异地备份。HDFS 数据本身有三副本,但元数据一旦丢,数据副本再多也找不到目录了。

7.2 JVM 堆内内存和 YARN 容器内存打架

提交 Spark 任务后,发现 Executor 被 YARN 直接 kill,日志里报Container killed on request. Exit code is 143,这种问题十有八九是内存超限。

YARN 用 cgroup 约束容器内存上限,如果 JVM 堆内存加上堆外内存超过了容器限制,操作系统会直接杀掉进程。比如给容器分配了 4GB,但spark.executor.memory=4g再加 10% overhead 和堆外内存,总占用可能超过 4GB。解决方法是给 overhead 留足空间:

--conf spark.executor.memoryOverhead=768m

同时容器内存要大于executor-memory + overhead之和。这个参数组合没有万能公式,得结合任务的 Shuffle 量和序列化方式调整。一般来说,堆外开销按堆内存的 15%~20% 留比较稳妥。

7.3 磁盘写入慢与数据倾斜的初步排查

集群跑了一段时间后,会开始出现“某个 Task 特别慢”的情况。如果不是节点硬件问题,优先怀疑数据倾斜。

在 Spark 中定位倾斜很简单:看 Application 界面里某个 Stage 的 Task 耗时分布,如果个别 Task 处理的数据量是平均值的几十倍,基本就是 join 键或 group by 键分布不均。

常见的处理思路有三个:

  • 对热点 key 加随机前缀,把数据打散到多个 Task;
  • 改用 Broadcast Join,把小表广播出去,避免 Shuffle;
  • 调大spark.sql.shuffle.partitions,让分区数量变多,单分区数据量降下来。

Flink 里类似问题表现为某个 Subtask 的 backpressure 持续很高,处理方式通常是调整 keyBy 的设计,或者给状态加 TTL 防止状态无限膨胀。

7.4 日常巡检的三个检查项

集群交付之后,日常巡检建议固定成三个动作:

  • HDFS 健康检查hdfs dfsadmin -report看是否有Under-Replicated块,如果长期不恢复,说明有节点掉线或磁盘空间满了。
  • YARN 资源水位:在 8088 界面看队列负载,如果某个队列长期 90% 以上,要考虑扩容或调队列配额。
  • Flink Checkpoint 稳定性:在 Flink Web UI 的 Checkpoint 页面看最近几小时的成功率和耗时,如果频繁失败,优先看 HDFS 写入性能和 RocksDB 的磁盘占用。

这套检查不需要太复杂,但一定要坚持做。大数据集群很少一夜之间坏掉,绝大多数故障都有前兆,只是没人看日志而已。

集群搭建这件事,做成一次不难,难的是建完以后能让它稳定地跑上几个月不出大问题。我见过太多团队把全部精力花在“搭建”上,却在巡检、备份、参数调优上草草收场。如果你刚准备动手,优先把这张拓扑图画清楚,把资源估算做扎实,再开始装系统。后面每一步,心里都有了数。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询