总有人问我:现在都在讲数据湖、湖仓一体,Hadoop 还有没有学习的必要?我的回答通常是——你不需要单独学 Hadoop,但你必须理解以 HDFS 为底座、以 YARN 为核心资源调度的这套大数据技术栈。它早就不是一个孤立的大数据框架,而是一整套分工明确的生态体系。存储、计算、调度、查询、治理环环相扣,每一层都承担着自己不可替代的关键角色。这篇文章我就按自己的理解,把这些角色到底负责什么、为什么这么设计、以及怎么把它们串起来用,一次性说清楚。
1. 顶层架构思路:为什么说 Hadoop 生态是一套“数据操作系统”
1.1 生态的全景与分层逻辑
我第一次接触 Hadoop 时也犯过糊涂,看到一堆名字:HDFS、YARN、MapReduce、Hive、HBase、Spark、Flink、Kafka、ZooKeeper……每个都能讲一堆东西,但就是搞不清谁先谁后、谁依赖谁。后来我带团队做数仓项目,才慢慢总结出一个比较稳的认知:这套技术栈本质上不是“组件大杂烩”,而是一套按层划分的数据操作系统。
- 存储层:HDFS / 对象存储 / HBase,管的是数据放在哪、怎么冗余、怎么扩展。
- 资源调度层:YARN,管的是集群有多少 CPU 和内存、谁有资格用。
- 计算引擎层:MapReduce、Spark、Flink、Tez,管的是数据怎么被算出来。
- 数据服务层:Hive、Trino、Impala、Kafka,管的是数据以什么结构、什么速度对外提供服务。
- 调度治理层:Oozie、DolphinScheduler、Airflow、Atlas、Ranger,管的是任务什么时候跑、谁有权限看、数据从哪来。
你可以把这套结构想象成一个大型餐厅:HDFS 是冷藏仓库,食材按区块存放在冷库里;YARN 是后厨的排班经理,决定哪个厨师用哪个灶台;Spark 和 MapReduce 是厨师团队,负责把菜做出来;Hive 是菜单,让点菜的人不用直接进后厨要食材;Kafka 是传菜电梯,保证前厅和后厨之间能快速周转;ZooKeeper 则是餐厅的总协调员,防止两个厨师同时抢一把刀。这样一想,每个组件的位置就非常清楚了。
1.2 深度集成:组件之间为什么能无缝协作
很多人以为 Hadoop 生态就是“装了一堆软件”,其实关键在深度集成。Hive 建立一张表,底层数据落在 HDFS 目录;Spark 读这张表时,不需要知道具体文件在哪,只需要告诉 Hive Metastore 要读哪个库表;YARN 收到 Spark 的申请后,在集群里找空闲容器,拉起来跑任务。整个过程像一套流水线,每个环节都通过标准接口对接。
这种设计的最大好处是:你可以按需替换局部组件,而不必推翻整套系统。比如你不想用 Hive 做引擎,可以换成 Spark SQL 直接读同一份 HDFS/Parquet 数据;你不想用 MapReduce 跑批,可以用 Tez 或 Spark 替代,YARN 不需要关心你用的是哪一种计算框架。这也是 Hadoop 生态生命力强的原因——它更像是一套协议,而不是一个绑定的单体内核。
1.3 组件重叠时怎么选型
等你看完生态全貌,会遇到一个更头疼的问题:好多组件功能重叠。Hive 和 Spark SQL 都能跑 SQL,Spark Streaming 和 Flink 都能做流处理,Flume 和 Kafka 都能传输日志,Sqoop 和 DataX 都能同步数据,到底用哪个?
我的选型原则很简单:先看数据量级和时效要求,再看团队熟悉度,最后看运维成本。离线批处理、团队又只会 SQL,Hive 就是最稳的选择;要做复杂 ETL 或机器学习特征,Spark 更顺手;要做到毫秒级事件处理,Flink 才是正解。不是非得选出“最好”的,而是要选出在你这张数据链路里“接得住”的。接下来的内容,我会把每个关键角色的设计逻辑和实操注意点展开,帮你判断什么场景用什么组件。
2. 骨架三件套:HDFS、YARN、ZooKeeper 的关键角色
2.1 HDFS:分布式存储不只是“把文件切开”
HDFS 是整个 Hadoop 生态的底座。它解决的问题很直接:单台服务器磁盘容量有限,数据大了放不下,而且一台机器挂了数据就没了。HDFS 把文件切分成 128MB 或 256MB 的块,分散存储到多台 DataNode 上,每个块默认保存 3 个副本,这样任何一台机器坏掉,数据都能从其他副本恢复。
但真正要理解 HDFS 的角色,不能只停在“分布式文件系统”这句话上。NameNode 是它的核心大脑,维护着整个文件系统的目录树和块位置信息。每个文件、每个目录、每个块在 NameNode 内存里都有对应的元数据记录。这意味着文件数量越多,NameNode 内存压力越大。我见过一个生产集群,明明磁盘容量还剩 40%,但 NameNode 堆内存涨到接近上限,集群进入安全模式,最后排查发现是某张业务表每天产生几千万个小文件,把元数据撑爆了。
另一个容易忽略的设计是机架感知。真实机房里,同一机架内的机器网络带宽高、跨机架带宽有限。HDFS 默认副本策略是:第一个副本放在客户端所在节点,第二个副本放在同机架不同节点,第三个副本放在不同机架。这样能兼顾写入效率和容灾能力。如果你没有配置net.topology.script.file.name,所有节点会被当成同一个机架,副本分配就会失效,极端情况下副本全落在同一个交换机下面,机架断电就只能等数据丢了。
实操建议:新手搭测试集群时,很容易把dfs.replication默认的 3 不改,三台 DataNode 也照跑,但这只是测试。真正上生产前,一定要规划好 NameNode 内存、副本数、块大小、机架感知,还要配置 NameNode HA。尤其是 HA,如果只有一个 NameNode,那它就是单点故障,元数据一丢,整个数据平台都等于归零。
2.2 YARN:资源调度层决定集群能同时跑多少活
YARN 的角色可以用一句话概括:把集群的 CPU 和内存统一收起来,按队列、按优先级、按资源量分配给不同任务。它把原来的 JobTracker 职责拆成 ResourceManager 和 NodeManager 两部分,ResourceManager 负责全局调度,NodeManager 负责管理单台节点上的资源,并启动容器运行任务。
MapReduce 在最早期是直接在 HDFS 上跑的,那时候一个集群要么跑 Map 要么跑 Reduce,资源利用率很低。YARN 出现后,Spark、Flink、MapReduce 等计算框架都能作为一种 ApplicationMaster 提交到同一套集群上,由 YARN 统一分配资源。这也是为什么你的 Hadoop 集群上能同时跑 Spark 批任务和 Flink 流任务,互不干扰。
做资源规划时,最需要关注的是调度器配置。生产环境一般用 Capacity Scheduler,把集群资源划分成多个队列,比如数据平台团队一个队列,业务分析团队一个队列,离线任务一个队列,实时任务一个队列。每个队列设好最小资源、最大资源,防止某个团队把资源占满。我早期吃过一个亏:所有任务都丢在 default 队列里,当时一个 Flink 实时任务提交时申请了整个集群的 80% 内存,结果后面所有 Spark 离线任务全部排队等待,业务方中午跑批迟迟出不来。后来我按业务重要程度分了三个队列,才彻底解决资源抢占问题。
排查 YARN 问题时,别急着看代码,先看 ResourceManager 的 Web 界面(8088端口),能直接看到每个队列的活跃和等待任务、每个容器的内存和 CPU 使用、以及任务失败原因。很多“跑不动”的问题,根本不是代码慢,而是资源队列满了,排队排到天荒地老。
2.3 ZooKeeper:分布式协调者,专门解决“选主”和“状态一致”
Hadoop 生态里有很多角色需要“选出一个老大”。HDFS 的 Active NameNode 是谁?两个 NameNode 同时活动就会脑裂,数据元数据就会混乱。YARN 的 ResourceManager 高可用模式下,Active 节点是谁?Kafka 的 Controller 是谁?这些问题的答案都来自 ZooKeeper。
ZooKeeper 的核心是ZAB 协议,保证多个节点之间数据一致,并提供临时节点、顺序节点、Watch 机制。最常用的是选主:多个节点同时去 ZooKeeper 创建一个临时顺序节点,编号最小的那个成为 Leader,其他节点成为 Follower。如果 Leader 服务器宕机,会话超时后临时节点会被删除,Follower 节点收到通知,再发起新一轮选主。
但 ZooKeeper 不是万能的,它不适合存大量数据,znode 里的内容尽量精简,只放状态信息,不放大对象。我见过有人把配置内容全都塞进 znode,结果 ZooKeeper 频繁 GC,整个集群的 Leader 选举和心跳全部超时。另外tickTime、sessionTimeout这些参数要结合实际网络环境调整,默认值在跨机房网络抖动时非常脆弱。我踩过的坑是:机房网络闪断几秒,ZooKeeper 会话超时,NameNode 以为对方挂了,触发自动切换,结果业务刚好在写入高峰期,产生了短暂写入异常。后面我把sessionTimeout从默认的 10 秒调到 30 秒,并优化了双机房专线,才稳住。
3. 数据链路关键角色:采集、计算、查询、存储的详细拆解
3.1 数据采集与传输:Flume、Kafka、Sqoop 的分工
要跑数仓,首先得把数据弄进来。日志类数据一般走 Flume + Kafka,关系型数据库里的业务数据则用 Sqoop(或者现在的 CDC 工具)同步。Flume 的定位是日志采集和简单聚合,它从 Web 服务器、应用服务器里监听日志文件或端口,按照配置的 Source、Channel、Sink 把数据发出去。Kafka 的角色则是削峰填谷和消息缓冲,它能扛住大量写入,让下游消费端按自己的速度处理,不至于流量高峰时把 HDFS 或计算集群打崩。
我在实际项目里的做法是:Flume 采集的日志先写入 Kafka,由 Kafka 统一暂存,再用 Spark Streaming 消费并清洗。为什么中间非要加一个 Kafka?直接 Flume 写 HDFS 不行吗?行,但不够稳。如果 Flume 和 HDFS 之间网络抖动,写 HDFS 失败会导致数据丢失或重复;有了 Kafka,Flume 只要能写入 Kafka 就算成功,下游处理失败可以重放,Kafka 的副本机制保证了数据不丢。
业务库数据同步方面,Sqoop 的经典用法是把 MySQL/Oracle 表导入 HDFS,再通过 Hive 建表查询。但 Sqoop 的增量同步能力比较弱,而且依赖 JDBC 直连,数据量大时性能一般。现在更常见的是用 CDC 工具监听数据库日志,实时写入 Kafka,再落数仓。不过从理解生态的角度,Sqoop 还是值得学的,它能帮你理解“导入导出”的基本原理:MapReduce 并行读数据库分区,再写入目标存储。
3.2 Hive:让数据仓库有了“表”的抽象
Hive 解决的问题非常朴素:HDFS 上只有文件和目录,没有表结构,分析师没法直接写 SQL。Hive 通过 Metastore 维护了一张张“表”的元数据,包括字段名、类型、分区、存储格式和 HDFS 路径。你写SELECT * FROM order_log WHERE dt='2024-05-01'时,Hive 会先查 Metastore,推断出order_log对应的 HDFS 路径/data/ods/order_log/dt=2024-05-01,然后把 SQL 翻译成计算任务去跑。
Metastore 是 Hive 的命门。很多人把 Hive 当成“一个 SQL 引擎”,其实更准确地说,Hive 是一套元数据服务体系外加 SQL 翻译器。后来 Spark SQL、Trino 也都能用 Hive Metastore,大家共享同一套表定义。这意味着你在 Hive 里建的表,Spark 里也能查,Trino 里也能查,所以 Metastore 的稳定性和备份一定要重视。我用过的一个集群曾因为 MySQL 里的 Metastore 表被误删,整个数据平台一夜之间看不到任何表结构,排查过程相当痛苦。
建表时要特别注意存储格式。我默认推荐 ORC 或 Parquet 列式存储,配合 Snappy 压缩,查询时只读需要的列,磁盘占用比纯文本省一大半。分区字段要选查询频率高的维度,比如日期、地区,否则SELECT会全表扫描。还有一个小坑:分区表的注释和字段注释最好在建表时写清楚,否则后期数据口径对不上,根本不知道这个字段当初是干嘛的。
3.3 计算引擎层:MapReduce、Spark、Flink 的定位差别
MapReduce 是 Hadoop 原生计算模型,思路是“Map 阶段分而治之,Reduce 阶段汇总”。但它有个致命弱点:每个 Map/Reduce 阶段都要把中间结果落到磁盘,复杂任务会变成一串串磁盘 IO,慢得让人崩溃。所以现在直接用 MapReduce 写作业的人几乎没有了,但 Hive 和 Spark 底层很多时候仍然借鉴了它的分阶段思想。
Spark 的定位是内存计算,把中间结果尽量留在内存里,比 MapReduce 快一到两个数量级。它提供 RDD/DataFrame API,适合复杂 ETL、多表关联、机器学习特征处理。在离线数仓里,Spark SQL 已经能替代大半 Hive 场景,尤其面对几百张小表关联时,Spark 的 SQL 优化器会比老 Hive 好很多。我在一个数仓项目里把 Hive 跑 2 小时的任务换成 Spark SQL 后,只用了 18 分钟,差别非常直观。
Flink 则是真正意义上的流处理,不只是把流切成一段段微批,而是每条数据都能被处理,支持精确一次语义和事件时间机制。如果你需要实时风控、实时大屏、实时预警,Flink 是比 Spark Streaming 更合适的选择。简单说:脸上扛得住几小时甚至天级延迟,选 Spark;要秒级甚至毫秒级响应,选 Flink。两个引擎又能和 Kafka、HDFS、Hive 无缝衔接,所以经常出现在同一套生态里。
3.4 交互式查询:Trino/Impala 和 Hive 到底怎么选
如果分析师写一条 SQL,希望几十秒内出结果,Hive 很可能满足不了,因为它的定位是跑大规模批处理,延迟天然高。这里就需要另一类角色:分布式 SQL 查询引擎,比如 Trino(以前叫 PrestoSQL)和 Impala。它们不把计算结果落盘,而是直接读 HDFS、Hive 表、对象存储,用内存做并行查询,非常适合即席分析。
Trino 的优势是连接器丰富,能同时查 Hive、HBase、Kafka、MySQL 等多个数据源,一条 SQL 完成跨源 join。我一直把它当作“数仓的数据服务出口”:底层数据统一放在 HDFS/Parquet,Trino 负责对外提供快速查询,BI 工具也主要通过它接数。Impala 和 Trino 定位相近,但 Impala 更重度依赖 Hive Metastore,早期在 HDFS 上性能表现很强,部署架构相对更重。选型时看团队基础设施:如果已经有一套 Hadoop 集群且不支持再多部署一个查询引擎,Trino 独立进程更容易扩展。
用 Trino 时要注意内存管理。它不像 Hive 那样把中间结果 spill 到磁盘,而是尽可能堆在内存里,大查询并发一高,Query 很容易被 kill。生产环境要设置好队列并发数和query.max-memory,避免一个大查询把查询引擎内存打满,影响其他分析师使用。
3.5 工作流调度:Oozie、Airflow、DolphinScheduler 怎么选
数仓任务很少只有一个环节,通常是“同步数据 → 清洗 → 宽表加工 → 指标计算 → 导出报表”一串依赖。工作流调度组件就是用来编排这些依赖、自动触发、失败重跑的角色。
Oozie 是老牌 Hadoop 调度器,XML 配置非常啰嗦,已经不太推荐新项目用了。Airflow 以 Python 定义 DAG,灵活强大,但部署在 Hadoop 这样的技术栈里有点“格格不入”,因为大数据平台往往希望用统一界面管理。DolphinScheduler 是我比较常用的一款,它原生支持 HDFS、Spark、Flink 任务,提供可视化 DAG 拖拽编排,还能配置依赖节点、超时告警、失败重试。更关键的是,它和 ZooKeeper、YARN 的兼容性好,任务提交后能直接看 YARN 上的运行状态。
我的建议是:调度器越早接越好,别等项目跑得多了再补。手工用 crontab 临时跑几个脚本,看着没问题,但一旦任务失败需要手动补数时,没有血缘关系记录,哪个表依赖哪张上游表,根本理不清。用 DolphinScheduler 之后,我只要看 DAG 图,就能知道 A 表加工失败会影响哪些下游表,重跑时也只重跑失败分支。
3.6 数据治理与安全:Atlas、Ranger、Kerberos 的重要角色
数据平台规模一大,你一定会面临两个问题:这张表是哪来的?谁能看这张表?这种“数据血缘 + 权限管理”必须有专门组件承接。
Atlas 负责元数据管理和血缘关系。它能自动从 Hive、Spark、Sqoop 任务里采集血缘信息,比如ods_order表被dwd_order_detail表读取加工,整个过程在 Atlas 界面里能看到流向图。数据出现口径问题时,顺着血缘就能找到源头表,不用再翻一坨乱七八糟的 SQL。
Ranger 则负责权限控制,可以给不同用户、用户组设置对 Hive 库表、HDFS 目录、Kafka topic 的访问策略。它和 Hive Metastore、Spark SQL 集成后,用户在客户端执行 SQL 时会被实时拦截校验,避免“某些人能查敏感数据”的问题。Kerberos 是整个系统底层认证的安全基础,能防止“谁能连集群”的问题。很多团队觉得 Kerberos 很难用,密钥管理麻烦,但没认证机制时,任何能访问集群端口的人都能读写数据,安全风险非常大。
我见过一个团队,数据血缘和权限治理是后来补的,结果补起来异常痛苦:历史 SQL 脚本到处散落,无法自动追溯;几十个自然人账户都直接共用同一个 hive 账号,Ranger 根本没法控制到人。所以,如果你的集群未来会接多个团队,尽量在第一天就部署 Ranger 和 Atlas,越晚成本越高。
4. 实操:用一个离线日志分析链路把生态串起来
4.1 场景假设与组件规划
下面我用一个实际做过的模拟项目来说明生态是怎么协同的。假设业务方每天产生约 200GB 的 Web 访问日志,格式是 JSON,每条日志包含user_id、page_url、visit_time、device_type、geo等字段。需求是:每天凌晨产出前一天的全站访问量、各页面 PV/UV、分地域统计,且分析师希望随时能用 SQL 查明细。
组件规划如下:
- 采集:Flume 监听应用服务器日志目录,写入 Kafka 的
web_log主题。 - 缓冲:Kafka 保存最近 3 天数据,副本数 3,分区数 24。
- 清洗入库:Spark Streaming 实时消费 Kafka,过滤脏数据,按日期分区写到 HDFS 的 Parquet 文件。
- 建表:Hive 创建分区外表,Metastore 管理元数据。
- 计算指标:DolphinScheduler 每天早上 1 点触发 Spark SQL,读取前一天分区的明细,生成统计结果表。
- 查询服务:Trino 连接 Hive Metastore,BI 工具通过 Trino 查询指标表。
- 治理安全:Ranger 控制分析师只能读指标表,Atlas 记录来源表到目标表的数据血缘。
这套链路几乎是经典离线数仓的范本,每层角色都靠标准接口衔接到位。
4.2 采集链路实现
Flume 配置一个 TailDir Source 监听日志目录,Sink 使用 Kafka 类型。核心配置类似:
agent.sources = tail agent.sinks = kafkaSink agent.channels = memChannel agent.sources.tail.type = taildir agent.sources.tail.positionFile = /data/flume/position.json agent.sources.tail.filegroups = f1 agent.sources.tail.filegroups.f1 = /var/log/web/access.log.* agent.sinks.kafkaSink.type = org.apache.flume.sink.kafka.KafkaSink agent.sinks.kafkaSink.kafka.topic = web_log agent.sinks.kafkaSink.kafka.bootstrap.servers = node1:9092,node2:9092,node3:9092 agent.channels.memChannel.type = memory agent.channels.memChannel.capacity = 20000 agent.sources.tail.channels = memChannel agent.sinks.kafkaSink.channel = memChannel这里有两个要点。第一,TailDir 能断点续传,通过记录文件位置避免重启后重复读取;第二,Channel 用内存时吞吐高但不持久,如果对数据零丢失有要求,应该换 File Channel。日志采集追求性能,我一般先用 Memory Channel,同时保证 Flume 和 Kafka 都有副本,这样单点故障影响可控。
Kafka 的 topic 设计也要提前想好。分区数最好和下游消费的并行度匹配,24 个分区可以让 Spark Streaming 用 24 个线程并行消费。不要把分区数设得太高,否则无谓地增加文件句柄和网络开销。日志格式建议在源头统一,尽量是扁平 JSON,避免嵌套层次过深。
4.3 用 Spark 做 ETL 并落 Parquet
Spark 消费 Kafka 的方式一般用 DataFrame 读取,然后解析字段。示例代码如下:
val df = spark.readStream .format("kafka") .option("kafka.bootstrap.servers", "node1:9092,node2:9092") .option("subscribe", "web_log") .option("startingOffsets", "latest") .load() val parsed = df.selectExpr("CAST(value AS STRING) AS json_str") .selectExpr( "get_json_object(json_str, '$.user_id') AS user_id", "get_json_object(json_str, '$.page_url') AS page_url", "get_json_object(json_str, '$.visit_time') AS visit_time", "get_json_object(json_str, '$.device_type') AS device_type", "get_json_object(json_str, '$.geo') AS geo" ) .filter("user_id IS NOT NULL AND visit_time IS NOT NULL") val query = parsed.writeStream .format("parquet") .partitionBy("dt") .option("path", "/data/ods/web_log") .option("checkpointLocation", "/data/checkpoint/web_log") .trigger(Trigger.ProcessingTime("1 minute")) .start()这段逻辑里partitionBy("dt")是关键,把每天的数据写进独立分区目录,这样下游查询和清理过期数据都非常方便。还有一点强烈建议:checkpoint 路径一定要放在不常被清理的目录。我用分布式文件系统做 checkpoint,任务崩溃后重启能从上次偏移量继续,不会造成数据重复或丢失。
这里的 JSON 解析用get_json_object最方便,速度也能接受。如果字段特别多,生产环境建议引入 schema,或者用 Protobuf/Avro 序列化,解析效率更高。写完 Parquet 后,要手动执行 Hive 的分区修复,或开启 Spark 的spark.sql.sources.partitionOverwriteMode规范参数,避免重跑时旧分区没清干净导致重复数据。
4.4 统一调度:用 DolphinScheduler 编排任务依赖
实际生产里,Spark Streaming 任务常驻运行,但离线指标计算必须靠调度系统每天定时触发。我通常在 DolphinScheduler 里建一个工作流,流程大概是这样:
- 凌晨 0 点检查
web_log表当天的 Pending 分区文件数量; - 确认数据完整后执行 Spark SQL,从 ODS 层明细加工出 DWD 层访问会话表;
- 再执行指标计算,生成 PV/UV/地域分布;
- 最后做数据质量检查,比如
PV 是否大于昨天同期的 90%,如果不满足则告警。
DolphinScheduler 里可以设置失败重试 3 次、超时 30 分钟自动杀死任务,并通过邮箱和 Webhook 通知运维。配置依赖时最容易忽略的是“上游成功才跑下游”和“上游表分区存在才跑下游”这两个条件。只盯着任务状态还不够,因为任务可能跑完了但数据是空的,所以我会在依赖节点里加一个 Shell 脚本,用 Hive SQL 去SELECT COUNT(*)检查分区行数,低于阈值就抛异常,从而阻止后续任务执行。
调度系统的价值不在于自动执行,而在于让异常可发现、可恢复、可追溯。真正的数据质量保障,是在调度节点里埋检查点实现的。
4.5 权限与血缘:Ranger 和 Atlas 的基本用法
当数仓表越来越多,权限和血缘必须跟上。我用 Ranger 给分析师创建一个analyst_ro用户组,授权它只能查询dws_*开头的表,ODS/DWD 层明细表一律不开放。Ranger 的策略文件要区分“表级授权”和“行级/列级过滤”,敏感用户 ID 和手机号可以做成脱敏策略,让普通分析师看到的是138****1234而不是完整手机号。
Atlas 的集成通常需要安装 Hive Hook、Spark Hook,让 SQL 执行后自动上报血缘。我一般让 Atlas 每半小时同步一次元数据和血缘,并在界面上定期检查核心指标表的血缘链路是否完整。还有个小技巧:给每个 Hive 表打上业务标签和负责人标签,后期做表生命周期管理时,能快速定位哪些表三个月没人访问,可以归档或删除。
没有血缘和权限的数仓,短期看不出问题,但一旦人员流动、业务调整,你就会面对“这表谁建的、能不能删、里面的指标口径是什么”的灵魂三问。所以权限和血缘这种看起来“不直接产出数据”的组件,其实才是长期稳定的关键角色。
5. 常见问题排查:我踩过的那些坑和速查表
5.1 HDFS 小文件太多导致 NameNode 告警
表现:NameNode 堆内存持续上涨,集群进入安全模式。原因:上游把 Kafka 数据按非常小的 batch 写入 HDFS,比如每秒一个文件,一天产生了几十万个文件。解决:在 Spark 写入时开启coalesce,把输出文件控制在个位数到几十个;对已有小文件可以用 Spark 读取目录后按目标分区重写一遍合并。千万注意:不要盲目调大 NameNode 堆内存,那是治标不治本,小文件数量才是根源。
5.2 数据倾斜导致任务长时间跑不完
表现:Spark 任务大部分执行器都结束了,只有一个或几个执行器卡住,Shuffle 读特别大。原因:某些user_id或page_url出现极高频聚合。我常用的三种办法:
- 两阶段聚合:第一轮先把 key 加上随机前缀打散,部分聚合后再去掉前缀聚合;
- 过滤热点数据:先找出高频 key,单独处理,再和其他数据 union;
- 调大 Shuffle 分区数:
spark.sql.shuffle.partitions或repartition,让每个任务负责的数据量减少。
数据倾斜排查的核心是在 Web UI 里观察各 Stage 的输入数据量,而不是只看日志里的堆栈。
5.3 YARN 队列资源死锁,任务全部等待
表现:新提交任务一直处于 ACCEPTED 状态,界面上所有队列 pending 任务扎堆。原因:某个队列最大资源被一个常驻 Flink 任务占满,其他队列又不能再借资源。排查步骤:看 YARN 队列详情,确认每个队列的 used 和 max;用yarn application -list找出常驻任务。解决:调整 Capacity Scheduler 配置,合理设置maximum-capacity和user-limit-factor,给离线批处理和实时任务隔离开。还要注意 Flink 任务申请资源时,如果没有指定yarn.application.maxFailedAttempts,失败重试时也可能把资源吃空。
5.4 Hive 查询慢,分区裁剪没生效
表现:明明查询只选了一天分区,但扫描的文件数是全表上百天的量。原因:分区表没有执行MSCK REPAIR TABLE,Metastore 不识别新分区;或者查询字段和分区字段类型不一致,导致隐式转换。处理:执行MSCK REPAIR TABLE tablename;检查 SQL 里分区字段有没有被函数包裹,比如WHERE dt = date_format(...)会让分区裁剪失效,尽量保持分区字段原始类型直接比较。
5.5 Kafka 消费堆积
表现:Spark Streaming 消费延迟持续加大,Kafka 消息积压到几十 GB。原因:下游 ETL 处理速度跟不上生产速率,或者分区数太少导致消费者线程不够。排查:
- 先看 Kafka consumer group 的 lag,确认是生产猛增还是消费卡住;
- 再看 Spark Executor 的 CPU 和内存使用,排除 GC 频繁;
- 最后看每个分区消费速率是否均衡,如果有热点分区,加分区数并让消费者重新分配。
如果确实是大促流量高峰,临时策略是消费后先写入原始 HDFS,不做过重清洗,等流量过去后再补算;或者直接提升 Spark 作业并行度并增加 Executor 资源。
5.6 小问题速查表
| 现象 | 可能原因 | 快速处理 |
|---|---|---|
| HDFS 写文件报 DiskOutOfSpace | DataNode 磁盘不均衡 | 检查数据倾斜,开启磁盘均衡或扩容 |
| Hive 表字段查出来全是 NULL | 列类型不匹配或文件格式损坏 | 查看 Hive 表 DDL 和实际文件 schema,用desc formatted确认 |
| Spark 任务频繁 Full GC | Executor 内存不足,缓存过大 | 调大spark.executor.memoryOverhead,减少同一 Executor 并发 |
| Trino 查询报 exceeded memory limit | 并发查询太多或单查询太大 | 调低query.max-memory或控制并发数 |
| Ranger 权限改了不生效 | 用户通过 Group 授权但 group 未同步 | 刷新 Ranger user sync,或确认客户端登录用户映射 |
排查问题的第一原则永远是“先看监控再猜原因”。Hadoop 生态每个关键组件都有完善的 Web UI 和日志体系,YARN 看 8088,HDFS 看 9870,ZooKeeper 看 2181,Kafka 看 consumer group 状态。你不一定要背熟所有参数,但至少要知道每个问题应该去哪个界面找证据。
最后再说一个我的个人习惯:学习这套生态时,不要一上来就背组件清单,而是先画一条数据流——从一条日志产生到最后业务报表展示,中间要经过哪几个节点,每个节点由谁负责,数据格式如何变化。先有了这条链路,再去了解每个节点里的组件为什么这么设计、有哪些关键参数,就不会再迷失在一堆技术名词里了。这套生态虽然名字里有“Hadoop”,但它早已不是单一框架,而是一整套相互深度集成的数据基础设施,理解每一层的角色边界,比会安装配置更值钱。