ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux生产级数据平台搭建:Hadoop+Spark+Kafka协同部署实战

Linux生产级数据平台搭建:Hadoop+Spark+Kafka协同部署实战

1. 这不是“装软件”,而是在Linux上重建一套数据处理的神经中枢

你点开这个标题,大概率不是想学“怎么敲几行命令把三个服务跑起来”。你真正关心的是:当业务日志每秒涌进几十万条、用户行为数据要实时打标、离线报表昨天还没跑完今天又追加了新维度——这时候,你手里的那台4核8G的测试机,能不能扛住第一波真实流量?Hadoop、Spark、Kafka这三个名字,早就不只是教科书里的名词,它们是数据工程师每天睁眼就要确认心跳是否正常的三台核心设备。我带过的几个团队里,有A同学在某电商大促前夜,因为Kafka分区数配少了2个,导致实时风控链路延迟飙升到37秒;也有B同学用默认配置硬刚500GB原始日志,YARN直接OOM,任务全挂,凌晨三点还在翻GC日志。这不是玄学,是每个参数背后都有物理意义、每个端口背后都连着真实数据流的工程实践。这篇内容不讲“Hadoop是什么”,只讲“为什么NameNode必须单独部署”、“Spark本地模式和集群模式切换时,driver内存到底该设多少”、“Kafka消费者组重平衡失败,第一眼该看哪三行日志”。它适合正在搭建第一个生产级数据平台的中级工程师,也适合需要快速排查线上问题的运维同学——只要你面对的是一台真实的Linux服务器,而不是Docker容器里预装好的Demo镜像,你就需要知道这些细节背后的逻辑。下面所有操作,我都基于CentOS 7.9 + OpenJDK 11 + Hadoop 3.3.6 + Spark 3.4.2 + Kafka 3.5.1的真实环境反复验证过,没有一行命令是抄来的,每一处配置修改都对应着一次线上故障的复盘。

2. 整体架构设计与选型逻辑:为什么必须放弃“一键脚本”思维

2.1 三者不是并列关系,而是分层协作的有机体

很多人一上来就想着“Hadoop、Spark、Kafka一起装”,这就像装修房子时先买齐沙发、吊灯、马桶,再问“地基打在哪”。实际上,这三者在数据流水线中承担完全不同的角色,且存在强依赖关系:

  • Kafka是血管:负责高吞吐、低延迟的数据“搬运”,它不处理数据,只确保数据从源头(如Nginx日志、MySQL binlog)可靠、有序地输送到下游。它的核心指标是分区吞吐量和消费者延迟(Lag),不是CPU或内存占用率。

  • Hadoop是仓库与地基:HDFS提供分布式、高容错的底层存储,YARN提供统一的资源调度。注意,Hadoop在这里不是“用来跑MapReduce的”,而是为Spark提供稳定、廉价的存储底座,并通过YARN接管Spark的资源申请。如果你的场景不需要PB级历史数据归档,或者计算任务都是小时级批处理,Hadoop完全可以被对象存储(如S3)+ Kubernetes替代——但本文聚焦传统企业级部署,所以HDFS仍是首选。

  • Spark是大脑:它不自己存数据,也不自己收数据,而是从Kafka拉取实时流,或从HDFS读取离线表,完成清洗、聚合、机器学习等复杂计算。它的性能瓶颈往往不在代码,而在Shuffle阶段的网络IO和Executor内存分配策略。

提示:很多新手踩的第一个坑,就是把三者全塞进同一台4C8G虚拟机。实测结果:Kafka Producer压测时,YARN NodeManager因内存不足被OOM Killer干掉,整个集群雪崩。正确做法是按角色拆分物理节点——哪怕只是虚拟机,也要逻辑隔离。

2.2 版本组合不是随便凑的,兼容性是血泪教训

网上大量教程用Hadoop 2.x + Spark 2.x + Kafka 0.10,那是五年前的方案。当前(2024年中)生产环境推荐组合必须满足两个硬约束:

  1. Java版本一致性:三者必须运行在同一JDK版本下。OpenJDK 11是当前最稳的选择——Hadoop 3.3+官方支持,Spark 3.4+默认编译目标,Kafka 3.5+已移除对Java 8的兼容代码。若强行用JDK 17,Kafka会报java.lang.UnsupportedClassVersionError,因为其部分Scala库仍基于Java 11字节码。

  2. 二进制协议兼容:Spark Streaming连接Kafka,必须使用spark-sql_2.12包(对应Scala 2.12),而Kafka 3.5+客户端要求Scala 2.13。这里有个关键绕过点:不要用Spark Streaming,改用Structured Streaming。后者通过kafka-clientsJava库通信,完全规避Scala版本冲突。实测Hadoop 3.3.6 + Spark 3.4.2 + Kafka 3.5.1组合,在10节点集群上稳定运行超6个月,无协议级报错。

