
1. 搭建前先想清楚这套平台的架构与版本搭配很多朋友拿到一台 Ubuntu 20.04 的机器第一反应就是apt install hadoop。如果你真的这么干大概率会收获一堆版本不匹配的报错连启动都启动不起来。大数据生态本身是一套互相依赖的组件体系Hadoop 提供底层存储和计算调度HBase 在上面做实时读写Hive 把 SQL 翻译成 MapReduce 或者 Spark 任务Spark 又负责跑内存计算。它们彼此之间有严格的服务端版本默契比如 HBase 对 Hadoop 的版本、Spark 对 Hadoop 的编译版本都有讲究。所以我在动手之前花了两天时间核对版本矩阵最后定下来这套方案也是今天这篇博文的核心配置组件版本说明Ubuntu20.04.6 LTS内核 5.15稳定且社区资料多JDKOpenJDK 1.8或 11Hadoop 3.x 官方支持 8 和 11Hadoop3.3.6带 HDFS YARN MapReduceZooKeeper3.7.2配合 Hadoop HA 和 HBaseHive3.1.3元数据库用 MySQL 8.0HBase2.4.17依赖 Hadoop 3.3.x兼容性最好Spark3.3.2内置 Hadoop 3.3.2 客户端开箱即用这套组合踩坑最少。如果你非要用 Hadoop 2.x 配 Hive 3.xHive 的元数据初始化脚本会直接报语法错误如果 HBase 2.4 配 Hadoop 2.7RegionServer 启动后会出现一堆NoSuchMethodError因为 HBase 调用了新版 Hadoop 才有的接口。版本统一后面跑起来才省心。网络拓扑方面我采用的是最常见的“三节点完全分布式 单节点伪分布式验证”的做法先在单机上把 Hadoop 伪分布式跑通确认配置正确后再复制到三台机器上组成集群。这样做的理由很实际——伪分布式模式下排错简单日志集中在一个进程里你能很快分清楚是配置问题还是环境问题一旦进入三节点模式NameNode、DataNode、ResourceManager 分布在多台机器上排查问题的复杂度会成倍上升。端口规划也是提前写好的。大数据平台端口特别多而且 HBase 和 Hadoop 之间有固定的 RPC 端口约定记不住的可以直接参照这张表组件端口用途NameNode9870HDFS Web UINameNode RPC8020客户端访问 HDFSDataNode9864DataNode Web UIYARN ResourceManager8088集群资源 Web UIYARN NodeManager8042单节点资源 Web UIZooKeeper2181分布式协调服务Hive Metastore9083元数据服务HBase Master16010HBase Web UIHBase RegionServer16030RegionServer Web UIHBase RPC16020客户端访问 HBaseSpark Standalone Master7077Spark 集群通信Spark Web UI8080Spark 集群监控页面提前把端口列成表格放在/etc/hosts注释里后续排查网络问题会方便得多。我一开始没做这件事后面 HBase 连不上 HDFS查了半天才发现是防火墙把 8020 端口拦了。2. 基础环境准备JDK、免密登录与目录规范系统层面的准备工作决定了后面所有组件的稳定性这里有三件事必须做扎实固定 IP、配置主机名映射、调整文件句柄限制。2.1 主机名映射与 hosts在三台机器上分别设置主机名我这边用的规划是node01、node02、node03。然后修改/etc/hosts把三台机器的内网 IP 加进去192.168.10.11 node01 192.168.10.12 node02 192.168.10.13 node03为什么要用主机名而不是直接写 IP因为 Hadoop 系列组件在 HA 切换、RegionServer 注册、Metastore 连接时大量使用主机名作为唯一标识如果你在不同配置文件里一会儿写 IP 一会儿写主机名HBase 的hbase.rootdir和 Hive 的元数据会记录不一致的地址后期维护非常痛苦。2.2 JDK 安装我选择的是 OpenJDK 1.8虽然 Ubuntu 20.04 自带的 OpenJDK 11 也可以跑 Hadoop 3.3但 Hive 3.1.3 社区里跑得最稳的还是 JDK 8而且 HBase 2.4 的官方文档明确说“支持 Java 8 或 11”。为了减少变量全平台统一用 JDK 8sudo apt update sudo apt install -y openjdk-8-jdk java -version然后设置JAVA_HOME注意 Hadoop 的hadoop-env.sh默认不识别/usr/bin/java它要求显式指定 JDK 目录。Ubuntu 上 OpenJDK 8 的典型路径是/usr/lib/jvm/java-8-openjdk-amd64echo export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 ~/.bashrc echo export PATH$PATH:$JAVA_HOME/bin ~/.bashrc source ~/.bashrc这里有个容易忽略的坑后续安装的 ZooKeeper、HBase、Spark 都会用JAVA_HOME如果你只改了.bashrc而没有在/etc/profile里同步配置通过 SSH 远程执行启动脚本时环境变量可能丢失导致“shell 里能启动、start-dfs.sh里报找不到 Java”的诡异问题。最稳妥的做法是同时写入/etc/profile和每个组件目录下的conf/*-env.sh。2.3 SSH 免密登录Hadoop 的启动脚本会通过 SSH 到所有节点执行远程命令所以必须配置免密登录。以 node01 为主节点生成密钥并分发到所有节点包括自身ssh-keygen -t rsa -P -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys ssh-copy-id node02 ssh-copy-id node03 chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys配置完后记得逐台验证ssh node01、ssh node02确保不再需要密码。很多第一次搭建的朋友在start-dfs.sh时报Host key verification failed就是因为known_hosts里没有目标主机的公钥记录手动 SSH 一次再退出就能解决。2.4 目录规范大数据组件会产生大量日志和临时文件我建议在每台机器上都建立一套统一目录/opt/bigdata/ # 组件安装目录 /data/hadoop/ # HDFS 数据存储目录 /data/hbase/ # HBase 数据存储目录 /data/hive/ # Hive 数据仓库外部分区目录 /data/spark/ # Spark 事件日志目录 /var/log/bigdata/ # 统一日志目录这么做的好处是后续磁盘扩容和管理权限非常清晰。官方安装包默认把数据写在/tmp下重启机器数据就没了生产环境一定不要用/tmp存 HDFS 数据块。另外HDFS 的 DataNode 数据目录如果做了多块磁盘挂载可以在hdfs-site.xml里用逗号分隔多个路径Hadoop 会自动做磁盘间的负载均衡。3. Hadoop 安装与配置从伪分布式到完全分布式3.1 下载解压与配置环境变量去 Hadoop 官网下载二进制包或者直接使用我验证过的 Apache 镜像地址wget https://archive.apache.org/dist/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz tar -zxvf hadoop-3.3.6.tar.gz -C /opt/bigdata/ ln -s /opt/bigdata/hadoop-3.3.6 /opt/bigdata/hadoop然后配置 Hadoop 的环境变量cat ~/.bashrc EOF export HADOOP_HOME/opt/bigdata/hadoop export HADOOP_CONF_DIR\$HADOOP_HOME/etc/hadoop export PATH\$PATH:\$HADOOP_HOME/bin:\$HADOOP_HOME/sbin EOF source ~/.bashrc同时修改$HADOOP_HOME/etc/hadoop/hadoop-env.sh把JAVA_HOME写死export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd643.2 核心配置文件详解接下来是 Hadoop 最核心的五个配置文件。先看core-site.xmlconfiguration property namefs.defaultFS/name valuehdfs://node01:8020/value /property property namehadoop.tmp.dir/name value/data/hadoop/tmp/value /property /configurationfs.defaultFS决定了整个集群的默认文件系统地址这里的node01:8020必须和 NameNode 所在主机及 RPC 端口一致。hadoop.tmp.dir是 NameNode 持久化元数据和 DataNode 存储数据块的根目录千万别用默认的/tmp/hadoop-${user.name}。然后是hdfs-site.xml伪分布式模式下只要配置 NameNode 和 DataNode 的 Web UI 端口以及副本数configuration property namedfs.namenode.http-address/name valuenode01:9870/value /property property namedfs.replication/name value2/value /property property namedfs.namenode.name.dir/name value/data/hadoop/namenode/value /property property namedfs.datanode.data.dir/name value/data/hadoop/datanode/value /property /configurationdfs.replication在单机伪分布式模式下必须设为 1否则 DataNode 只有一台的时候会一直报块副本不足。如果后续搭建三节点集群再改成 2 或 3。再来看yarn-site.xml这里要注意资源配置。默认的yarn.nodemanager.resource.memory-mb是 8192MB如果你的机器只有 16GB 内存同时还要跑 HBase、SparkYARN 会因为内存不足拒绝为容器分配资源。我在这台测试机上把内存参数调低了configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.resourcemanager.hostname/name valuenode01/value /property property nameyarn.nodemanager.resource.memory-mb/name value4096/value /property property nameyarn.nodemanager.resource.cpu-vcores/name value2/value /property property nameyarn.scheduler.maximum-allocation-mb/name value4096/value /property /configurationmapred-site.xml相对简单主要指定 MapReduce 跑在 YARN 上configuration property namemapreduce.framework.name/name valueyarn/value /property /configuration最后是workers文件在 Hadoop 3.x 中已经替代了旧的slaves文件node01 node02 node033.3 伪分布式启动与验证配置完成后先格式化 NameNodehdfs namenode -format这一步会清空dfs.namenode.name.dir下的所有元数据所以只在第一次搭建或明确要重置集群时执行。格式化后启动服务start-dfs.sh start-yarn.sh jpsjps命令会输出当前节点的 Java 进程伪分布式模式下应该看到NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager五个进程。然后在浏览器访问http://node01:9870和http://node01:8088看到对应页面就说明 Hadoop 基本跑通了。3.4 扩展到完全分布式单机验证通过后把 Hadoop 安装目录用rsync分发到 node02 和 node03注意 worker 文件的 IP 也要同步修改workers文件配置的其实是 DataNode 和 NodeManager 的主机名不需要在每台机器上改rsync -avz /opt/bigdata/hadoop-3.3.6 node02:/opt/bigdata/然后按上面的配置把核心 XML 文件同步到所有节点确保每台机器都有相同的配置。此时 NameNode 只在 node01 上启动DataNode 会在所有workers列出的机器上启动。重启整个集群后通过 HDFS 页面的Datanodes标签页能看到三台节点的状态这就完成了单节点到集群的迁移。4. ZooKeeper 安装与 Hadoop HA 整合Hadoop 单 NameNode 是典型的单点故障NameNode 挂了整个 HDFS 就不可写。要解决这个问题需要引入 ZooKeeper让 Active 和 Standby 两个 NameNode 通过 ZooKeeper 完成自动故障切换。HBase 的 RegionServer 也需要 ZooKeeper 来做分布式协调所以 ZooKeeper 是整个平台里必须提前部署的组件。4.1 ZooKeeper 单机与集群安装下载并解压wget https://archive.apache.org/dist/zookeeper/zookeeper-3.7.2/apache-zookeeper-3.7.2-bin.tar.gz tar -zxvf apache-zookeeper-3.7.2-bin.tar.gz -C /opt/bigdata/ ln -s /opt/bigdata/apache-zookeeper-3.7.2-bin /opt/bigdata/zookeeper进入 ZooKeeper 的conf目录复制一份配置cp zoo_sample.cfg zoo.cfg编辑zoo.cfgtickTime2000 initLimit10 syncLimit5 dataDir/data/zookeeper clientPort2181 server.1node01:2888:3888 server.2node02:2888:3888 server.3node03:2888:3888三个节点的 ZooKeeper 配置相同但每个节点需要在dataDir下建一个myid文件内容分别填 1、2、3mkdir -p /data/zookeeper echo 1 /data/zookeeper/myid启动前有个细节ZooKeeper 集群必须奇数个节点我这里用三台。如果只有两台机器也建议启动三个 ZooKeeper 进程可以部署在同一台机器上做端口隔离否则半数节点宕机就无法选主了。启动命令/opt/bigdata/zookeeper/bin/zkServer.sh start用zkServer.sh status检查选举结果三台机器中会有一台显示leader另外两台显示follower说明 ZooKeeper 集群已经就绪。4.2 Hadoop 自动故障转移配置Hadoop HA 需要配置两件事一是 NameNode 的共享存储二是基于 ZooKeeper 的自动故障转移。共享存储我选了 QJMQuorum Journal Manager它不需要额外搭建 NFS而是让两个 NameNode 通过一组 JournalNode 同步编辑日志更符合分布式环境。在hdfs-site.xml中追加 HA 相关配置property namedfs.nameservices/name valuemycluster/value /property property namedfs.ha.namenodes.mycluster/name valuenn1,nn2/value /property property namedfs.namenode.rpc-address.mycluster.nn1/name valuenode01:8020/value /property property namedfs.namenode.rpc-address.mycluster.nn2/name valuenode02:8020/value /property property namedfs.namenode.http-address.mycluster.nn1/name valuenode01:9870/value /property property namedfs.namenode.http-address.mycluster.nn2/name valuenode02:9870/value /property同时配置 JournalNode 地址和故障转移的代理类property namedfs.namenode.shared.edits.dir/name valueqjournal://node01:8485;node02:8485;node03:8485/mycluster/value /property property namedfs.client.failover.proxy.provider.mycluster/name valueorg.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider/value /property property namedfs.ha.automatic-failover.enabled/name valuetrue/value /property property nameha.zookeeper.quorum/name valuenode01:2181,node02:2181,node03:2181/value /property在core-site.xml中也要把默认文件系统地址改为 HA 服务名property namefs.defaultFS/name valuehdfs://mycluster/value /property4.3 HA 初始化和启动的常见错误初始化顺序很重要我第一次就搞反了结果zkfcZKFailoverController一直在报连接超时。正确的顺序是先在 node01、node02、node03 全部启动 ZooKeeper在 node01 上执行hdfs zkfc -formatZK在 ZooKeeper 里初始化 HA 状态在三台节点上启动 JournalNodehdfs --daemon start journalnode在 node01 上格式化 NameNodehdfs namenode -format启动 HDFS 集群start-dfs.sh在 node02 上同步元数据hdfs namenode -bootstrapStandby把 Active NameNode 的元数据拷贝到 Standby最后启动 YARNstart-yarn.sh。如果你在start-dfs.sh之后看到 NameNode 反复重启多半是dfs.ha.automatic-failover.enabled没有在两个 NameNode 节点上都配置或者 ZooKeeper 客户端的超时参数太短。另外注意hdfs zkfc -formatZK必须在任何 NameNode 格式化之前执行否则 ZooKeeper 里没有初始化 HA 状态ZKFC 无法选举 Active NameNode。5. Hive 安装配置、窗口函数与小文件优化Hive 本质上是一个翻译器把 HiveQL 翻译成 MapReduce 或 Spark 作业。它的核心是 Metastore——存放表结构、分区、字段等元数据的服务。生产环境里我建议开启 Metastore 独立服务模式而不是用默认的嵌入式 Derby因为 Derby 不支持并发连接两个会话同时操作就会锁库。5.1 安装 Hive 并配置 MySQL 元数据库下载解压之后先安装 MySQL 用来存储 Hive 元数据sudo apt install -y mysql-server sudo mysql在 MySQL 里创建 Hive 的元数据库和用户CREATE DATABASE hive_meta CHARACTER SET utf8mb4; CREATE USER hive% IDENTIFIED BY hive_password; GRANT ALL PRIVILEGES ON hive_meta.* TO hive%; FLUSH PRIVILEGES;Hive 需要 MySQL JDBC 驱动把驱动 jar 放到/opt/bigdata/hive/lib/下然后在$HIVE_HOME/conf/hive-site.xml里配置连接信息configuration property namejavax.jdo.option.ConnectionURL/name valuejdbc:mysql://node01:3306/hive_meta?useSSLfalseamp;allowPublicKeyRetrievaltrue/value /property property namejavax.jdo.option.ConnectionDriverName/name valuecom.mysql.cj.jdbc.Driver/value /property property namejavax.jdo.option.ConnectionUserName/name valuehive/value /property property namejavax.jdo.option.ConnectionPassword/name valuehive_password/value /property property namehive.metastore.uris/name valuethrift://node01:9083/value /property /configuration用官方脚本初始化元数据库结构cd /opt/bigdata/hive bin/schematool -initSchema -dbType mysql启动 Metastore 服务后就可以进入 Hive 命令行操作了nohup bin/hive --service metastore bin/hive5.2 表 DDL 与数据分区设计Hive 建表最需要注意的是EXTERNAL和PARTITIONED的组合用法。外部表删掉表结构不会删 HDFS 上的数据文件对数据资产管理非常友好。举个例子CREATE EXTERNAL TABLE ods_user_log ( user_id BIGINT, event_type STRING, event_time TIMESTAMP, page_url STRING ) PARTITIONED BY (dt STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t STORED AS TEXTFILE LOCATION /data/hive/warehouse/ods_user_log;这里把分区字段dt单独定义在列定义之外因为分区字段并不是真正的数据列而是目录名。对应分区目录结构就是/data/hive/warehouse/ods_user_log/dt2025-01-01/ /data/hive/warehouse/ods_user_log/dt2025-01-02/加载数据时用LOAD DATA INPATH或者INSERT OVERWRITE ... SELECT两种方式。我的建议是初次上数用LOAD DATA移动文件快、无计算后续清洗加工用INSERT能触发引擎做类型转换和过滤。5.3 窗口函数实战Hive 从 2.1 开始支持完整的窗口函数语法这是做数据分析时最高频的技能组合。最常见三种场景排名计算SELECT user_id, order_amount, RANK() OVER (PARTITION BY user_id ORDER BY order_amount DESC) AS rk, DENSE_RANK() OVER (PARTITION BY user_id ORDER BY order_amount DESC) AS drk, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_amount DESC) AS rn FROM user_orders;RANK()遇到并列会跳号DENSE_RANK()不跳号ROW_NUMBER()给每一行一个唯一递增编号。如果业务上要给每一行标号也就是标题里那个“给每一行标号”的热词场景直接ROW_NUMBER()即可。开窗后如果想要“每个用户最近一笔订单”用子查询包一层过滤掉rn 1。滚动聚合和累计值SELECT order_date, order_cnt, SUM(order_cnt) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS rolling_7d FROM daily_orders;这里ROWS BETWEEN ... PRECEDING AND CURRENT ROW指定了窗口范围是做天粒度滚动指标的标配写法。注意ORDER BY在窗口函数里决定了累计方向。滑动时间窗口SELECT user_id, event_time, LAG(event_time, 1) OVER (PARTITION BY user_id ORDER BY event_time) AS prev_event_time, LEAD(event_time, 1) OVER (PARTITION BY user_id ORDER BY event_time) AS next_event_time FROM user_events;LAG和LEAD在计算会话时长、前后事件时间差时非常好用比自关联高效一个数量级。5.4 小文件优化小文件问题是 Hive 生产环境的头号杀手。大量几十 KB 的文件会导致 NameNode 内存暴涨、MapReduce 启动 task 的开销远大于数据计算本身。我自己遇到过数仓跑完一张 OD 层表HDFS 上多出十几万个小文件直接把 NameNode 压垮的情况。治本的办法是在写入阶段做合并。Hive 3.x 可以使用merge特性或者通过在建表时设置合理的文件格式来减少文件数。实践中我常用两种方式第一种在跑批任务末尾加一步小文件合并 SQLINSERT OVERWRITE TABLE target_table PARTITION (dt) SELECT ... FROM source_table DISTRIBUTE BY CAST(RAND() * 10 AS INT);DISTRIBUTE BY控制 Reduce 输出的分区数随机分布到 10 个分区最终生成的文件数就控制在 10 个左右。这里的具体数值要根据数据量来定通常每个产出文件控制在 128MB 到 256MB 是最健康的。第二种使用 Hive 的CONCATENATE对 ORC 格式表做文件合并ALTER TABLE target_table PARTITION (dt2025-01-01) CONCATENATE;这个命令不重新跑 MapReduce只做文件块的合并速度快适合处理已经存在的小文件。但前提是表的存储格式为 ORCTEXTFILE 不支持。另外 Tez 或 Spark 引擎下可以开启小任务自动合并的配置Hive 3.x 对应的hive.merge.mapfiles、hive.merge.mapredfiles、hive.merge.size.per.task都需要显式设置。我建议在会话级别统一设置SET hive.merge.mapfilestrue; SET hive.merge.mapredfilestrue; SET hive.merge.size.per.task134217728; SET hive.merge.smallfiles.avgsize134217728;6. HBase 安装与配置、端口清单及 Java API 操作HBase 是列族数据库建立在 HDFS 之上擅长海量数据的随机实时读写。部署 HBase 前Hadoop、ZooKeeper 都必须已经正常运行。6.1 下载与配置wget https://archive.apache.org/dist/hbase/2.4.17/hbase-2.4.17-bin.tar.gz tar -zxvf hbase-2.4.17-bin.tar.gz -C /opt/bigdata/ ln -s /opt/bigdata/hbase-2.4.17 /opt/bigdata/hbase修改conf/hbase-env.sh指定 JDK 路径并关闭 HBase 自带的 ZooKeeper因为我们已经独立部署了 ZooKeeperexport JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export HBASE_MANAGES_ZKfalse再编辑conf/hbase-site.xmlconfiguration property namehbase.rootdir/name valuehdfs://node01:8020/hbase/value /property property namehbase.cluster.distributed/name valuetrue/value /property property namehbase.zookeeper.quorum/name valuenode01:2181,node02:2181,node03:2181/value /property property namehbase.zookeeper.property.clientPort/name value2181/value /property /configuration注意hbase.rootdir的端口要和当前 HDFS 的 NameNode RPC 端口一致。如果集群配置了 HA这里就要写成hdfs://mycluster/hbase否则 HBase 会连不上 HDFS。这也是我见过最多的配置问题。6.2 端口清单与启动验证HBase 涉及的核心端口可以对照下面这张表检查端口服务说明16010HBase Master Web UI查看 Region 分布、表状态16020HBase Master RPC客户端连接和数据读写16030RegionServer Web UI查看单节点 Region 状态2181ZooKeeperRegionServer 注册与 Master 选举启动命令start-hbase.sh启动后执行jps应当看到HMaster和HRegionServer两个进程。进入 HBase Shellhbase shell建表并写入一条数据create test:user, info put test:user, 10001, info:name, zhangsan get test:user, 10001HBase 默认情况下没有命名空间test需要在建表时自动创建。如果你想为不同业务建独立命名空间先执行create_namespace test。6.3 表设计和数据操作重点HBase 表设计有几个容易踩坑的地方。第一个是 RowKey 的设计它决定了数据在 Region 上的分布热点。如果用自增 ID 作为 RowKey写入会全部集中在最后一个 Region 上造成热点。常用做法是对 ID 做哈希取模或加盐前缀// RowKey 加盐将用户 ID 取模分桶后拼上原始 ID String salt String.valueOf(Math.abs(userId % 100)); String rowKey String.format(%02d_%d, Integer.parseInt(salt), userId);第二个是列族数量。列族不是越多越好一个列族对应一个 StoreRegion 刷新时会把所有列族的数据都刷入 HFile。建议单表最多 2~3 个列族把访问频度相近的列放同一个列族。6.4 用 Java 操作 HBase用 Java API 操作 HBase 是面试和工程里的高频题目核心思路是把 HBase 的 Table 封装成 DAO。我写一个最小可运行的示例dependency groupIdorg.apache.hbase/groupId artifactIdhbase-client/artifactId version2.4.17/version /dependencyimport org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; public class HBaseUserDao { private final Connection connection; public HBaseUserDao() throws Exception { Configuration conf HBaseConfiguration.create(); conf.set(hbase.zookeeper.quorum, node01:2181,node02:2181,node03:2181); // 这里用 ShadedConnection 还是原生 Connection 取决于 hbase-client 版本 this.connection ConnectionFactory.createConnection(conf); } public void putUser(String userId, String name) throws Exception { Table table connection.getTable(TableName.valueOf(test:user)); try { Put put new Put(Bytes.toBytes(userId)); put.addColumn(Bytes.toBytes(info), Bytes.toBytes(name), Bytes.toBytes(name)); table.put(put); } finally { table.close(); } } public String getUser(String userId) throws Exception { Table table connection.getTable(TableName.valueOf(test:user)); try { Get get new Get(Bytes.toBytes(userId)); Result result table.get(get); Cell cell result.getColumnLatestCell(Bytes.toBytes(info), Bytes.toBytes(name)); return cell null ? null : Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()); } finally { table.close(); } } }这里要注意两个细节第一Connection是线程安全的重对象整个应用只需创建一次不要每次操作都新建第二Table实例不是线程安全的每次操作后都要关闭建议放在try-with-resources里。如果你用的是 HBase 2.x 的 APItable.close()之后还想复用连接ConnectionFactory管理得不好会泄漏用connection.getTable()每次拿新句柄即可。7. Spark 部署、集群搭建与内存调优7.1 安装与运行模式Spark 的部署灵活度很高可以本地跑、Standalone 集群跑、也可以完全基于 YARN 跑。我的建议是阶段式选择学习验证阶段用 Local 模式平台集成阶段用 Standalone 模式生产阶段再接入 YARN。下载 Spark 3.3.2注意选择预编译好的 Hadoop 版本wget https://archive.apache.org/dist/spark/spark-3.3.2/spark-3.3.2-bin-hadoop3.tgz tar -zxvf spark-3.3.2-bin-hadoop3.tgz -C /opt/bigdata/ ln -s /opt/bigdata/spark-3.3.2-bin-hadoop3 /opt/bigdata/spark配置conf/spark-env.shexport JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export SPARK_MASTER_HOSTnode01 export SPARK_MASTER_PORT7077 export SPARK_WORKER_CORES2 export SPARK_WORKER_MEMORY2g启动 Standalone 集群start-master.sh start-workers.sh访问http://node01:8080能看到 Master 页面中可以看到 Worker 的注册情况。提交一个任务到集群bin/spark-submit \ --master spark://node01:7077 \ --class org.apache.spark.examples.SparkPi \ examples/jars/spark-examples*.jar \ 107.2 读取 JSON 与 Spark SQLSpark 最常用的数据源之一就是 JSON。读取时要注意Spark 会根据 JSON 内容推断 schema如果不同行的字段类型不一致比如某行price是字符串另一行是数字类型推断会给出null或者直接报错。稳妥做法是预先用StructType定义好 schemaimport org.apache.spark.sql.types._ val schema StructType(Seq( StructField(user_id, LongType, true), StructField(order_amount, DoubleType, true), StructField(order_time, TimestampType, true) )) val df spark.read.schema(schema).json(hdfs://node01:8020/data/orders.json) df.createOrReplaceTempView(orders)之后用 Spark SQL 处理业务逻辑SELECT user_id, COUNT(*) AS order_cnt, SUM(order_amount) AS total_amount FROM orders GROUP BY user_idSpark SQL 里的DataFrame操作会被 Catalyst 优化器自动优化比如谓词下推和列裁剪。你只需要保证数据源路径写的是 HDFS 完整路径Spark 就会自动通过 Hadoop 客户端读写 HDFS。如果遇到权限问题记得在启动参数里加上--conf spark.hadoop.fs.defaultFShdfs://node01:8020。7.3 Spark 内存调优实战Spark 作业失败最常见的原因就是 OOM。Spark 的内存由执行内存和存储内存共同组成默认由spark.memory.fraction0.6划分。当执行内存不够时Shuffle 阶段会频繁溢写磁盘当存储内存不够时Cache 的数据会被丢弃重算。我的调优路径是这样的先看 Web UI 的Executors页面确认是哪个 Stage 出问题。如果是 Shuffle 阶段 OOM调大分区数降低单个任务的数据量bin/spark-submit \ --master spark://node01:7077 \ --num-executors 3 \ --executor-cores 2 \ --executor-memory 4g \ --conf spark.sql.shuffle.partitions200 \ --conf spark.default.parallelism200 \ --conf spark.memory.offHeap.enabledtrue \ --conf spark.memory.offHeap.size2g \ app.jar几个参数的含义逐个说清楚--num-executors决定启动多少个执行器--executor-cores是每个执行器占用的 CPU 核数--executor-memory是每个执行器的堆内存spark.sql.shuffle.partitions控制 Shuffle 输出的分区数过小会导致单个 Task 处理的数据量过大过大又会导致调度开销过高。我的经验是分区数参考总数据量 / 每个分区期望处理量每条记录平均 1KB 的话每个分区控制在 100MB~200MB 比较合适。7.4 Spark 集成 HiveSpark 读取 Hive 表时可以把 Spark 的hive-site.xml指向 Hive 的配置文件并拷贝 Hive 的 MySQL JDBC 驱动ln -s /opt/bigdata/hive/conf/hive-site.xml /opt/bigdata/spark/conf/hive-site.xml cp /opt/bigdata/hive/lib/mysql-connector-java-*.jar /opt/bigdata/spark/jars/然后启动 Spark SQLbin/spark-sql在 Spark SQL 里直接SHOW TABLES;就能看到 Hive 里已存在的表。从技术上来说Spark 只是复用了 Hive 的 Metastore并不依赖 Hive 的计算引擎。这也是后面做“Hive 跑批 Spark 实时分析”混合架构的基础。8. 平台联调、数据迁移与 Docker 快速部署经验整套平台搭建完之后最让我头疼的是服务之间的启动顺序和各类隐性故障。整理一下我实际踩过的坑希望能帮你避开。8.1 正确的启动顺序大数据平台不是所有组件一起启动就能正常工作的合理的顺序是启动 ZooKeeper所有机器启动 JournalNode如果是 HA 集群启动 HDFSstart-dfs.sh启动 YARNstart-yarn.sh启动 Hive Metastorenohup bin/hive --service metastore 启动 HBasestart-hbase.sh启动 Sparkstart-master.sh start-workers.sh关闭的顺序正好相反先停 Spark再停 HBase然后停 Hive再停 YARN 和 HDFS最后停 ZooKeeper。如果先停 HDFSHBase 会长时间重试连接 HDFS日志刷屏不说还可能导致 Region 数据丢失。生产环境写一个运维脚本按顺序启停能省很多事。8.2 Hadoop distcp 参数与跨集群数据迁移如果你以后要把数据从一个 HDFS 集群迁移到另一个distcp是最常用的工具。它本质上是一个 MapReduce 任务分布并行地拷贝文件。它的常用参数我在工程里整理过hadoop distcp \ -Dmapreduce.map.memory.mb2048 \ -m 20 \ -bandwidth 100 \ -p rbugp \ -update \ -delete \ hdfs://source-cluster:8020/data/warehouse/tmp/ \ hdfs://target-cluster:8020/data/warehouse/tmp/几个关键参数逐个说-m指定最大 map 数也就是并行度-bandwidth限制每个 map 任务的最大带宽 MB/s适合在跨机房低带宽环境下使用-p rbugp表示保留文件权限、块大小、用户、组和属性Permission、Block size、User、Group、Permission 的 p 具体含义要对照官方文档记忆-update表示只覆盖源端比目标端更新的文件-delete表示删除目标端多出来的文件让目标目录和源完全一致。如果只是追加增量数据建议不要加-delete否则误删目标端的额外文件是常事。8.3 用 Docker 镜像快速验证很多朋友不想在物理机上把环境完全推倒重来这时候可以用 Docker 快速实验。社区里有不少 Hadoop 全家桶镜像直接拉下来就能用docker pull apache/hadoop:3.3.6但要注意Docker 单容器方式默认是伪分布式数据无法持久化容器一删数据就没了。如果是学习和验证可以在容器里跑如果是搭建真实平台还是建议直接装在 VM 或物理机上。我之前用 docker-compose 编排过 Hadoop Hive 的镜像测试很方便但生产环境的性能损耗和数据安全没有保障。8.4 常见故障排查清单最后把我日志里常见的几个报错列出来方便你遇到类似问题时快速定位现象可能原因排查方向DataNode 起不来日志里有Incompatible clusterIDs格式化过 NameNode 但没清理旧 DataNode 数据停止集群备份后删除/data/hadoop/datanode/current/VERSION重新启动HBase RegionServer 连不上 HDFShbase.rootdir里写的端口和 NameNode 端口不一致检查hbase-site.xmlHA 模式下写hdfs://mycluster/hbaseSpark 任务 OOM分区数太少或者 executor 内存不足调大spark.sql.shuffle.partitions调大 executor 内存Hive 执行SHOW TABLES卡死Metastore 没启动或 MySQL 连接数耗尽检查 9083 端口是否有进程登录 MySQL 查连接数ZooKeeper 启动后一个 leader 都没有myid文件没配置或节点间 2888 端口不通检查三台机器的myid检查防火墙我在实际搭建过程中最大的体会就是大数据平台每一步都依赖前一步的正确性Hadoop 配错了后面 HBase 和 Spark 连带的报错往往让你误以为是它们自身的问题。所以不要急着一次装完每装一个组件就验证一个组件等jps、Web UI、Shell 命令都确认通过后再进入下一步。这个习惯是我踩遍了所有坑之后最想告诉你的经验。