如果你搜过“Hadoop 流处理”这几个字,大概率会跟我踩一样的坑:搜出来的前几页几乎全是 Hadoop 集群搭建教程、伪分布式安装、HDFS 文件操作、Hive 入门。等你按着教程把集群搭起来,兴奋地打开 Web UI,然后呢?数据还是乖乖躺在 HDFS 上,离“流”字差了十万八千里。这个题目的真正含义,并不是教你怎么让 MapReduce 跑得更快,而是要把 Kafka、Spark Streaming/Flink、HDFS、Hive、ZooKeeper 这几个组件串成一条能实时消费、实时计算、最终落地的数据管道。
这篇文章我就按这条链路,把每个环节的选型、配置、代码和实际踩过的坑都过一遍。适合刚接触大数据、想从离线批处理扩展到实时场景的开发,也适合正在准备 Hadoop 与流处理相关面试的朋友把知识串成体系。我会尽量少讲虚的,多给能直接上手的东西。
1. 先说清楚:Hadoop 生态里“流处理”到底指什么
1.1 批处理与流处理的本质差异
批处理和流处理最直观的区别,可以拿洗照片来类比:传统批处理是攒一胶卷照片,全部拍完再统一冲洗,出来的永远是“上一段时间”的成果;流处理更像拍立得,按下快门就出一张,数据每时每刻都在被处理。
MapReduce 天生就是批量模型——把任务切分成 map 和 reduce 阶段,中间还要经过 shuffle,数据在环节之间等待、排序、归并,这决定了它不是为毫秒级延迟设计的。你如果用 MapReduce 去做实时指标,等于每天晚上洗一次昨天的照片,业务方第二天早上看到的数据永远是滞后的。所以“Hadoop 流处理”这句话,严格讲并不是“用 Hadoop 原生做流”,而是“以 Hadoop 生态为底座,搭建流式数据处理体系”。
现在主流的两条技术路线,本质都是“假装在流式处理”或“真的在流式处理”:
- Spark Streaming / Structured Streaming,把实时拆成一个个小批次,比如每 10 秒一个 batch,相当于“每 10 秒洗一张照片”。
- Flink,每来一条事件就处理一条,才是真正意义上的拍立得。
这两套引擎跑在 YARN 上,计算结束后的结果仍然落到 HDFS,再通过 Hive 或 Presto 对外提供查询能力。这就是为什么你会发现,几乎所有大数据岗位招聘都要求同时懂 Hadoop、Spark、Kafka,它们根本就是一条产业链上的不同工序。
1.2 Hadoop 全家桶里的流处理拼图
如果说 HDFS 是仓库,YARN 是调度中心,那么流计算引擎就是仓库里的流水线,Kafka 就是传送带的入口。数据在这套体系里的完整路径是:
业务日志或消息 → Kafka → 流计算引擎 → HDFS → Hive 数仓 → 报表 / 业务服务
这里面还有个容易被忽略的角色:ZooKeeper。它负责协调大家,HDFS 高可用时的 NameNode 选举、YARN 的 ResourceManager 选主、Kafka 的 broker 注册和 broker 元数据管理,全都在它身上。一套三节点的 ZooKeeper 集群,几乎是大数据平台的“神经系统”。
所以你去搜“hadoop和zookeeper整合实战”“hadoop spark hive”,其实都是在搭建这套拼图的不同部分。真正的流处理项目,很少是只用某一个组件的,它一定是一个组合架构。我在后面每一章里,都会围绕这条链路讲一个具体的落地环节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零搭建可跑通的实验环境:JDK、Hadoop 与免密登录的细节
2.1 版本组合与选型理由
很多初学者一上来就装最新版,结果组件之间互相不兼容,折腾两三天最后怀疑人生。大数据生态最忌讳盲目追新,我推荐一套经过验证的稳定组合:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 对 Hadoop/Spark/ZooKeeper 兼容性最好,JDK 17 看着新,很多框架的反射机制会出问题 |
| Hadoop | 3.3.x | 支持 YARN 多租户、NameNode Federation,默认端口和旧版不一样 |
| Spark | 3.3.x | 内置 Structured Streaming,Scala 2.12 版本最稳 |
| Kafka | 3.3.x | 虽然支持 KRaft 模式,但和 Hadoop 生态结合还是推荐 ZooKeeper 模式 |
| ZooKeeper | 3.6.x 或 3.7.x | 如果 HDFS HA 和 Kafka 都用,统一装一套,节省资源 |
为什么 JDK 必须用 1.8?不是因为它先进,而是因为大数据组件生态经过了十几年磨合,几乎所有框架的官方文档、社区踩坑帖都基于 JDK 8。你换成高版本,编译打包、反射调用、JVM 参数可能全要重新调一遍,对于一心想跑通流处理的你来说,这个成本不值得。
Spark 3.3 和 Hadoop 3.3 的兼容性我已经实测过,Spark 能直接读 HDFS 上的数据,提交任务到 YARN 也没有额外的兼容性补丁。Kafka 这边,3.x 的客户端向后兼容性做得很好,跟 Spark 的 kafka010 集成包一起用没什么问题。
2.2 伪分布式环境的三个常见坑
本地实验阶段,大部分人不会上来就搞五台机器,都是先搭伪分布式。网上教程很多,我只讲三个最常踩的坑,这三个坑我基本每次搭建都会遇到,全是血泪经验。
第一个坑是 SSH 免密没配好。Hadoop 启动脚本会通过 SSH 登录 localhost 来拉起进程,如果免密没生效,启动时就会报连接拒绝或者要求输密码,整个集群进程起不来但日志又不够直观。正确做法:
bash复制ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
ssh localhost
最后一条命令如果能直接进来不提示输入密码,说明免密成功,再去启动集群。
第二个坑是格式化 NameNode 之后,DataNode 起不来或者不在 Web UI 上显示。原因是多次执行 hdfs namenode -format 会生成新的 clusterID,而 DataNode 第一次启动时已经把旧的 clusterID 写到自己的 VERSION 文件里了,两边对不上,DataNode 就会默默退出,你查日志才能看到 Incompatible clusterIDs 的报错。解决方式很简单:删除 Hadoop 临时目录下的数据再重新格式化,或者在 DataNode 的 VERSION 文件里把 clusterID 改成和 NameNode 一致。
第三个坑是内存不够。默认配置下,Hadoop 和 YARN 给 JVM 分配的内存很大,小机器经常出现进程刚启动就被系统杀掉的情况。建议在 hadoop-env.sh 里显式调小:
bash复制export HADOOP_HEAPSIZE=512
在 yarn-env.sh 里也把 ResourceManager 和 NodeManager 的堆内存调到 512M 左右。实验环境不需要那么多内存,能跑通流程才是第一目的。
2.3 用 Docker 镜像快速替代手动集群
如果你已经不想再经历一遍“Java 路径配错、目录权限不对、配置文件少个标签”的折磨,我强烈建议实验环境直接用 Docker。网上有很多现成的 Hadoop 镜像,搜一下就能看到。
用 Docker 跑 Hadoop 的关键认知是:容器只是一个进程,镜像里的数据默认是临时的,容器一删全没了。所以必须做两件事:一是把 NameNode 和 DataNode 的数据目录挂载到宿主机,二是把 9870(NameNode UI)、8088(YARN UI)这些端口映射出来,不然你根本访问不到页面。
我自己的做法是把宿主机 /data/docker/hadoop 挂到容器里的 /opt/hadoop/data,这样集群挂了重建容器,数据还在。镜像启动之后,进容器内部验证 hdfs dfsadmin -report,能看到 DataNode 状态正常,再继续装 Kafka。
Docker 方式还有一个好处:方便多节点模拟。你可以在同一台机器上启动三个容器组成集群,网络用 docker 的 bridge 网络,容器之间通过服务名互相访问,效果和真机集群差别不大,非常适合学习 Kafka + Spark 这种跨组件流转的场景。
3. 数据入口:Kafka 如何与 HDFS 打通
3.1 Kafka 为什么是流处理的标准入口
流处理的第一步,是把源源不断产生的数据“接进来”。为什么几乎所有人都选 Kafka?因为它在吞吐、持久化和多消费者能力上做了很好的平衡。
理解 Kafka 最核心的三个概念就够了:
- Topic,相当于一个消息分类,所有页面点击日志可以叫
pageview-log。 - Partition,一个 Topic 被切成多个分区,分区内消息有序,这是并行消费的基础。
- Offset,消费者在分区里的消费位置,类似书签,记录读到哪一条了。
可以用快递集散中心来类比:Topic 是一条传输线,Partition 是传输线上的多个分拣口,消费者组里的每个实例是一个分拣员,谁来接哪个口由 Kafka 协调,Offset 是分拣员手里记录的单号,保证不重复、不遗漏。
Kafka 与 ZooKeeper 的关系也值得一提。在 Kafka 3.x 之前,Kafka 的 broker 元数据、Controller 选举全部由 ZooKeeper 管理;就算用 Kafka 3.x 的 KRaft 模式,你会发现它把 ZooKeeper 的逻辑内部实现了,但还是需要一套 quorum。如果你的 Kafka 是和 HDFS HA、YARN HA 一起部署,统一使用同一个 ZooKeeper 集群是合理的,这也是我和团队在生产环境中的做法。
3.2 从 Kafka 到 HDFS 的三种落地方案
数据进了 Kafka 之后,怎么搬到 HDFS?有几种常见方案,我分别说明适用场景:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Flume | 配置简单,不用写代码 | 灵活性差,ETL 逻辑不好做 | 纯搬运,数据格式简单 |
| Kafka Connect | 官方生态,支持多种 Sink | 配置较繁琐,部分插件需要自己编译 | 数据入湖、跨系统迁移 |
| 自研/流计算框架 | 可做复杂计算,完全可控 | 需要开发与调优 | 实时计算 + 落 HDFS 一体完成 |
Flume 的配置比较直观,核心就是配一个 Kafka Source 和一个 HDFS Sink:
properties复制agent.sources = kafka_source
agent.channels = mem_channel
agent.sinks = hdfs_sink
agent.sources.kafka_source.type = org.apache.flume.source.kafka.KafkaSource
agent.sources.kafka_source.kafka.bootstrap.servers = node1:9092,node2:9092
agent.sources.kafka_source.kafka.topics = pageview-log
agent.sources.kafka_source.kafka.consumer.group.id = flume-group
agent.channels.mem_channel.type = memory
agent.channels.mem_channel.capacity = 10000
agent.sinks.hdfs_sink.type = hdfs
agent.sinks.hdfs_sink.hdfs.path = /data/flume/pageview/%Y%m%d
agent.sinks.hdfs_sink.hdfs.fileType = DataStream
agent.sinks.hdfs_sink.hdfs.rollInterval = 300
但你要注意一点:如果只是把 Kafka 数据搬到 HDFS,那数据进到 HDFS 之后还是“死”的,你要做实时统计还得再写一套任务去读 HDFS。所以更主流的选择是第三种,也就是下一章要讲的,让流计算引擎直接从 Kafka 消费,算完写 HDFS。Kafka 在这里扮演的是缓冲和削峰的角色,而不是终点。
3.3 流式写 HDFS 的小文件问题怎么解
这是流处理项目里最经典、也最容易后期爆雷的问题。
流式任务每跑一个批次就会产生一个或多个文件,批次一多,HDFS 上就会堆满几百 KB、几 MB 的小文件。小文件的危害有两层:第一层,NameNode 把每个文件的信息都放在内存里,小文件数量上来了,NameNode 内存会被吃光;第二层,后续用 Hive 跑 SQL 时,每个小文件都要开一个 task 去读,任务调度开销比实际读取还大,查询慢到怀疑人生。
解决办法的核心思路是“滚动合并”:
- 按时间滚动:比如每 300 秒生成一个文件,文件通常能到百兆级别。
- 按大小滚动:文件达到 128MB 再关闭,适合流量大的场景。
- 在 Spark 里做合并:输出前
coalesce(n)控制分区数,避免每个分区写一个文件。
我见过不少项目,前面流算得飞快,最后写 HDFS 产生每天几百万个小文件,直接把集群拖垮。所以在设计阶段就要问一句:每个输出文件有多大?如果达不到几十 MB 级别,就必须做合并逻辑,或者定期跑 Compaction 任务把老数据重新合并。
4. 核心实操:用 Spark Streaming 跑通第一个流式统计任务
4.1 先选 API:DStream 还是 Structured Streaming
Spark 生态里有两套流处理 API:老的 DStream(微批 RDD 流)和新的 Structured Streaming(基于 DataFrame 的流式处理)。面试爱考 DStream 的底层原理,但生产环境我推荐 Structured Streaming,因为它写出的代码更接近普通 DataFrame 操作,语义更清晰,端到端 exactly-once 也更容易实现。
不过这一章我仍然用 DStream 来做演示,原因有二:一是它概念纯粹,适合理解流处理本质;二是很多老项目、老教程都是它,面试题也绕不开。你掌握了 DStream,再看 Structured Streaming 会非常快。
4.2 需求、数据格式与完整代码
我们的目标很简单:模拟电商网站点击流,统计每 10 秒内各页面的访问量。
先用 Python 写一个 Kafka 生产者,模拟持续产生点击日志:
python复制import random
import time
from kafka import KafkaProducer
producer = KafkaProducer(
bootstrap_servers='node1:9092,node2:9092',
value_serializer=lambda v: v.encode('utf-8')
)
pages = ['/home', '/search', '/detail', '/cart', '/pay']
while True:
line = "{},{},{}".format(
int(time.time() * 1000),
random.choice(pages),
random.randint(1, 10000)
)
producer.send('pageview-log', value=line)
time.sleep(0.5)
这里为了演示简洁,我用的是 CSV 格式:时间戳、页面、用户ID。真实生产建议用 JSON 或 Avro,带 schema,后续像 Hive 这种表结构系统对接会更方便。
然后写 Scala 的 Spark Streaming 消费端:
scala复制package com.example
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.kafka010._
object PageViewStream {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("PageViewStream")
val ssc = new StreamingContext(conf, Seconds(10))
ssc.checkpoint("hdfs:///user/spark/checkpoint/pageview")
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "node1:9092,node2:9092",
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "pageview-group",
"auto.offset.reset" -> "latest",
"enable.auto.commit" -> (false: java.lang.Boolean)
)
val topics = Array("pageview-log")
val stream = KafkaUtils.createDirectStream[String, String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](topics, kafkaParams)
)
val lines = stream.map(_.value())
val counts = lines
.map(line => {
val arr = line.split(",")
(arr(1), 1L)
})
.reduceByKeyAndWindow(_ + _, Seconds(30), Seconds(10))
counts.foreachRDD { (rdd, time) =>
if (!rdd.isEmpty()) {
val path = s"hdfs:///data/stream/pageview/time=${time.milliseconds}"
rdd.coalesce(1).saveAsTextFile(path)
}
}
ssc.start()
ssc.awaitTermination()
}
}
这段代码里有两个细节值得讲:
第一,reduceByKeyAndWindow 的窗口长度是 30 秒,滑动间隔是 10 秒,所以每次统计结果实际上覆盖最近 30 秒。如果只要最近 10 秒,窗口长度就设成 10 秒。
第二,enable.auto.commit 我设成了 false,并用 Spark 的 checkpoint 来维护 offset,这样才能在任务重启时做到“至少一次”语义,而不是盲目自动提交导致丢数据。
4.3 打包与提交
Maven 依赖方面,核心是引入 spark-streaming 和 spark-streaming-kafka-0-10:
xml复制<properties>
<scala.version>2.12.17</scala.version>
<spark.version>3.3.2</spark.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.12</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.3.2</version>
</dependency>
</dependencies>
打包建议用 maven-shade-plugin 打成 fat jar,把 Kafka 客户端和 Spark 集成都打进去,不然 spark-submit 的时候还要带一堆 --jars,很容易出现版本冲突。
提交命令:
bash复制spark-submit \
--master yarn \
--deploy-mode client \
--driver-memory 1g \
--executor-memory 2g \
--executor-cores 1 \
--num-executors 2 \
--class com.example.PageViewStream \
target/streaming-demo-1.0.jar
跑起来之后,打开 Spark UI 的 Streaming 页签,能看到 batch 的处理时间曲线;到 HDFS 的 /data/stream/pageview/ 目录下,能看到一批批生成的结果文件。
4.4 消费位移与重复消费问题
流处理项目上线后,最怕“数据对不上”。你用 Kafka 消费,如果任务挂了,offset 没有提交,重启后就会从上一个提交点重新消费,出现一部分重复数据,这就是“至少一次”语义。如果为了追求不重复而先提交 offset 再处理数据,任务中途挂了就会丢数据,变成“至多一次”。
真实生产环境,我建议这样处理:
- 把流计算结果写到一个带唯一标识的中间表或带时间戳的路径,下游做幂等;
- 开启 Spark Streaming 的 checkpoint,让 offset 管理交给框架;
- 如果下游是 Hive,落数据时按统计时间做分区,重复跑的批次可以
INSERT OVERWRITE同一批分区,而不是不断追加。
“精确一次”不是框架一个开关就能解决的,它需要消费端、计算端、输出端配合,这是面试官最喜欢追问的点,也是实战中最考验架构能力的地方。
5. Flink 与 Spark Streaming 怎么选:工程师视角的对比
5.1 延迟与计算模型差异
很多人纠结 Spark 和 Flink 选哪个。我先把两边的底层差异讲透:
Spark Streaming 是微批模型,每攒一个 batch 才处理一次,延迟受 batch 间隔限制,一般做到秒级已经不错。Flink 是事件流模型,数据到了就触发计算,延迟在毫秒级。对实时数仓这种场景,秒级和毫秒级差异不大;但对风控、限流、异常交易检测这种场景,差一秒钟可能就是完全不同的结果。
5.2 功能与生态对比
| 对比项 | Spark Streaming | Flink |
|---|---|---|
| 延迟 | 秒级 | 毫秒级 |
| 计算模型 | 微批 | 真流式 |
| 窗口支持 | 丰富 | 更丰富,Event Time 是原生概念 |
| 状态管理 | 有限,需要 checkpoint | 强大,支持细粒度状态 |
| 精确一次 | 配合 Kafka 和输出端可做 | 原生支持较好 |
| SQL 支持 | Structured Streaming SQL | Flink SQL |
| 与 Hadoop/Hive 集成 | 非常成熟 | 成熟 |
| 学习曲线 | 较低,Spark 程序员易上手 | 概念多,需要理解事件流 |
这里我要提醒一点:如果你的团队已经熟 Spark,别因为 Flink 更“高级”就盲目切换。工具是给业务服务的,不是用来证明自己技术前卫的。把 Spark Streaming 的微批调优好,很多场景完全够用。
5.3 我实际决策时会问的三个问题
第一个问题:业务对延迟的容忍度是多少?如果是 10 秒甚至 1 分钟都行,Spark 足够;如果要求 500 毫秒以内,直接 Flink。
第二个问题:状态计算复杂吗?比如需要按用户维持会话状态、30 分钟未操作则算超时,这种场景 Flink 的 Keyed State 和定时器非常顺手,用 Spark 做会很别扭。
第三个问题:这套体系里 SQL 分析重不重?如果流算完的结果还要频繁进 Hive、给分析师查,Spark 那一套 SQL 生态用起来更顺,因为它和 Hive 的底层是同一套优化思路。当然,Flink SQL 也在追赶,但团队的学习成本要算进去。
6. 面试与实战中最高频的流处理问题
6.1 数据延迟变大怎么定位
实时任务最怕延迟。延迟变大的时候,不要慌,按链路逐段排查:
第一步,先看数据有没有进 Kafka。用命令看 consumer group 的消费滞后:
bash复制kafka-consumer-groups.sh --bootstrap-server node1:9092 --describe --group pageview-group
如果 LAG 数值不断增长,说明消费者处理不过来了。
第二步,看计算引擎。Spark UI 里看 batch 的 scheduling delay 和 processing time,如果 processing time 接近 batch interval,说明资源不够,要加 executor;如果 scheduling delay 很长,说明整个资源池被占满,要检查是不是有别的任务在抢资源。
第三步,看输出端。如果计算结果写到 HDFS 或者 Redis,而下游扛不住,也会拖慢整个链路。我曾经遇到一个任务,一切正常,最后发现是写 HDFS 时不停生成小文件,NameNode 处理请求的线程池被打满,所有 Spark 任务的输出操作都在排队。
6.2 什么是反压,怎么处理
反压是流处理里一个重要概念,简单说就是“上游数据倒灌”。
生产者每秒发 10 万条,消费者每秒只能处理 1 万条,处理不过来的时候,数据会在 Kafka 或者内存里堆积。如果不处理反压,内存最终会被撑爆,任务崩溃。
Spark Streaming 的处理方式比较暴力:开启 spark.streaming.backpressure.enabled=true,再配合 spark.streaming.kafka.maxRatePerPartition 来限制每个分区每秒最多消费多少条,让系统自适应地降低消费速率。Flink 则天然支持反压传播,上游会发现下游处理不动,自动减缓发送。
面试官如果问“反压怎么解决”,最好答两层:一是框架层面的参数调优,二是业务层面的处理策略,比如削峰填谷,把峰值数据先压到 Kafka,让消费者用匀速去消费。
6.3 “jar does not exist or is not a normal file”这类环境错误
这个报错在 Hadoop 社区出现频率极高,我见过不少同学排了一下午,最后发现是最简单的路径问题。报错全文一般长这样:
code复制jar does not exist or is not a normal file: /usr/local/hadoop/share/hadoop/m...
我总结了这个问题的三类原因:
第一类,路径确实不存在。检查一下 /usr/local/hadoop/share/hadoop/ 目录,看是不是你少写了后续的 mapreduce/lib 之类的子路径。这种通常是配置 HADOOP_CLASSPATH 时手抖,少写或多写了一段。
第二类,文件权限问题。jar 文件存在,但当前用户没有读权限。用 ls -l 看一眼,加上 chmod 644 就可以。
第三类,路径里有多余字符。比如 hadoop classpath 的输出里有换行,或者某些配置工具在追加类路径时末尾带了冒号或逗号,也会让 Hadoop 认为那不是正常文件。排查时可以用 echo"$HADOOP_CLASSPATH" 看看实际值。
这类环境问题,很多是从网上复制粘帖命令时,路径版本对不上导致的。我建议养成一个习惯:拿到一段配置命令,先看目录结构再执行,别一上来就回车。
6.4 精确一次与幂等设计
“精确一次”是流处理面试永恒的高频考点。框架层的 exactly-once 只是第一步,真正的难点在输出端。
举个例子:你的流任务把结果写入 MySQL 的 today_page_cnt 表,任务重启后重复执行了同一个窗口的数据,如果直接 INSERT,就会产生重复记录;但是如果你在表里加一个唯一键,比如 batch_id,重复写入时让数据库拒绝或更新,就做到了幂等。
同理,写 HDFS 时保证路径唯一(我上一章代码里用了时间戳)本质也是在构造幂等条件。面试时你能说出“框架保证 + 输出端幂等”这两层,就已经比大多数人强了。
7. 绕不开的生态整合:ZooKeeper、Hive、HDFS 扩容
7.1 ZooKeeper 在 Hadoop 生态中的实际作用
前面提过 ZooKeeper 是神经系统,这里详细展开。我见过有人问:ZooKeeper 是不是只给 Kafka 用的?不是,它在 Hadoop 体系里至少承担三类职责:
- HDFS HA:两个 NameNode 谁是 Active,由 ZooKeeper 选主,Active 挂掉后自动切换。
- YARN HA:ResourceManager 的选主也靠它。
- Kafka:broker 元数据、Controller 选举、消费 offset 的存储(旧版本),全依赖它。
实操中的典型配置是 core-site.xml 里把 fs.defaultFS 指定为 nameservice,然后配置 ha.zookeeper.quorum 指向 ZooKeeper 集群地址:
xml复制<property>
<name>ha