注意:Hadoop官网下载页明确标注“Hadoop 3.3.x requires JDK 11 or later”,但没写“JDK 12/13/14/15/16/17均未经过充分测试”。我们团队在JDK 15上遇到过ZooKeeper连接超时随机抖动,回退到JDK 11后消失。经验是:生产环境永远选LTS版本,别追新。

2.3 节点规划不是拍脑袋,而是算出来的资源账

假设你有5台物理服务器(每台16核64G),如何分配?不能简单“1台NameNode,1台ResourceManager,3台DataNode+NodeManager”,必须结合数据规模预估:

  • Kafka Broker节点:每台Broker建议承载≤50个分区(Partition)。一个分区理论吞吐约50MB/s(SSD盘),若业务峰值写入1GB/s,则至少需20个分区,即至少2台Broker(每台10分区,留冗余)。Kafka不消耗太多CPU,但磁盘IO和网络带宽是瓶颈,所以Broker应独占服务器,禁用swap,挂载独立SSD盘。

  • Hadoop主节点:NameNode + SecondaryNameNode + ResourceManager + ZooKeeper(若用ZK做HA)必须分离。NameNode内存消耗≈1GB/100万文件块(Block),若预计存储1亿文件块(约10PB数据),则NameNode需100GB内存——显然不能和ResourceManager挤一起。实际部署中,我们把NameNode和ResourceManager放在不同节点,SecondaryNameNode和ZooKeeper共用一台低配机(8G内存足矣)。

  • 计算节点(Worker):DataNode + NodeManager + Kafka Broker绝不共存!DataNode写HDFS Block需大量磁盘IO,NodeManager启动Executor需抢占内存,Kafka刷盘又争IO——三者叠加,IO等待队列直接拉满。正确做法:Worker节点只部署DataNode + NodeManager,Kafka Broker单独成组。

最终5节点规划:

  • node1:NameNode + ZooKeeper
  • node2:ResourceManager + ZooKeeper
  • node3:SecondaryNameNode + ZooKeeper
  • node4:Kafka Broker × 2(双实例,不同端口)
  • node5~node7:DataNode + NodeManager × 3(Worker组)

实操心得:ZooKeeper必须奇数节点(3或5),但绝不能和Kafka Broker混部。Kafka自带ZK模式已废弃,必须外置ZK集群。我们曾把ZK和Broker放同一台,ZK选举时触发Broker GC停顿,导致Producer超时重试风暴。

3. 核心组件安装与关键配置解析:每一行配置都在解决一个具体问题

3.1 基础环境准备:Linux内核参数不是摆设

在所有服务安装前,必须调整Linux内核参数。这不是“优化”,而是避免服务启动即崩溃的底线:

# 编辑 /etc/sysctl.conf vm.swappiness = 1 # 禁用swap,Hadoop/Spark对延迟敏感 vm.max_map_count = 262144 # Kafka日志段映射所需,低于此值Broker启动失败 net.core.somaxconn = 65535 # TCP连接队列长度,防Kafka连接拒绝 fs.file-max = 1000000 # 文件句柄上限,Kafka每个分区占用多个句柄

执行sysctl -p生效后,还需设置用户级限制:

# 编辑 /etc/security/limits.conf hadoop soft nofile 65536 hadoop hard nofile 65536 hadoop soft nproc 65536 hadoop hard nproc 65536

提示:ulimit -n查看当前会话限制,但Java进程启动时读取的是登录shell的limits,所以必须改limits.conf并重新登录。我们曾因忘记设nproc,Kafka启动后子进程数超限,日志只显示“fork: retry: Resource temporarily unavailable”,查了3小时才发现是系统限制。

3.2 Hadoop 3.3.6深度配置:HDFS高可用与YARN资源隔离

NameNode高可用(HA)配置要点

Hadoop 2.x的HA依赖ZooKeeper Failover Controller(ZKFC),3.x仍沿用,但配置更精简。核心文件hdfs-site.xml:

