1. 为什么要在虚拟机里搭这套三件套集群
先把结论摆在前面:这套组合——Zookeeper + Hadoop + HBase,是很多大数据入门者绕不开的一道坎。你可能是为了跑通一个课程实验,可能是为了本地复现一套分布式存储环境,也可能是为了给上层的数据分析任务准备一个稳定的底座。不管出于什么目的,在单台物理机上用虚拟机搭一套三节点集群,是成本最低、容错最高、也最适合反复折腾的方案。
我选择 Ubuntu 18.04 作为基础系统,不是因为它新,恰恰是因为它“旧得刚刚好”。这个版本的内核、glibc、systemd 行为都非常稳定,社区里针对它的踩坑记录最全,Hadoop 和 HBase 的官方文档也大量以它为参考环境。你换成更新的发行版,反而容易在 SSH、主机名解析、Java 版本这些地方遇到意料之外的兼容问题。虚拟机平台用 VMware Workstation,理由更直接:快照功能太香了。集群搭建过程中你会反复修改配置文件、重启服务、格式化数据目录,一旦某一步把环境搞脏了,回滚快照比逐条排查快十倍。
这套集群能做什么?简单说,Zookeeper 负责分布式协调,Hadoop 的 HDFS 提供底层分布式文件存储,YARN 负责资源调度,HBase 则在这之上提供列式存储和随机读写能力。三者叠起来,你就有了一个可以跑真实分布式程序的迷你环境。适合谁来参考?我认为是三类人:一是刚接触大数据、需要动手理解“分布式”到底怎么回事的学生或转行者;二是需要本地验证 HBase 表结构、RowKey 设计、Region 分裂行为的开发人员;三是想在不依赖云服务的前提下,把整套链路摸清楚的运维初学者。
需要提前说明的是,下面所有操作都基于我自己的实践总结,涉及的主机名、IP 段、目录路径都是示例,你完全可以根据自己的机器情况调整。但调整的时候要理解每一处改动背后的原因,否则出了问题你连从哪查起都不知道。
2. 集群整体设计与资源规划思路
2.1 三节点角色分配与为什么这么分
很多人一上来就问:我能不能只用一个节点?能,但那就失去了搭集群的意义。HBase 强依赖 Zookeeper,Hadoop 的 HDFS 又需要 NameNode 和 DataNode 分离才能体现分布式特性。我的建议是最小化三节点方案,角色分配如下:
| 主机名 | IP 示例 | 承担角色 |
|---|---|---|
| node1 | 192.168.10.101 | NameNode、ResourceManager、HMaster、Zookeeper |
| node2 | 192.168.10.102 | DataNode、NodeManager、HRegionServer、Zookeeper |
| node3 | 192.168.10.103 | DataNode、NodeManager、HRegionServer、Zookeeper |
为什么把主控角色都放在 node1?因为在小集群里,NameNode 和 ResourceManager 本身并不吃太多 CPU,真正吃资源的是 DataNode 和 RegionServer。把主控集中在一个节点,可以减少跨节点通信带来的配置复杂度。Zookeeper 则必须凑够奇数个节点才能形成有效的选举 quorum,三个是最小可用规模。你如果只部署两个 Zookeeper,一旦其中一个挂掉,整个集群就失去多数派,HBase 会直接不可用。
2.2 虚拟机资源配置的取舍
每台虚拟机的配置我建议这样给:
- CPU:2 核起步,4 核更稳。HBase 的 Compaction 和 Hadoop 的 Shuffle 都是吃 CPU 的操作。
- 内存:4GB 是底线,8GB 会舒服很多。Zookeeper 默认堆 512MB,Hadoop 各守护进程加起来约 2GB,HBase 的 RegionServer 默认 1GB,再算上系统本身,4GB 会非常紧张。
- 磁盘:每台 40GB 以上。HDFS 副本系数默认是 3,你存 1GB 数据实际占 3GB,再加上 HBase 的 WAL 和 StoreFile,磁盘消耗比想象中快。
- 网络:VMware 用 NAT 模式即可,关键是三台机器之间要能互相 ping 通,并且能通过主机名解析。
这里有个容易被忽略的点:虚拟机磁盘类型尽量选“单个文件”而不是“拆分成多个文件”,虽然单个文件在宿主机上看起来很大,但读写性能更好,快照管理也更简单。另外,三台虚拟机的时钟要同步,Zookeeper 的选举和 HBase 的租约机制都对时间敏感。你可以在每台机器上装 ntp 服务,或者干脆在 VMware 里开启时间同步。
2.3 版本选型的逻辑
版本搭配是这套集群最容易翻车的地方。我的经验是:不要追最新版,要选经过大量验证的稳定组合。以下是我实测下来比较稳的一套:
- Ubuntu 18.04.6 LTS
- JDK 8(必须是 8,Hadoop 2.x 和 HBase 1.x/2.x 对 JDK 9 以上支持很差)
- Hadoop 2.7.7 或 3.1.x
- HBase 1.4.x 或 2.1.x
- Zookeeper 3.4.14 或 3.5.7
为什么强调 JDK 8?因为 Hadoop 2.x 的很多脚本里硬编码了对tools.jar的依赖,而 JDK 9 之后这个包被移除了。你如果非要用高版本 JDK,就得自己改脚本,得不偿失。HBase 和 Zookeeper 虽然对 JDK 版本宽容一些,但为了统一环境,全部用 JDK 8 最省心。
3. 基础环境准备与系统调优
3.1 主机名、Hosts 与 SSH 免密
三台机器装好 Ubuntu 之后,第一件事是改主机名。在 node1 上执行:
sudo hostnamectl set-hostname node1node2 和 node3 同理。改完之后记得重启或者重新登录,让提示符生效。接着编辑每台机器的/etc/hosts,把三台机器的 IP 和主机名对应关系写进去:
192.168.10.101 node1 192.168.10.102 node2 192.168.10.103 node3这一步极其重要。Hadoop 和 HBase 的配置文件里到处都在用主机名,如果解析不了,服务启动时会报UnknownHostException,而且报错信息往往很隐晦,新手很难定位。
SSH 免密是集群操作的基石。在 node1 上生成密钥对:
ssh-keygen -t rsa一路回车即可。然后把公钥分发到三台机器(包括 node1 自己):
ssh-copy-id node1 ssh-copy-id node2 ssh-copy-id node3验证方式很简单,在 node1 上执行ssh node2,如果不需要密码就能登录,说明配置成功。这里有个坑:有些教程让你只做 node1 到其他节点的免密,但 HBase 的启动脚本可能会从任意节点发起 SSH 连接,所以三台机器之间最好两两免密。最省事的做法是在每台机器上都生成密钥,然后互相 copy。
3.2 JDK 安装与环境变量
我习惯把 JDK 装在/usr/local/java目录下。下载好 JDK 8 的压缩包后:
sudo mkdir -p /usr/local/java sudo tar -zxvf jdk-8uXXX-linux-x64.tar.gz -C /usr/local/java然后编辑/etc/profile,在末尾追加:
export JAVA_HOME=/usr/local/java/jdk1.8.0_XXX export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar执行source /etc/profile后,用java -version验证。三台机器都要做同样的操作。注意JAVA_HOME的路径一定要写对,后面 Hadoop 和 HBase 的配置都会引用这个变量。
3.3 关闭防火墙与 SELinux 的考量
Ubuntu 默认没有 SELinux,但防火墙可能是开启的。为了避免端口被拦截,我通常直接关闭 ufw:
sudo ufw disable有人会问:这样安全吗?在实验环境里,虚拟机处于 NAT 网络内部,不直接暴露公网,关闭防火墙是可以接受的。但如果你是在桥接模式下并且网络环境复杂,建议不要关闭,而是按需放行端口。Hadoop 和 HBase 涉及的端口非常多,逐个放行很麻烦,实验环境下关闭是最实际的选择。
3.4 时间同步与文件句柄数调整
时间同步前面提过,这里补充具体操作。安装 ntp:
sudo apt-get install ntp然后编辑/etc/ntp.conf,指定一个可靠的时间源。如果实验环境没有外网,可以让 node1 作为时间服务器,node2 和 node3 同步 node1 的时间。
文件句柄数也要调。HBase 在数据量大时会打开大量文件,默认的 1024 不够用。编辑/etc/security/limits.conf,追加:
* soft nofile 65536 * hard nofile 65536改完后重新登录生效。你可以用ulimit -n查看当前值。
4. Zookeeper 集群部署与验证
4.1 安装与配置文件的写法
把 Zookeeper 解压到/usr/local/zookeeper,然后进入conf目录,复制一份配置模板:
cp zoo_sample.cfg zoo.cfg编辑zoo.cfg,核心内容如下:
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/usr/local/zookeeper/data dataLogDir=/usr/local/zookeeper/logs clientPort=2181 server.1=node1:2888:3888 server.2=node2:2888:3888 server.3=node3:2888:3888这里解释几个关键参数。tickTime是 Zookeeper 的基本时间单元,单位毫秒,其他超时时间都是它的倍数。initLimit是 follower 与 leader 建立连接时的最大心跳数,syncLimit是 follower 与 leader 之间同步的最大心跳数。dataDir和dataLogDir建议分开,因为事务日志的写入频率很高,分开存放可以减少 I/O 竞争。
server.N的配置里,2888是 follower 连接 leader 的端口,3888是选举端口。这两个端口不要和其他服务冲突。
4.2 myid 文件与启动顺序
在dataDir指定的目录下,每台机器创建一个myid文件,内容分别是 1、2、3,对应zoo.cfg里的server.N编号。这个文件不能有扩展名,也不能有多余的空格或换行。
启动顺序没有严格要求,但建议先启动 node1,再启动 node2 和 node3。每台机器执行:
/usr/local/zookeeper/bin/zkServer.sh start然后用zkServer.sh status查看状态。你应该看到一台是leader,另外两台是follower。如果三台都是standalone,说明它们没有组成集群,大概率是zoo.cfg或myid配错了。
4.3 用客户端验证集群一致性
在任意一台机器上执行:
/usr/local/zookeeper/bin/zkCli.sh -server node1:2181进入客户端后,创建一个节点:
create /test "hello"然后在另一台机器上连接,执行get /test,如果能读到hello,说明集群数据同步正常。这个验证步骤很多人会跳过,但它能提前暴露很多配置问题。
注意:Zookeeper 的
dataDir目录如果被误删,重启后 myid 文件也会丢失,服务会启动失败。建议把 myid 文件的内容记下来,或者干脆把整个 dataDir 纳入快照管理。
5. Hadoop 集群配置与启动
5.1 核心配置文件逐项拆解
Hadoop 的配置集中在etc/hadoop目录下。需要修改的文件有:core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml,以及hadoop-env.sh和slaves文件。
先看core-site.xml:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://node1:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/usr/local/hadoop/tmp</value> </property> </configuration>fs.defaultFS指定了 HDFS 的访问入口,客户端和 DataNode 都通过这个地址找到 NameNode。hadoop.tmp.dir是 Hadoop 的临时目录,很多运行时数据都放在这里,建议显式指定,不要用默认的/tmp,因为系统重启可能会清理/tmp。
hdfs-site.xml:
<configuration> <property> <name>dfs.replication</name> <value>3</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/usr/local/hadoop/data/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/usr/local/hadoop/data/data</value> </property> </configuration>dfs.replication设为 3,因为我有三个 DataNode。如果你只有两个 DataNode,设 3 会导致副本永远无法满足,HDFS 会一直报副本不足的警告。name.dir和data.dir分开存放,便于管理和备份。
mapred-site.xml(需要先从模板复制):
<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration>这表示 MapReduce 任务跑在 YARN 上,而不是本地模式。
yarn-site.xml:
<configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>node1</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> </configuration>yarn.nodemanager.aux-services必须配成mapreduce_shuffle,否则 MapReduce 任务会卡在 shuffle 阶段。
5.2 环境变量与 slaves 文件
在hadoop-env.sh里显式指定JAVA_HOME:
export JAVA_HOME=/usr/local/java/jdk1.8.0_XXX虽然系统环境变量里已经有了,但 Hadoop 的启动脚本有时不会加载/etc/profile,显式写一遍最保险。
slaves文件(Hadoop 3.x 里叫workers)里写上:
node2 node3这告诉启动脚本哪些节点要启动 DataNode 和 NodeManager。注意不要把 node1 写进去,因为 node1 是主控节点,不承担数据存储角色。
5.3 格式化与启动的先后逻辑
第一次启动前必须格式化 NameNode:
hdfs namenode -format这个操作只能做一次。如果你重复格式化,NameNode 的集群 ID 会变,而 DataNode 还保留着旧的集群 ID,导致 DataNode 无法注册。如果确实需要重新格式化,必须先把所有节点的data目录清空。
启动 HDFS:
start-dfs.sh这个脚本在 node1 上执行,它会通过 SSH 连接到 node2 和 node3 启动 DataNode。启动完成后,用jps检查进程:node1 上应该有 NameNode 和 SecondaryNameNode,node2 和 node3 上应该有 DataNode。
启动 YARN:
start-yarn.shnode1 上会出现 ResourceManager,node2 和 node3 上会出现 NodeManager。
5.4 用 Web UI 和命令验证
HDFS 的 Web UI 默认在http://node1:50070(Hadoop 3.x 是 9870),YARN 的在http://node1:8088。打开浏览器能看到集群概况,重点看 Live Nodes 是不是 2,也就是 node2 和 node3。
命令行验证:
hdfs dfs -mkdir -p /test hdfs dfs -put /etc/hosts /test/ hdfs dfs -cat /test/hosts如果文件能上传并读取,说明 HDFS 工作正常。
实操心得:
start-dfs.sh启动时如果卡住不动,大概率是 SSH 免密没配好,或者/etc/hosts解析有问题。可以先手动ssh node2试试,确认能免密登录再启动。
6. HBase 集群部署与联调
6.1 HBase 与 Hadoop、Zookeeper 的关系
HBase 是构建在 HDFS 之上的列式数据库,它自己不存储实际数据,而是把数据以 HFile 的形式存在 HDFS 上。同时,HBase 依赖 Zookeeper 来管理 RegionServer 的状态、Master 的选举以及元数据的定位。所以 HBase 的配置必须正确指向已有的 HDFS 和 Zookeeper 集群。
6.2 hbase-site.xml 的关键配置
编辑conf/hbase-site.xml:
<configuration> <property> <name>hbase.rootdir</name> <value>hdfs://node1:9000/hbase</value> </property> <property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <property> <name>hbase.zookeeper.quorum</name> <value>node1,node2,node3</value> </property> <property> <name>hbase.zookeeper.property.dataDir</name> <value>/usr/local/zookeeper/data</value> </property> </configuration>hbase.rootdir必须和core-site.xml里的fs.defaultFS保持一致,否则 HBase 会写到本地文件系统而不是 HDFS。hbase.cluster.distributed设为 true 表示分布式模式。hbase.zookeeper.quorum列出所有 Zookeeper 节点。
这里有个细节:HBase 自带了一个 Zookeeper 实例,如果你用外部的 Zookeeper,需要把hbase.zookeeper.property.dataDir指向外部 Zookeeper 的 dataDir,并且确保 HBase 不会去启动自己的 ZK。更稳妥的做法是在hbase-env.sh里设置:
export HBASE_MANAGES_ZK=false这明确告诉 HBase 不要管理 Zookeeper,用我们之前搭好的那套。
6.3 regionservers 与环境变量
在conf/regionservers文件里写入:
node2 node3这和 Hadoop 的 slaves 文件类似,指定哪些节点启动 HRegionServer。
hbase-env.sh里同样要指定JAVA_HOME,并且可以调整堆大小:
export JAVA_HOME=/usr/local/java/jdk1.8.0_XXX export HBASE_HEAPSIZE=1024如果内存紧张,可以把HBASE_HEAPSIZE调小,但不要低于 512MB。
6.4 启动 HBase 与验证读写
启动命令:
start-hbase.sh启动后,node1 上应该有 HMaster,node2 和 node3 上应该有 HRegionServer。用jps确认。
进入 HBase shell:
hbase shell创建一个测试表:
create 'test_table', 'cf'插入数据:
put 'test_table', 'row1', 'cf:name', 'value1'读取数据:
get 'test_table', 'row1'如果能看到value1,说明 HBase 集群工作正常。你还可以在 HDFS 上查看/hbase目录,应该能看到 HBase 的文件结构。
注意:HBase 启动时如果报
Connection refused连接 Zookeeper 失败,先检查 Zookeeper 是否正常运行,再检查hbase-site.xml里的 quorum 配置是否和实际主机名一致。
7. 常见问题与排查技巧实录
7.1 启动失败类问题速查
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| DataNode 启动后立即消失 | 集群 ID 不一致 | 查看 DataNode 日志,确认是否提示ClusterId mismatch,清空 data 目录重新格式化 |
| HBase Master 启动失败 | Zookeeper 连接不上 | 用zkCli.sh手动连接 ZK,确认 quorum 配置 |
| YARN 任务卡在 ACCEPTED | NodeManager 未注册 | 检查 node2/node3 的 NodeManager 日志,确认 ResourceManager 地址 |
| SSH 启动脚本卡住 | 免密未配置或 hosts 解析失败 | 手动 ssh 测试,检查/etc/hosts |
7.2 端口冲突与进程残留
Hadoop 和 HBase 涉及的端口很多,如果你之前装过其他服务,可能会冲突。比如 8080 端口经常被 Tomcat 占用,而 YARN 的 Web UI 默认也是 8088,一般不会冲突,但 50070 可能被其他 Hadoop 实例占用。排查方法是:
netstat -tlnp | grep 端口号如果发现进程残留,用jps找到进程号,然后kill -9强制结束。但要注意,直接 kill 可能会导致数据损坏,尤其是 NameNode 和 HBase Master。更优雅的方式是用对应的 stop 脚本。
7.3 时间不同步引发的诡异问题
Zookeeper 的选举对时间非常敏感。如果三台机器的时间差超过tickTime的若干倍,follower 会认为 leader 失联,触发重新选举。表现就是集群频繁切换 leader,HBase 连接时断时续。排查方法是:
date在三台机器上分别执行,看时间是否一致。如果不一致,检查 ntp 服务是否正常运行。
7.4 内存不足导致的 OOM
虚拟机内存给得不够时,Java 进程会被系统 OOM Killer 杀掉。表现是某个服务莫名其妙消失,日志里可能有Killed字样。排查:
dmesg | grep -i kill如果确认是 OOM,要么加内存,要么调小各服务的堆大小。Hadoop 的HADOOP_HEAPSIZE、HBase 的HBASE_HEAPSIZE、Zookeeper 的JAVA_OPTS都可以调整。
7.5 格式化 NameNode 后的连锁反应
前面提过,重复格式化 NameNode 会导致 DataNode 无法注册。如果你不小心格式化了,解决办法是:停止所有 Hadoop 服务,清空所有节点的dfs.datanode.data.dir目录,然后重新启动。注意,这会丢失 HDFS 上的所有数据,所以格式化前一定要确认。
8. 集群日常维护与扩展思路
8.1 快照管理与回滚策略
VMware 的快照是这套环境的救命稻草。我的习惯是:每完成一个阶段就拍一个快照,比如“基础环境就绪”“Zookeeper 集群正常”“Hadoop 集群正常”“HBase 集群正常”。这样一旦后续操作把环境搞坏,可以精确回滚到某个健康状态,而不用从头再来。
快照命名要有意义,不要用“快照1”“快照2”这种。另外,快照会占用磁盘空间,如果宿主机磁盘紧张,可以删除旧快照,但保留最近一个健康状态的快照。
8.2 日志查看的基本方法
Hadoop 和 HBase 的日志都在各自的logs目录下。排查问题时,优先看对应服务的日志文件,比如 NameNode 看hadoop-用户名-namenode-主机名.log。日志文件可能很大,用tail -f实时查看,或者用grep过滤关键字。
Zookeeper 的日志在logs目录下,重点关注zookeeper.out。
8.3 增加节点时的注意事项
如果你后续想从三节点扩展到四节点或五节点,步骤是:准备新虚拟机,配置主机名和 hosts,配置 SSH 免密,安装 JDK,同步 Hadoop 和 HBase 的安装目录和配置文件,修改 slaves 和 regionservers 文件,然后启动新节点上的 DataNode、NodeManager 和 HRegionServer。Zookeeper 的扩展稍微麻烦一些,需要修改所有节点的zoo.cfg,加入新节点的server.N配置,然后滚动重启。
扩展时要注意:HDFS 的副本系数不会自动调整,如果你从三节点扩到五节点,副本系数还是 3,新节点上的数据分布可能不均匀。可以用hdfs balancer命令手动触发数据均衡。
8.4 这套环境还能怎么用
搭好之后,你可以在这套集群上做很多事情:跑 MapReduce 作业、用 HBase 存储时序数据、测试 Hive 或 Spark 连接 HBase、验证 Phoenix 的 SQL 查询。甚至可以把这套环境当作学习分布式系统原理的沙盘,手动 kill 掉某个节点,观察集群的容错和恢复行为。
我个人在实际操作中的体会是:这套集群最大的价值不在于“搭起来”,而在于“折腾坏再修好”的过程。每一次故障排查,都会让你对分布式系统的理解更深一层。最后再分享一个小技巧:把常用的启动、停止、状态检查命令写成 shell 脚本,放在/usr/local/bin下,能省下大量重复输入的时间。