刚接触大数据的人,打开任何一张 Hadoop 生态组件图,第一反应大概率是慌。HDFS、YARN、ZooKeeper、Hive、Spark、Flink、Kafka、HBase……几十个组件名字挤在一张图上,每个名字都眼熟,但真要让你说清楚谁负责存储、谁负责计算、谁负责资源分配、谁负责数据接入,很容易卡壳。我刚开始做大数据开发时也一样,最怕开会时被问一句“你们这条链路里 Hive 和 Spark 到底是什么关系”。那时候我连“Hive 只是个元数据翻译官”这种基本定位都说不利索。
后来在真实项目里踩过不少坑,才慢慢摸清楚,Apache Hadoop 生态其实是一条分工明确的流水线。换一个说法,它更像一个大型工厂的车间:原料要有人采购运输,货品要有人入库上架,加工环节有人负责不同工序,最后还有厂里的调度系统和质检部门。组件之间不是谁替代谁,而是在不同环节承担各自的关键角色。
这篇不打算罗列 API,也不做教程式背诵,而是把 Hadoop 生态以及跟它深度绑定的大数据技术栈当成一个整体系统来看,逐个拆解每个组件的定位。适合三类人:刚转大数据开发、想升级架构视野的工程师、还有被各种组件名搞晕的运维同学。看完你至少能画出自己的架构图,并且能讲清楚为什么每个组件会被放在那个位置。
1. 先摸清 Hadoop 生态主心骨:存储、资源、计算、协调
很多人以为 Hadoop 就是一个软件,其实它是一套生态。整套生态里最底下有四个角色,几乎所有上层组件都要跟它们打交道:HDFS 管数据存哪里,YARN 管计算资源怎么分,MapReduce 是计算引擎的鼻祖,ZooKeeper 负责集群里的协调工作。这四个角色先搞清楚,后面看别的组件就会特别顺。
1.1 HDFS:整个体系的存储底座
数据从进入系统开始,首先面临的问题就是存哪。HDFS 承担的就是这件事。它把一份大文件拆成多个默认 128MB 的块,分散存储到集群的不同节点上,同时由 NameNode 维护整个文件系统的目录结构和块到 DataNode 的映射,DataNode 真正存数据块。
一开始我还觉得 HDFS 不就是“大号的分布式硬盘”嘛,后来才意识到一个关键区别:它是为“大文件、顺序读写、写一次读多次”设计的,不是为随机小数据更新设计的。这也是为什么 HDFS 能在一个几千台节点的集群里保存 PB 级数据,但没法像 MySQL 那样支持快速的单行更新。
操作上有一个很多人容易忽略的点:副本数不必永远保持默认的 3。如果存的是中间结果、临时数据、可由源端重新生成的数据,副本设成 2 甚至 1 都能直接省存储成本。我试过把某些临时实验目录的副本临时改成 1,用完再清理掉,集群存储压力明显下降。但改动之前你必须先回答一个问题:这个数据丢了能不能重新生成?如果不能,老老实实 3 副本,别省这个钱。
1.2 YARN:资源调度的“物业公司”
数据要算,就必须占用 CPU 和内存。YARN 的角色就是统一管理整个集群的资源,把节点上的资源切成一个个 Container,谁要用资源就向 ResourceManager 申请,用完再还回去。上面跑的是 MapReduce、Spark 还是 Flink,对 YARN 来说没有本质区别,都是“一个应用”。
我见过不少团队在早期不认真规划 YARN 队列,所有任务都往 default 队列里塞。结果就是白天报表任务和即席查询挤成一团,晚上离线跑批又抢不过在线服务,最后谁都在等资源。后来我们按业务优先级把队列拆成交互、离线、实验三层,并给每个队列设了最低资源保障和上限,调度立刻顺畅了很多。YARN 队列的划分本质上是在做“资源预算”,这和公司部门预算是一个道理。
1.3 MapReduce:批处理引擎的“老前辈”
很多人一听到 Hadoop 就想到 MapReduce,但其实现在真正把 MapReduce 当作主力计算引擎的团队已经很少了。它的思路是把一个计算任务拆成 Map 和 Reduce 两个阶段,中间结果写磁盘,能处理超大规模数据,但延迟也慢得很明显,一个跑几个小时的批任务很正常。
不过这并不意味着可以完全无视它。MapReduce 把分布式计算“分而治之”的思想讲得最清楚,Hive 早期的执行引擎也是它,很多老集群里还挂着用 MapReduce 写的定时任务。理解它的 Map-Shuffle-Reduce 流程,再去看 Spark 的 Shuffle 设计就会容易很多,因为 Spark 优化的正是 MapReduce 中间结果落盘的短板。
1.4 ZooKeeper:集群里的“协调大脑”
ZooKeeper 在企业里很少被单独提起,但 Hadoop 生态一多半的高可用机制都离不开它。HDFS 的 NameNode 做主备切换时,Active 和 Standby 谁当主,是依赖 ZooKeeper 做选主和状态记录;Kafka、HBase 的元数据和主节点选举也频繁用到它。简单说,ZooKeeper 提供的是分布式环境下的命名、配置、锁和集群成员管理。
时刻记住 ZooKeeper 本身不是数据库,它保存的数据量很小,主要用于协调。别把业务数据往里面塞,我见过有人图省事用它存配置之外的业务状态,数据量一大,整个集群稳定性立刻受到影响。
2. 数据接入层:哪些组件承担“入口”角色
数据必须先进来,才能谈存储和计算。接入层的组件负责把散落在各处的数据,比如服务器日志、业务数据库记录、埋点上报数据,统一搬运到 Hadoop 生态里。这一层的选型往往决定了后续数据链路的稳定性和数据质量。
2.1 Flume:日志采集的“搬运工”
Flume 处理的是日志类数据的采集,它的架构是 Source 读、Channel 缓冲、Sink 写。Source 可以从日志目录、监控端口拿数据,Channel 负责暂存,Sink 再把数据写到 HDFS、Kafka 或别的下游。用 Flume 采集服务器日志到 Hadoop 集群是经典做法。
但它有个被吐槽了很多年的问题:如果 Flume 直接写 HDFS,会对文件进行滚动,默认可能会导致大量小文件。小文件对 NameNode 的内存是致命打击,也会拖慢后续计算。我的经验是,日志链路里不要让 Flume 直接对接 HDFS,中间加一层 Kafka,让 Flume 先写 Kafka,由下游消费端按批次落 HDFS,文件数量会可控得多。
2.2 Kafka:整套实时链路的“缓冲中枢”
Kafka 表面上是一个消息队列,但在大数据技术栈里,它更像整条实时链路的“定海神针”。生产端把数据源源不断写进 Topic,消费端按自己的进度去读,生产和消费之间彻底解耦。上游再大的流量洪峰,到了 Kafka 这里都会被削平,不会直接打垮下游。
Topic 的分区设计很需要经验。Kafka 只能保证分区内有序,所以如果你要求全局有序,只能用一个分区,但那样并发就上不去了。大部分业务并不需要全局有序,按订单 ID、用户 ID、设备 ID 来做键,把同一条业务线的数据路由到同一分区就好。我在实操中一般让分区数量跟下游消费并行度匹配,比如消费并发 6,分区数设为 6 的倍数,尽量不做消息积压下的“临时加分区”,因为加完分区后 key 的分布不一定均衡。
2.3 Sqoop 与 DataX:关系库和数仓之间的“摆渡人”
传统业务数据在关系型数据库里,要把它们搬进 Hadoop,最常用的就是 Sqoop 和 DataX。Sqoop 是 Hadoop 生态里的老组件,通过 JDBC 把关系库表导入 HDFS 或 Hive,但实际用起来经常要踩驱动兼容、性能不稳定的坑。DataX 是更现代的替代方案,插件式架构,对各种数据源支持更完善,速度也快,我所在的团队已经全面用 DataX 接管原来的 Sqoop 任务。
值得一提的变化是,现在很多新增的“数据同步”不只是离线批量抽取,还要考虑变化数据捕获,比如依赖数据库日志做实时增量。这类组件和 Kafka 深度联动,已经成了实时数仓里不可缺失的一环。如果你还在设计架构,老实地把“离线批量迁移”和“实时增量同步”分开考虑,而不是用一套方案吃天下。
3. 存储层角色再细分:Hive 元数据、文件格式与 HBase
数据存进 HDFS,但 HDFS 本身不理解“表”是什么。存储层要做的事,就是在这个文件系统之上,把原始文件包装成表、库、分区这些可以被业务理解的概念,同时根据查询需求选择合理的文件格式,并为一些特殊场景配套专门存储系统。
3.1 Hive:给文件加一层“表”的外衣
Hive 的核心价值不在存储,而在元数据管理。它把 HDFS 上的文件目录映射成逻辑上的数据库表,用类 SQL 的 HiveSQL 去表达计算逻辑。很多人误以为 Hive 存数据,实际上数据实体还是 HDFS 文件或对象存储,Hive 只是给这些文件起了一个“表”的名字,把字段结构、分区信息记进元数据库。
这个定位特别容易理解错。我在带新人的时候,第一个问题就是“Hive 和 MySQL 区别是什么”,能答出“Hive 的存储本质是文件、查询本质是分布式离线计算”的,基本已经掌握了大数据的门道。分区表的用法也是关键,日志类数据按日期分区,查询时如果能修剪掉大部分分区,扫描数据量会下降一个量级,这比任何执行引擎优化的效果都明显。
3.2 文件格式与压缩:性价比最高的“隐形调优点”
同一份数据,用 TextFile 存和用 Parquet 存,查询性能差很远。TextFile 可读、简单,但空间占用大、扫描慢。ORC 是 Hive 的最佳搭档,列式存储、带索引,Hive on Tez 表现优秀。Parquet 则兼容性更好,和 Spark、Hive、Trino 以及各种现代引擎都相处得不错。我的选型原则简单粗暴:Hive 主导的离线数仓用 ORC,团队有多引擎查询诉求就用 Parquet。
压缩格式也要一起定。Snappy 压缩速度和解压速度平衡,是日常首选;Zstd 的压缩率更高,但 CPU 开销更大。如果你的数据量非常大且基本不重读,可以用 Zstd 换空间;如果是被反复扫描的中间数据,Snappy 更合适。还有一点容易踩坑:同一份 ORC 和 Parquet 文件不能在不同引擎间直接互换使用,格式选型一旦定下来,改动成本很高,这里最好提前想清楚并写进规范。
3.3 HBase:面向实时点查的“分布式键值库”
HBase 也依赖 HDFS,但它的定位和普通“表”完全不一样。它本质上是一个分布式多维键值库,适合对海量数据进行实时随机读写,比如订单消息的流水查询、设备状态查询。HBase 把同一个 RowKey 的数据放在一个 Region 内,所以 RowKey 的设计直接决定查询性能。
这里有个很常见的坑:如果直接用自增 ID 或毫秒时间戳做 RowKey,新写入的数据会集中在同一个 Region,形成写热点。我一个比较常用的技巧是用“用户 ID 反序 + 时间戳”作为 RowKey,把连续的数据打散到不同 Region。设计 HBase 表的时候多花十分钟,运行后能少熬很多夜。同样,HBase 不适合做复杂聚合分析,那不是它该干的活,上层聚合还是交给计算引擎。
3.4 数据湖表格式:Iceberg、Hudi 与 Delta Lake 的补充角色
这几年你还会频繁看到 Iceberg、Hudi、Delta Lake 这几个名字。它们不是存储引擎,而是管理 HDFS 或对象存储上文件的一层“表格式”。数据真正还是落在文件里,但由这层格式来管理哪些文件在哪个快照下可见,从而带来事务、时间旅行、增量读取和自动小文件合并。
如果只是做离线批处理,Hive 表已经足够。但一旦实时链路想与离线链路共用一份数据,同时希望数据能更新、支持“读到某一个时刻的一致视图”,数据湖表格式就非常有价值。我建议新建项目时,如果没有历史包袱,可以直接考虑把底层数据放在这种表格式上,而不是再用老的 Hive 目录方式,后续扩展会省很多事。
4. 计算引擎层:离线、实时、交互式谁说了算
数据存得再好,最终要变成结果,还是要靠计算引擎。Hadoop 生态里计算引擎从来不只有一个,不同引擎解决不同问题。这一部分我把离线批处理、实时流计算和即席查询三条线的代表引擎拆开讲,重点说清楚它们各自承担的关键角色。
4.1 一个核心认知:存储是存储,计算是计算
在 Hadoop 生态里,存储和计算是可以分离的。同一份 HDFS 文件,既可以被 Spark 跑批,也可以被 Flink 跑流,还能被 Trino 做即席查询。它们读取的是同一份底层数据,只是执行模式和延迟不一样。这个认知想通后,你就不会再问“Spark 和 Hadoop 是什么关系”这种问题了。
4.2 Spark:离线批处理的“实际主力”
真正让 Spark 成为 Hadoop 生态标配的,是它在内存里做计算。MapReduce 的中间结果要写磁盘,Spark 尽量把中间结果留在内存,所以很多离线 ETL、指标加工的任务,落地到 Spark SQL 非常顺手。Spark 本身不承担存储,它的地位是计算引擎;它甚至连数据源都可以透明地衔接 HDFS、Hive 表、Kafka 等。
操作上最常踩坑的是内存参数。executor 内存设太大,容易撑爆节点;设太小,Shuffle 阶段频繁溢写磁盘。我的基本做法是先按单 executor “2~4 核 + 4~8G”起步,再根据任务 Shuffle 量做微调,同时关注并行度设置,避免出现大量空闲 executor 等着个别任务跑完的情况。
4.3 Flink:实时计算里的“事实标准”
如果说 Spark 是离线批处理的主力,那 Flink 就是实时计算的事实标准。它的核心优势是真正的流式处理,数据一条条进、按事件时间处理,自带的分布式状态和 Checkpoint 机制能把计算进度定期保存下来,进程挂掉后可以从最近一次快照恢复,这恰好是流式场景里最需要的准确性保障。
用 Flink 的时候,我最重视的是 Watermark 设计。乱序数据如果没有合理的 Watermark 甚至直接不做事件时间语义,统计结果会在晚到数据出现时产生偏差。很多业务方抱怨实时指标“对不上”,根因往往在这里。真实场景里我更推荐按业务容忍的延迟来设置 Watermark,比如允许数据迟到 30 秒,再配合窗口计算,既不会太慢,也不会漂移太多。
4.4 Trino/Presto:即席查询的“加速器”
Trino 就是借了 Presto 的衣钵重生的项目,面向的是“对着一套大数仓,快速跑出一条 SQL 结果”的场景。它是 MPP 架构,把 SQL 拆到多节点并行执行,不落中间结果,所以特别适合交互式查询、临时取数、数据探查。
它和 Spark 的取舍很明确:如果你要定义一个跑批任务,自动调度每天执行,那是 Spark 的地盘;如果业务方临时问一句“昨天某类订单分时趋势是什么”,用 Trino 查最舒服。我们团队的做法是数仓工程师写好的正式报表任务用 Spark 调度,而对外的 SQL 查询平台接 Trino,两边分工明确,互不干扰。
5. 调度与治理层:没有它们,集群迟早变成一团乱麻
计算引擎只是干活的人,真正让整个体系有条不紊运转的,是调度与治理层。任务什么时候跑、跑挂了怎么重试、表由谁管理、谁能读谁不能读,这些如果不提前想好,集群规模一大就会失控。
5.1 定时任务编排:从 Oozie 到 Airflow 的取舍
Hadoop 时代的调度组件不少,Oozie 是 Hadoop 原生的,但用 XML 定义工作流,写起来非常痛苦。Azkaban 简单直接,Hadoop 任务用起来方便,但扩展能力一般。现在更多团队选择 Airflow,用 Python 代码定义 DAG,依赖控制、重跑、告警都做得成熟,即使完全不用 Hadoop,单独做数据任务编排也很顺手。
调度层最重要的是幂等。每个任务重跑后不能产生重复数据,不然调度系统一重跑,数仓里就会出现脏数据。我们约定所有任务按分区写入,跑失败后清空目标分区再重跑,从源头避免重复。
5.2 元数据、血缘与权限治理:数据质量的生命线
集群规模一大,谁也记不住每张表是谁建的、字段含义是什么。元数据管理工具把表、字段、负责人、变更记录沉淀下来,数据血缘工具则把 SQL 任务之间的上下游关系画成图谱。权限控制层管的是谁能读哪张表、谁能写哪个库。这三者合起来就是一个团队数据治理的底座。
我见过最混乱的团队,权限只能靠 DBA 手动给,新同事加权限等半天,离职同事的权限又回收不干净。上了统一的权限服务后,所有权限申请走工单,审批后自动生效,表血缘也自动能查,我才真正体会到治理层是省人力而不是增加麻烦。对数据平台来说,计算快只是体验的一部分,数据可信、权限可控才是长期稳定的前提。
6. 选型组合与常见坑:两套模板加四个高频问题
最后给可以直接用的内容。选型这件事没有标准答案,但根据公司业务形态,至少可以套两套成熟模板,再围绕高频问题做排查和优化。
6.1 两套可以直接参考的组合模板
离线数仓和实时数仓的搭建逻辑并不相同,我常用下面两套模板。
| 场景 | 数据接入 | 存储/表格式 | 计算 | 查询 |
|---|---|---|---|---|
| 离线数仓 | DataX 批量同步、Flume 采集 | HDFS + Hive 分区表(ORC/Parquet) | Spark SQL 跑批 | Trino 即席查询 |
| 实时数仓 | Kafka 接入,Flink/CDC 同步 | Kafka + HBase/对象存储 + 数据湖表格式 | Flink 实时加工 | Doris/ClickHouse 或 HBase 点查 |
离线场景里,业务数据通过 DataX 同步到 HDFS,日志通过 Flume 先进 Kafka 再由批任务落库,Hive 负责建表建模,Spark 负责跑批计算,最后开 Trino 让业务方自己跑查询。实时场景里,Kafka 是核心入口,Flink 负责流式加工,结果落到支持随机查询的 HBase 或 OLAP 引擎,供在线服务实时读取。两套模板各有侧重,并不冲突,很多公司会同时部署两条链路。
6.2 四个高频问题与排查思路
第一个是数据倾斜。现象是任务里某个 Task 卡了几个小时,其他 Task 全部跑完。原因多半是 join 或 group by 的 key 集中在少数值上,比如按某个业务线 ID 汇总时,头部业务线数据量占了 90%。解决办法有加盐拆散、把小表广播、调大并行度。加盐的思路是把热点 key 拆成多处再聚合,实际效果比较直接。
第二个是小文件爆炸。现象是 NameNode 内存占用高,查询越来越慢。产生原因常常是动态分区写入太碎、流式任务频繁写 HDFS、Flume 滚动太短。解决思路是用合并任务把小的分区文件重写,同时调整写入端参数,像 Flink 写 HDFS 时打开行组级合并,Flume 写 Kafka 而不是直接写 HDFS。
第三个是 YARN 队列饥饿。现象是任务提交后一直等待,不是跑不动,而是没资源。根因常是某个大任务把队列资源全占了,或者服务型任务和离线任务抢一份资源。解决思路是按照业务优先级拆队列、设置容量保障和抢占。
第四个是存储格式选错导致跨引擎读取困难。现象是 Spark 能读的数据 Trino 报错,或者 Hive 写出的 ORC 换个引擎读不了。根因大多是混合使用了不同表格式和格式版本。解决办法是在团队内形成统一规范:数仓统一用 Parquet + Snappy/Zstd,实时结果按查询引擎需求单独设置。
排查问题时我的习惯是:先看任务日志,再看资源监控,最后才怀疑代码写错。很多“慢任务”其实不是计算问题,而是资源和数据本身的问题,上来就优化 SQL 反而容易白忙一场。先确认元凶在哪一层,再对症下药。
最后说一点个人体会。做大数据架构,真正重要的不是把组件列表背下来,而是搞清楚每一层到底谁负责什么。一个组件在一个场景里是核心,在另一个场景里可能变成累赘。比如你的数据量其实不大,业务查询也不复杂,硬堆 HDFS 加 Hive 加 Spark 就是给自己找麻烦,一台好点的机器装个数据库就能跑得很舒服。架构的价值永远在匹配业务,而不是堆砌组件。先把系统里的角色定位搞明白,再谈选型和优化,这条路走下来会顺很多。