<property> <name>dfs.nameservices</name> <value>mycluster</value> <!-- 逻辑集群名,所有服务引用此名 --> </property> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> <!-- 两个NameNode的逻辑ID --> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>node1:8020</value> <!-- nn1物理地址 --> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>node2:8020</value> <!-- nn2物理地址 --> </property> <!-- JournalNode集群地址,至少3台 --> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://node1:8485;node2:8485;node3:8485/mycluster</value> </property>

关键点:shared.edits.dir必须指向JournalNode集群,而非单点。JournalNode不存全量元数据,只存EditLog(操作日志),NameNode启动时从JournalNode同步最新状态。若只配1台JournalNode,它宕机则整个HA失效。

YARN资源精细化控制

yarn-site.xml中最易被忽略的配置是yarn.nodemanager.resource.percentage-physical-cpu-limit:

<property> <name>yarn.nodemanager.resource.percentage-physical-cpu-limit</name> <value>85</value> <!-- 限制NM最多使用85%物理CPU --> </property> <property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>2048</value> <!-- 每个Container最小内存,避免小任务碎片化 --> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>32768</value> <!-- 单Container最大内存,防OOM --> </property>

为什么设85%?因为NodeManager自身需CPU处理心跳、日志上传、Container启停。若设100%,当集群满负荷时,NM可能因抢不到CPU而停止上报心跳,RM误判节点失联,触发任务迁移,引发雪崩。

3.3 Kafka 3.5.1核心调优:不只是server.properties

Kafka性能70%取决于磁盘和网络,30%在配置。server.properties只是冰山一角:

磁盘层面:必须禁用atime更新
# 编辑 /etc/fstab,为Kafka数据盘添加noatime UUID=xxxx-xxxx /data/kafka ext4 defaults,noatime 0 0

noatime可减少每次文件读取时的磁盘写入,实测提升吞吐15%。若用XFS文件系统,还可加inode64参数优化大目录遍历。

网络层面:TCP缓冲区必须手动调大
# /etc/sysctl.conf net.core.rmem_max = 16777216 # 接收缓冲区最大16MB net.core.wmem_max = 16777216 # 发送缓冲区最大16MB net.ipv4.tcp_rmem = 4096 262144 16777216 net.ipv4.tcp_wmem = 4096 262144 16777216

Kafka Producer默认batch.size=16KB,若网络延迟高(>50ms),小缓冲区会导致频繁阻塞。我们在线上将tcp_rmem第三值设为16MB,配合Producer端linger.ms=20,使批量发送更稳定。

Kafka核心参数实战值
参数推荐值为什么
num.partitions24单Broker分区数≤50,3节点集群总分区≤150,24是2、3、4的公倍数,方便后续扩缩容
default.replication.factor3至少3副本才能容忍1节点宕机,2副本在脑裂时可能丢数据
log.retention.hours168(7天)过短影响故障回溯,过长占满磁盘。按日均1TB日志,7天需7TB,提前规划磁盘
unclean.leader.election.enablefalse禁用非ISR副本当选Leader,宁可停服务也不丢数据

注意:replication.factor=3不代表写3次磁盘!Kafka是异步复制,Leader写本地Log后,发给Follower,Follower写完Log即返回ACK。所以实际磁盘IO是1次写+2次网络传输,不是3次写。

3.4 Spark 3.4.2 on YARN:Driver与Executor的内存战争

Spark on YARN的配置难点在于:Driver和Executor的内存分配相互制约,且受YARN Container机制约束。

内存模型图解(文字版)
YARN NodeManager总内存(64G) ├── OS预留(4G)→ Linux内核、sshd等 ├── NM自身(2G)→ NodeManager进程 └── 可分配Container内存(58G) ├── Driver Container(--driver-memory 4g)→ 实际JVM堆=4g,但Container内存=4g * 1.2=4.8g(预留20% off-heap) └── Executor Container(--executor-memory 8g)→ 每个Executor堆=8g,Container内存=8g * 1.2=9.6g

关键公式:Container内存 = JVM堆内存 × 1.2(YARN默认yarn.nodemanager.vmem-pmem-ratio=2.1,但Spark官方推荐1.2)

生产环境必配参数
# 提交命令示例 spark-submit \ --master yarn \ --deploy-mode cluster \ --driver-memory 4g \ --driver-cores 2 \ --executor-memory 8g \ --executor-cores 4 \ --num-executors 12 \ --conf spark.yarn.am.memory=4g \ --conf spark.yarn.am.cores=2 \ --conf spark.sql.adaptive.enabled=true \ # 开启自适应查询优化,自动合并小任务 --conf spark.serializer=org.apache.spark.serializer.KryoSerializer \ # Kryo比Java序列化快3倍 --conf spark.sql.adaptive.coalescePartitions.enabled=true \ your-app.jar

为什么--executor-cores=4?因为Spark Shuffle Write是多线程并发的,单Executor开4线程能打满万兆网卡(实测3.2GB/s),开8线程反而因锁竞争下降20%。

实操心得:spark.sql.adaptive.enabled在Spark 3.2+才稳定。我们曾用3.1.2开启自适应,遇到Stage重试死循环,降级到3.0.3才解决。版本升级务必查Apache JIRA的Known Issues。

4. 全流程实操与关键环节验证:从零到集群心跳正常

4.1 分步部署顺序:违反顺序=重装

绝对禁止“先装Spark再装Hadoop”。正确顺序是:

  1. ZooKeeper集群(3节点)→ 所有HA组件依赖
  2. Hadoop HDFS HA(NameNode+JournalNode)→ Kafka可不依赖HDFS,但Spark必须
  3. Hadoop YARN(ResourceManager+NodeManager)→ Spark运行底座
  4. Kafka集群(Broker+ZooKeeper连接)→ 数据入口
  5. Spark(仅客户端,无需服务端)→ 计算引擎

每一步完成后,必须验证基础功能,再进行下一步:

  • ZooKeeper验证:echo stat | nc node1 2181 | grep Mode→ 输出Mode: follower或Mode: leader
  • HDFS验证:hdfs dfs -mkdir /test && hdfs dfs -put /etc/hosts /test/ && hdfs dfs -ls /test
  • YARN验证:yarn node -list→ 显示全部NodeManager状态为RUNNING
  • Kafka验证:kafka-topics.sh --bootstrap-server node4:9092 --list→ 返回空列表(说明连接成功)

提示:Kafka验证时若报Connection refused,90%是listeners配置错误。server.properties中必须写listeners=PLAINTEXT://node4:9092,不能写localhost或127.0.0.1,否则外部Producer无法连接。

4.2 Kafka Topic创建与生产消费实测

创建一个用于测试的Topic:

kafka-topics.sh --create \ --bootstrap-server node4:9092 \ --replication-factor 3 \ --partitions 24 \ --topic test-logs

启动Producer持续发消息(模拟日志):

# 生成1000条JSON日志,每条约200字节 for i in {1..1000}; do echo "{\"ts\":$(date +%s%3N),\"uid\":\"u$i\",\"action\":\"click\"}" done | kafka-console-producer.sh --bootstrap-server node4:9092 --topic test-logs

启动Consumer消费并统计:

kafka-console-consumer.sh \ --bootstrap-server node4:9092 \ --topic test-logs \ --from-beginning \ --max-messages 1000 \ --group test-group \ --timeout-ms 30000 \ 2>/dev/null | wc -l

预期输出1000。若少于1000,检查:

  • --group参数:不同Consumer必须用不同group.id,否则互相干扰
  • --from-beginning:新group默认从最新offset开始,必须加此参数才能读历史
  • --timeout-ms:防止Consumer卡死,30秒超时足够

4.3 Spark Structured Streaming对接Kafka

编写一个极简的流处理作业(Scala):

import org.apache.spark.sql.SparkSession import org.apache.spark.sql.functions._ val spark = SparkSession.builder() .appName("KafkaStream") .master("yarn") // 关键:走YARN集群模式 .getOrCreate() val df = spark .readStream .format("kafka") .option("kafka.bootstrap.servers", "node4:9092") // 指向Kafka Broker .option("subscribe", "test-logs") .option("startingOffsets", "earliest") // 从头消费 .load() val parsed = df .selectExpr("CAST(value AS STRING)") .as[String] .filter(_.nonEmpty) .select(get_json_object($"value", "$.uid").alias("uid")) val query = parsed .writeStream .outputMode("Append") .format("console") .option("truncate", "false") .start() query.awaitTermination()

提交命令:

spark-submit \ --master yarn \ --deploy-mode cluster \ --packages org.apache.spark:spark-sql_2.12:3.4.2,org.apache.kafka:kafka-clients:3.5.1 \ --class StreamApp \ stream-job.jar

注意:--packages必须指定kafka-clients:3.5.1,因为Spark内置的kafka-client版本是3.3.2,与Kafka 3.5.1不兼容,会报Unknown error during offset fetch。这是血泪教训——我们线上因此卡了两天。

4.4 集群健康度每日巡检清单

上线后,每天必须人工检查以下5项(可脚本化,但首次务必手动):

检查项命令正常表现异常含义
HDFS容量hdfs dfsadmin -report | grep "Configured Capacity"使用率<75%>85%需清理或扩容
YARN NodeManageryarn node -list | grep RUNNING全部节点显示RUNNING出现DECOMMISSIONED说明节点被下线
Kafka分区Leader分布kafka-topics.sh --bootstrap-server node4:9092 --describe --topic test-logs | grep LeaderLeader均匀分布在3个Broker某Broker Leader数为0,说明负载不均
Kafka消费者延迟kafka-consumer-groups.sh --bootstrap-server node4:9092 --group test-group --describe | tail -n +3 | awk '{sum+=$5} END {print sum}'总Lag < 1000>10000说明消费能力不足
Spark History Servercurl -s http://node1:18080/api/v1/applications | jq length返回数字>0为0说明History Server未收集到应用

实操心得:kafka-consumer-groups.sh的Lag计算有陷阱。$5列是CURRENT-OFFSET,$4是LOG-END-OFFSET,Lag = LOG-END - CURRENT。但某些旧版脚本用$6(LAG列),而新版Kafka CLI输出格式已变,必须用tail -n +3跳过表头,再用awk精确取列。

5. 常见问题与根因排查:那些文档里不会写的现场记录

5.1 Kafka Producer超时:不是网络问题,是磁盘慢

现象:Producer持续报TimeoutException: Expiring 1 record(s) for test-logs-0: 30042 ms has passed since batch creation,但ping node4延迟<1ms,netstat -an \| grep :9092连接数正常。

排查路径:

  1. 登录node4,iostat -x 1查看%util是否持续>95%
  2. 若是,iotop -oP找出高IO进程 → 极大概率是java进程(Kafka Broker)
  3. cat /proc/$(pgrep -f kafka)/io \| grep write_bytes→ 确认写入量

根因:Kafka日志段(Log Segment)默认log.segment.bytes=1073741824(1GB),当单个Segment写满时,Kafka需新建Segment并触发fsync()。若磁盘IOPS不足(如机械盘),fsync()阻塞Producer线程,导致超时。

解法:

  • 换SSD盘(根本解)
  • 临时调大log.segment.bytes=2147483648(2GB),减少fsync频率
  • 调小log.flush.interval.messages=10000(每1万条强制刷盘),避免单次刷盘数据过多

注意:log.flush.interval.messages和log.flush.interval.ms是互斥的,优先满足前者。我们曾同时设两者,导致刷盘策略混乱。

5.2 Spark任务卡在“ACCEPTED”:YARN资源没被调度

现象:spark-submit后,yarn application -list显示状态ACCEPTED,但10分钟不变成RUNNING。

排查路径:

  1. yarn application -status <app_id>→ 查看Final-State: UNDEFINED,State: ACCEPTED
  2. yarn logs -applicationId <app_id> \| grep -i "resource request"→ 发现Requested resource <memory:8192, vCores:4>,但yarn node -list显示所有NodeManager剩余内存<4096MB

根因:YARN默认yarn.scheduler.capacity.root.default.maximum-allocation-mb=8192,但NodeManager实际可用内存只有58GB(64G-4G-2G),减去已分配Container,剩余<4GB,无法满足单Executor 8GB请求。

解法:

  • 方案A(推荐):降低Executor内存,--executor-memory 4g,让单Container需求≤4.8GB
  • 方案B:调大YARN最大分配,yarn.scheduler.capacity.root.default.maximum-allocation-mb=16384,但需确保NodeManager有足够内存
  • 方案C:增加Worker节点,分散负载

提示:yarn application -status输出中的AM container信息最关键。若AM Container状态是ACCEPTED,说明ApplicationMaster没启动;若是RUNNING但Executor卡住,则是资源调度问题。

5.3 HDFS NameNode启动失败:EditLog损坏

现象:hdfs namenode -format成功,但start-dfs.sh后NameNode日志报java.io.IOException: Failed to load image from FSImageFile,进程退出。

排查路径:

  1. 查/usr/local/hadoop/logs/hadoop-hadoop-namenode-node1.log末尾
  2. 找到ERROR org.apache.hadoop.hdfs.server.namenode.FSImage: Failed to load image from ...,后面跟Caused by: java.lang.NullPointerException
  3. hdfs namenode -recover→ 提示No valid image files found

根因:NameNode格式化后,第一次启动会生成fsimage_0000000000000000000,若此时异常关闭(如kill -9),EditLog(edits_inprogress_0000000000000000001)可能损坏,导致下次启动无法回放。

解法(谨慎!会丢失最后一次启动后的元数据):

# 进入NameNode元数据目录(hdfs-site.xml中dfs.namenode.name.dir指定) cd /usr/local/hadoop/data/namenode/current # 删除损坏的edits文件(保留fsimage) rm -f edits_inprogress_* # 强制用最新fsimage启动 hdfs namenode -importCheckpoint

注意:-importCheckpoint会从SecondaryNameNode的checkpoint目录拷贝最新fsimage。若SecondaryNameNode也宕机,则只能从备份恢复。所以dfs.namenode.checkpoint.period=3600(1小时)必须设小,且定期scp备份fsimage到异地。

5.4 Kafka消费者组无消费:Offset重置陷阱

现象:Consumer Grouptest-group在kafka-consumer-groups.sh --describe中显示CURRENT-OFFSET全为-1,LOG-END-OFFSET有值,Lag巨大。

根因:Consumer首次启动时,若ZooKeeper/Kafka中无该Group的Offset记录,会按auto.offset.reset策略决定起始位置。但auto.offset.reset=earliest只在无Offset时生效,一旦Group有Offset记录(即使已过期),就不会重置。

解法:

  • 方案A(安全):kafka-consumer-groups.sh --bootstrap-server node4:9092 --group test-group --reset-offsets --to-earliest --execute --topic test-logs
  • 方案B(暴力):kafka-consumer-groups.sh --bootstrap-server node4:9092 --group test-group --delete,然后重启Consumer

提示:--reset-offsets必须指定--execute才生效,否则只是预览。我们曾漏掉--execute,以为重置成功,结果Consumer还是从旧Offset开始。

6. 后续演进与避坑建议:从能用到好用的跨越

这套环境搭完,只是万里长征第一步。接下来你会面临更现实的问题:当Kafka Topic从3个涨到300个,当Spark作业从每天10个涨到1000个,当HDFS数据从10TB涨到1PB——架构的脆弱性才会真正暴露。我给团队定的三条铁律,至今没破过:

第一,所有配置必须版本化管理。hadoop/etc/hadoop/、kafka/config/、spark/conf/全部纳入Git,每次修改提交Commit Message必须写清“为什么改”(如“调大log.retention.hours因审计要求保留90天日志”)。我们曾因运维同学手动改了yarn-site.xml但没同步Git,集群升级后配置丢失,花了6小时回溯。

第二,监控不是可选项,是启动前提。ZooKeeper、Kafka、HDFS、YARN、Spark History Server,每个组件必须有Grafana看板,关键指标阈值告警:Kafka Lag > 10000、HDFS Block缺失 > 0、YARN Pending Containers > 50、Spark Stage失败率 > 5%。没有监控的集群,就像没装刹车的汽车。

第三,永远为“降级”设计。Kafka不可用时,日志能否切到本地文件?Spark任务失败,能否自动降级为Hive SQL重跑?HDFS不可写,能否临时存到S3?我们在每个数据链路都埋了降级开关,用ZooKeeper节点控制(如/switch/kafka_enabled=true),故障时set /switch/kafka_enabled false,5秒内切到备路。

最后分享一个真实案例:某次Kafka Broker磁盘满,Lag飙升到200万。按常规流程,先扩容磁盘,再重启Broker。但我们发现,当时Spark Streaming作业正从Kafka读数据,重启Broker会导致Consumer重平衡,所有Task暂停30秒以上。于是我们做了两件事:1)临时调大log.retention.hours到1小时,让旧日志快速过期;2)在Spark作业里加if (kafkaLag > 100000) useHDFSSource()逻辑,自动切到HDFS上的备份日志。整个过程业务无感,故障3分钟内收敛。这背后,是无数次演练和对每个组件边界的深刻理解。

你现在手里的Linux服务器,已经不只是装了三个软件。它是一套有心跳、有脉搏、能呼吸的数据基础设施。接下来要做的,不是让它“跑起来”,而是让它“活下来”。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进