
前阵子帮某实验室搭了一套大数据教学和科研用的基础环境技术栈就是标题里这套CentOS 8.1 Hadoop MapReduce HBase。先说个题外话标题里的hdoop其实是从Hadoop来的笔误大家别被这个拼写带偏了实际要装的就是Apache Hadoop生态。整套环境从裸机到HBase能正常读写前后花了两天半中间踩了不少坑——有些是版本兼容问题有些是配置项理解不到位导致的还有纯粹是操作顺序错了。这篇文章就把整个搭建过程、配置思路、踩坑记录和调优心得完整梳理一遍给准备入门大数据集群或者正在做同类环境搭建的人一个可复现的参考。先说说这套组合能做什么。Hadoop提供分布式存储HDFS和分布式计算MapReduce的基础能力HBase则是在HDFS之上构建一个面向海量结构化数据的分布式列存数据库。三者合在一起就是一套经典的大数据离线处理与实时读写链路MapReduce负责跑批量计算任务计算结果落到HBase里供上层应用查询。适合的场景包括日志分析、用户行为数据存储、推荐系统特征存取、物联网传感器数据入库等等。对于学生、刚接触大数据的开发者或者实验室需要一套内网大数据实验平台的朋友来说这套方案是最稳妥的入门选择也是目前各大教程和课程里覆盖最广的组合。下面直接进入正题按照从规划到落地再到排障的顺序来写。1. 项目整体规划与技术选型思路1.1 为什么选CentOS 8.1 Hadoop MapReduce HBase这套组合先说Hadoop。Hadoop在大数据领域的位置有点像数据库界的MySQL属于绕不开的基础设施。HDFS负责把文件切块分布式存储MapReduce负责把计算任务分发到数据所在节点执行这种数据不动计算动的思想是整个大数据生态的地基。讲MapReduce可能有些人觉得老了现在Spark、Flink更时髦但MapReduce的价值恰恰在于它足够简单、足够稳定特别适合理解分布式计算的本质分而治之、移动计算而非移动数据。把MapReduce吃透后面学Spark、Flink的调度模型都会轻松很多。再说HBase。它解决的是HDFS不能随机读写的痛点——HDFS适合顺序读写大文件但你要做点查、更新、删除它就力不从心了。HBase把一个大表按RowKey范围切成很多Region分散到多台RegionServer上每个Region内部按列族存储配合MemStore和HFile机制实现了海量数据下的随机读写。HBase不依赖MapReduce运行但两者天然互补MapReduce可以直接以HBase表作为数据源和数据目标跑完批量计算把结果写进HBase供实时查询使用。这也是很多生产系统的经典架构。CentOS 8.1在这个组合里扮演底层操作系统的角色。选择它不是因为它最新而是因为它在稳定性、软件源覆盖和社区资料丰富度上非常均衡。Hadoop生态组件对操作系统的要求其实不高只要内核支持、Java环境正常、网络互通基本都能跑。8.x系列在内存管理和文件系统性能上比7.x更好对现代硬件的兼容性也更强。1.2 集群拓扑与角色分配我这次用的是3节点集群配置是每台机器4核CPU、8GB内存、100GB磁盘千兆内网互联。三台机器的主机名和角色规划如下主机名IP角色分配node01192.168.20.11NameNode、ResourceManager、HMaster、SecondaryNameNodenode02192.168.20.12DataNode、NodeManager、RegionServernode03192.168.20.13DataNode、NodeManager、RegionServer这个规划的核心思路是Master角色集中在一台机器上Worker角色均匀分布在另外两台机器上。生产环境当然不会这么干生产上NameNode和ResourceManager都要独立部署甚至做HA但作为实验室环境和学习用途这种一主两从的经典结构完全够用而且能让你更清楚地看到各组件的主从协作关系。内存分配上要特别注意8GB内存跑这套全家桶其实是有点紧张的。我在node01上给Hadoop的NameNode分配了1GBResourceManager分配了1GBHMaster分配了1GB剩下的留给操作系统和SecondaryNameNode。NodeManager和RegionServer的内存也都要明确限制否则JVM默认会占用物理内存的1/4三四个Java进程加起来直接把内存吃满。1.3 版本选型的原则版本选型是这个项目里最容易被忽视、但影响最大的环节。我最终确定的版本组合是CentOS 8.1OpenJDK 1.8Java 8Hadoop 3.3.xHBase 2.4.x为什么卡死Java 8因为Hadoop 3.x虽然支持Java 8但对Java 11的完整支持是从3.3版本开始的而HBase 2.4.x在Java 8下是最成熟的。稳妥起见全部统一到Java 8避免跨大版本跑出一堆奇怪异常。Hadoop和HBase的版本匹配同样关键。HBase 2.4.x官方明确支持的Hadoop版本是3.3.x。要特别注意HBase的lib目录里自带了Hadoop的jar包但版本可能和你安装的Hadoop不一致启动时容易出现ClassNotFoundException或者协议不匹配的问题。解决办法是修改hbase-env.sh让HBase使用你系统里安装的Hadoop类库而不是它自带的那些这个后面会详细说。选版本的时候可以记住一个原则不要追求最新而要追求别人已经验证过的稳定组合。Hadoop和HBase的版本兼容矩阵在官方文档里有详细标注动手之前先查一遍能少走很多弯路。2. 基础环境准备把系统的坑提前踩掉2.1 CentOS 8.1安装后的第一轮优化装好系统之后别急着装软件先把系统层面的基础配置做完。首先是关闭防火墙和SELinux这个在实验环境里是必须的否则各种端口访问不通会让人抓狂。集群节点之间通信涉及一堆端口包括NameNode的9870Hadoop 3.x默认端口、DataNode的9864、ResourceManager的8088、HBase Master的16010、RegionServer的16030等防火墙规则写起来很繁琐实验室内网直接关掉最省事。systemctl stop firewalld systemctl disable firewalld sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/selinux/config setenforce 0然后是配置主机名和hosts映射。三台机器的主机名分别设置好之后在每台机器的/etc/hosts里加上三行映射192.168.20.11 node01 192.168.20.12 node02 192.168.20.13 node03这一步千万不能省。Hadoop集群内部节点之间是通过主机名互相识别的如果你的hosts文件没配好节点之间解析不到对方启动的时候会出现各种UnknownHostException或连接超时而且日志里不会明确告诉你是主机解析问题排查起来非常痛苦。顺带建议把/etc/hostname里的默认主机名也改掉云服务器和虚拟机模板经常会带一串随机主机名不统一改掉后面会很难管理。2.2 JDK安装与关键环境变量CentOS 8.1可以通过dnf直接安装OpenJDK 8命令如下dnf install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel这里要强调一下java-1.8.0-openjdk和java-1.8.0-openjdk-devel两个包都要装。前者是JRE运行环境后者包含编译器、jar包打包工具等开发工具。Hadoop在读源码、执行某些编译类任务时会用到JDK的工具只装JRE虽然能启动Hadoop但后面遇到编译任务就会报javac not found这种低级错误。环境变量统一写在/etc/profile里让所有用户都能读到export JAVA_HOME/usr/lib/jvm/java-1.8.0-openjdk export PATH$PATH:$JAVA_HOME/bin export HADOOP_HOME/opt/hadoop export HBASE_HOME/opt/hbase export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$HBASE_HOME/bin注意JAVA_HOME的路径用dnf装的OpenJDK实际安装路径是带版本号的目录比如/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.xxx后面通常还跟着架构后缀。直接用/usr/lib/jvm/java-1.8.0-openjdk这种不带版本号的路径是最稳妥的因为它是一个指向具体版本的软链接以后升级也不会失效。配置完环境变量后执行source /etc/profile使其立即生效。验证方式是在每个节点上运行java -version和echo $JAVA_HOME确保输出正确再进入下一步。2.3 SSH免密登录配置SSH免密登录是Hadoop集群启动的必要条件。Hadoop的启动脚本会通过SSH远程登录到各DataNode节点去启动守护进程如果每次都要输入密码你甚至无法自动化管理集群。配置方法很简单在node01上生成密钥对然后把公钥分发到三台机器上ssh-keygen -t rsa -b 4096 -P -f ~/.ssh/id_rsa ssh-copy-id node01 ssh-copy-id node02 ssh-copy-id node03-P 表示空密码这样SSH登录时不需要额外输入密钥口令。三台机器之间建议全部配好免密虽然Hadoop实际只需从主节点免密到其他节点但后续你可能会在任意节点上执行hbase shell或者hdfs dfs命令全互联更省心。验证方法就是分别执行ssh node02、ssh node03能直接登录而不提示输入密码就说明配置正确。2.4 时间同步配置分布式系统对时间一致性要求很高尤其是HBaseRegionServer和HMaster之间的心跳超时判断依赖时间戳时间偏差过大会导致节点被误判为故障。实验室环境没有外部NTP服务的话最简单的做法是选一台机器作为时间源其他机器定时同步。我用的方法是node01安装并启动chrony服务node02和node03配置为指向node01# node01上执行 dnf install -y chrony systemctl enable chronyd --now # 修改/etc/chrony.conf允许内网客户端同步 # 在allow行添加allow 192.168.20.0/24 # node02和node03上执行 # 修改/etc/chrony.conf注释掉默认的pool行添加 server 192.168.20.11 iburst systemctl restart chronyd时间同步配置好之后用chronyc sources -v命令检查同步状态。对实验环境来说只要三台机器时间差在秒级以内就完全够用了。3. Hadoop集群部署配置文件是重点3.1 解压安装与目录规划Hadoop我安装在/opt/hadoop目录下统一管理所有大数据组件路径清晰也好记。从Apache官网下载hadoop-3.3.x.tar.gz后解压并重命名tar -zxvf hadoop-3.3.x.tar.gz -C /opt mv /opt/hadoop-3.3.x /opt/hadoop这里有个小技巧可以在/etc/profile里的HADOOP_HOME指向/opt/hadoop之前先确认解压出来的目录名和你要用的路径一致避免后面环境变量配好之后路径对不上。Hadoop的配置全部集中在$HADOOP_HOME/etc/hadoop/目录下。核心需要修改的配置文件有5个core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml、workers。3.2 core-site.xml与hdfs-site.xml配置解析core-site.xml主要设置默认文件系统为HDFS以及临时目录的路径configuration property namefs.defaultFS/name valuehdfs://node01:9820/value /property property namehadoop.tmp.dir/name value/opt/hadoop/tmp/value /property /configurationfs.defaultFS的值是NameNode的地址和RPC端口。注意Hadoop 3.x的NameNode RPC端口默认是9820很多旧教程里写的是9000或者8020那是老版本的默认值照抄旧教程配置会导致客户端连接不上。hadoop.tmp.dir这个参数很关键。很多文件系统元数据默认存放在这个目录下NameNode的镜像文件、DataNode的数据块索引都在这里。如果不配置默认值是/tmp/hadoop-${user.name}系统重启后/tmp目录可能被清理导致集群元数据丢失后果就是DataNode报错、NameNode进入安全模式。所以我强烈建议把临时目录设置到专门的数据目录比如/opt/hadoop/tmp并且确保这个目录所属用户是启动Hadoop的用户。hdfs-site.xml是HDFS的核心配置主要设置副本数和NameNode的元数据目录configuration property namedfs.replication/name value2/value /property property namedfs.namenode.name.dir/name valuefile:///opt/hadoop/dfs/name/value /property property namedfs.datanode.data.dir/name valuefile:///opt/hadoop/dfs/data/value /property property namedfs.namenode.secondary.http-address/name valuenode01:9868/value /property /configurationdfs.replication设置为2是因为3节点集群中3副本策略会把数据均匀分布在3个节点上但这样每个数据块就占用3倍的磁盘空间。测试环境数据量不大2副本即可满足数据不丢失的需求同时节省磁盘。dfs.namenode.name.dir和dfs.datanode.data.dir一定要指定到非系统盘目录避免系统盘满了导致集群崩溃。尤其是NameNode的元数据目录一旦丢失整个集群的文件系统索引就全没了有条件的话可以用独立磁盘甚至挂载网络存储。3.3 yarn-site.xml与mapred-site.xml配置解析yarn-site.xml负责配置资源调度器configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.nodemanager.aux-services.mapreduce_shuffle.class/name valueorg.apache.hadoop.mapred.ShuffleHandler/value /property property nameyarn.resourcemanager.hostname/name valuenode01/value /property property nameyarn.nodemanager.resource.memory-mb/name value4096/value /property property nameyarn.scheduler.maximum-allocation-mb/name value2048/value /property /configurationyarn.nodemanager.aux-services固定是mapreduce_shuffle这个是MapReduce任务在NodeManager之间传输中间结果需要的辅助服务配错或少配都会导致MapReduce任务在Shuffle阶段失败。内存相关的两个参数需要根据你的物理内存调整。8GB内存的机器上我给NodeManager分配了4GB每个任务容器最大2GB。这意味着同一台机器最多同时跑2个Map或Reduce任务容器。如果你内存更大可以适当调高这两个值会直接提升任务并行度。mapred-site.xml设置计算框架和默认的任务调度器configuration property namemapreduce.framework.name/name valueyarn/value /property property namemapreduce.jobhistory.address/name valuenode01:10020/value /property /configurationmapreduce.framework.name设为yarn是让MapReduce任务跑在YARN上而不是本地的local模式。本机模式适合调试小任务但完全体现不出分布式计算的效果。workers文件Hadoop 3.x叫workers老版本叫slaves用来列出所有DataNode节点的主机名node02 node03特别提醒workers文件里不要写node01。虽然node01上也安装了DataNode但在这种一主两从的结构里NameNode节点一般只承担管理角色不存数据。如果你把node01也写进去它会同时充当NameNode和DataNode虽然也能工作但管理节点的磁盘I/O会干扰NameNode的元数据读写性能。3.4 格式化NameNode与启动配置文件全部就绪后在node01上执行格式化命令hdfs namenode -format这是所有Hadoop初学者的第一次恐惧。格式化命令会生成NameNode的初始元数据同时会输出一堆日志。看到日志里出现successfully formatted就说明成功了。注意格式化操作只需要做一次而且必须在NameNode节点上执行。如果集群运行中出现了严重问题需要重新初始化集群可以重新格式化但要先删除所有节点的dfs.datanode.data.dir目录内容否则DataNode的VERSION文件与NameNode不一致启动时会报错。格式化完成后在node01上启动HDFSstart-dfs.sh启动脚本会按workers文件里的主机名远程登录到node02和node03启动DataNode进程。启动完成后用jps命令验证进程node01上应该有NameNode、SecondaryNameNodenode02和node03上应该有DataNode然后启动YARNstart-yarn.sh启动后再用jps验证node01上应该有ResourceManagernode02和node03上应该有NodeManager如果任何一个节点缺少对应进程就要去对应节点的Hadoop日志目录排查。Hadoop日志默认在$HADOOP_HOME/logs/下查看hadoop-hadoop-hostname-daemon.log这个文件错误信息都会写在这里。最后在浏览器访问http://node01:9870确认HDFS的Web UI正常显示访问http://node01:8088确认YARN的ResourceManager页面能打开。这里要说明9870端口是Hadoop 3.x的NameNode Web UI端口老版本是50070看教程时要留意版本对应的端口。4. MapReduce从提交任务到结果落地的完整链路4.1 用官方示例验证计算链路Hadoop自带一个wordcount示例程序位于$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.x.jar。先用它验证整个计算链路是否通畅。先在HDFS上准备输入数据hdfs dfs -mkdir -p /input hdfs dfs -put /etc/hosts /input/ hdfs dfs -ls /input然后提交wordcount任务hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.x.jar wordcount /input /output任务提交后状态会经过ACCEPTED、RUNNING、SUCCEEDED几个阶段。观察任务运行时可以在浏览器里打开YARN的ResourceManager页面能看到Map和Reduce的进度百分比。任务结束后查看结果hdfs dfs -cat /output/part-r-00000这里要注意HDFS上的/output目录在任务运行前必须不存在否则MapReduce会直接报Output directory hdfs://node01:9820/output already exists的错误。这是一个非常经典的坑根本原因在于MapReduce框架不允许覆盖已存在的输出目录防止误操作覆盖之前的结果。解决方法是每次跑任务前删掉输出目录或者每次用不同的输出路径。4.2 编写自定义MapReduce任务的关键点跑通官方示例后最好自己动手写一个简单的MapReduce程序因为只有亲手写过才能真正理解数据流。以词频统计为例代码结构分三部分Mapper、Reducer、Driver。Mapper类继承MapperLongWritable, Text, Text, IntWritable输入是行号和行内容输出是单词和计数1public class WordMapper extends MapperLongWritable, Text, Text, IntWritable { private Text word new Text(); private IntWritable one new IntWritable(1); Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line value.toString(); String[] words line.split( ); for (String w : words) { word.set(w); context.write(word, one); } } }Reducer类把相同key的value列表汇总求和public class WordReducer extends ReducerText, IntWritable, Text, IntWritable { private IntWritable result new IntWritable(); Override protected void reduce(Text key, IterableIntWritable values, Context context) throws IOException, InterruptedException { int sum 0; for (IntWritable val : values) { sum val.get(); } result.set(sum); context.write(key, result); } }写代码时有一个新手非常容易犯的错误在Mapper的map方法里直接new Text()或new IntWritable()。这在数据量小的时候看不出问题但面对海量数据时每行数据都新建对象会严重增加GC压力性能直线下降。正确的做法是在setup方法或者类成员变量里预先创建对象map方法里只需复用和set值。Driver类负责配置并提交任务public class WordCountDriver { public static void main(String[] args) throws Exception { Configuration conf new Configuration(); Job job Job.getInstance(conf, word count); job.setJarByClass(WordCountDriver.class); job.setMapperClass(WordMapper.class); job.setReducerClass(WordReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } }编译打包后提交hadoop jar wordcount.jar WordCountDriver /input /output24.3 MapReduce的Shuffle机制与调优方向很多人跑通了MapReduce但说不清中间发生了什么这里必须讲一下Shuffle因为它是MapReduce的性能命门。Shuffle过程可以理解为Map端和Reduce端之间的数据快递系统。Map端输出的中间结果不是直接传给Reducer的而是要经过四个阶段分区partition、排序sort、溢写spill、合并merge。每个Map任务会把自己的输出按照key的哈希值划分到不同的分区每个分区对应一个Reducer分区内部按键排序当缓冲区满了默认100MB达到80%阈值时就溢写到本地磁盘多个溢写文件最后归并成一个最终文件同时每个分区内已经有序。Reduce端拿到Map输出后先进行拷贝copy和归并merge把来自不同Map任务的同一分区数据合并成一个有序的大文件然后才进入Reducer的reduce方法。理解了Shuffle排错就有方向了。如果任务卡在Shuffle阶段大概率是磁盘I/O瓶颈或者网络传输慢如果Reduce阶段内存溢出说明mapreduce.reduce.memory.mb分配太小如果Map阶段大量溢写说明mapreduce.map.sort.spill.percent阈值太低或者排序内存不够。调优的核心思想是减少磁盘I/O和网络I/O因为分布式计算最慢的就是这两项。常见的调节参数包括增大mapreduce.map.memory.mb给Mapper更多内存、调大mapreduce.map.sort.spill.percent让缓冲区更晚溢写、打开mapreduce.map.output.compress让Shuffle传输的数据压缩后传递。测试环境数据量小这些参数不用太纠结但作为实验项目建议至少把mapreduce.map.output.compress设为true对理解生产环境有直接帮助。5. HBase部署与Hadoop的深度对接5.1 HBase配置让HBase真正跑在HDFS上HBase解压安装到/opt/hbase后核心修改三个文件hbase-env.sh、hbase-site.xml、regionservers。先改hbase-env.sh定位两行关键配置export JAVA_HOME/usr/lib/jvm/java-1.8.0-openjdk export HBASE_PID_DIR/opt/hbase/pids特别重要的一个隐患在HBase的lib目录。HBase自带了一整套Hadoop相关jar包版本可能与你安装的Hadoop不一致。启动HBase时如果直接使用自带的Hadoop类库可能和正在运行的HDFS NameNode产生RPC协议不兼容。解决方法是强制让HBase使用系统安装的Hadoop类库。在hbase-env.sh里注释掉默认的CLASSPATH设置然后手动把Hadoop的依赖目录全部加进来。简单粗暴的版本把HBASE_CLASSPATH环境变量去掉让HBase从$HADOOP_HOME的lib目录下找类库。这个操作如果配置不对启动时会报ClassNotFoundException或者各种协议异常多试几次就明白了。然后是hbase-site.xmlconfiguration property namehbase.rootdir/name valuehdfs://node01:9820/hbase/value /property property namehbase.cluster.distributed/name valuetrue/value /property property namehbase.zookeeper.quorum/name valuenode01,node02,node03/value /property property namehbase.zookeeper.property.dataDir/name value/opt/hbase/zookeeper-data/value /property property namehbase.regionserver.handler.count/name value30/value /property /configuration逐项解释。hbase.rootdir指定HBase的根目录这里必须指向HDFS的地址而且端口要和Hadoop的core-site.xml里配置的fs.defaultFS完全一致。如果你Hadoop配置的是9820端口这里就不能写成9000或8020否则HBase启动后根本无法连接HDFS。hbase.cluster.distributed设为true表示以分布式模式运行。如果为falseHBase会以本地模式运行数据只能存在本地文件系统不涉及HDFS——这不是我们要的效果。hbase.zookeeper.quorum配置ZooKeeper的节点列表。这里有个容易混淆的点HBase内置了一个ZooKeeper我们并没有单独部署ZooKeeper集群HBase自带的ZooKeeper实例会运行在每个节点上三台机器互相通信组成一个ZooKeeper集群。生产环境会建议独立部署ZooKeeper但实验环境用HBase内置的完全够用。hbase.zookeeper.property.dataDir是ZooKeeper数据目录务必改掉默认值因为默认路径在/tmp下重启会丢数据。HBase启动时会自动在这个目录下初始化ZooKeeper数据。5.2 同步Hadoop配置与启动顺序HBase要连接HDFS必须让HBase节点拿到Hadoop的配置文件。最简单的做法是把core-site.xml和hdfs-site.xml复制到HBase的conf目录下cp $HADOOP_HOME/etc/hadoop/core-site.xml $HBASE_HOME/conf/ cp $HADOOP_HOME/etc/hadoop/hdfs-site.xml $HBASE_HOME/conf/然后把HBase整体分发到node02和node03。注意regionservers文件内容要和Hadoop的workers文件一致node02 node03HBase的启动顺序有讲究先启动Hadoop的HDFS再启动HBase。如果HDFS还没就绪就启动HBaseHMaster会反复尝试连接NameNode但失败日志里出现大量Connection refused最后进程自己退出。我在第一次启动时就踩过这个坑等HDFS正常了再启动HBase几秒就完成了。在node01上执行start-hbase.sh启动后在各节点验证node01上应有HMaster进程node02和node03上应有HRegionServer进程不要忘了HBase还依赖ZooKeeper你会看到HBase的启动脚本其实会先在每个节点上启动ZooKeeperHQuorumPeer进程然后再启动HMaster和RegionServer。如果你看到某个节点只有HQuorumPeer而没有HRegionServer说明RegionServer启动失败了去$HBASE_HOME/logs/目录看对应日志。5.3 用HBase Shell和Java API验证读写链路启动成功后先用hbase shell验证基本操作hbase shell # 创建一张测试表列族名为cf create test_table, cf # 插入一条数据RowKey为row1 put test_table, row1, cf:name, 张三 put test_table, row1, cf:age, 25 # 查询这条数据 get test_table, row1 # 全表扫描 scan test_table # 删除表需先disable disable test_table drop test_tablecreate语句里HBase的表必须先定义列族Column Family列Column Qualifier不需要预先定义随时写入随时创建。这个设计是HBase的一大特色跟关系型数据库的固定列结构完全不同也让做数据模型设计的人需要转换思维RowKey的散列分布决定了读写热点是否均匀列族的设计决定了存储和IO模式。从Java API访问HBase时核心代码如下Configuration conf HBaseConfiguration.create(); conf.set(hbase.zookeeper.quorum, node01,node02,node03); Connection conn ConnectionFactory.createConnection(conf); Table table conn.getTable(TableName.valueOf(test_table)); Put put new Put(Bytes.toBytes(row2)); put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(name), Bytes.toBytes(李四)); table.put(put); Get get new Get(Bytes.toBytes(row2)); Result result table.get(get); byte[] value result.getValue(Bytes.toBytes(cf), Bytes.toBytes(name)); System.out.println(Bytes.toString(value)); table.close(); conn.close();用Java API访问时最容易遇到的问题就是网络超时。出现SocketTimeoutException时首先确认ZooKeeper的端口默认2181和HBase Master的端口默认16000在防火墙内可达。如果HBase和客户端不在同一台机器还要检查客户端机器上的/etc/hosts是否配置了HBase集群所有节点的IP和主机名映射。HBase客户端拿到ZooKeeper返回的RegionServer地址后会尝试直接连接RegionServer所在的机器名如果解析不了就必然超时。5.4 让MapReduce读写HBase的整合配置这套环境最出彩的地方就是把MapReduce和HBase打通。使用HBase提供的TableInputFormat和TableOutputFormat可以让MapReduce直接从HBase表读数据或者把计算结果写进HBase表。配置上关键一步是用TableMapReduceUtil来初始化任务。示例代码如下Configuration conf HBaseConfiguration.create(); conf.set(hbase.zookeeper.quorum, node01,node02,node03); Job job Job.getInstance(conf, HBase MapReduce); job.setJarByClass(HBaseMRDemo.class); // 设置Scan指定要读取的列 Scan scan new Scan(); scan.addColumn(Bytes.toBytes(cf), Bytes.toBytes(name)); // 自动配置TableInputFormat TableMapReduceUtil.initTableMapperJob( source_table, scan, MapperClass.class, Text.class, IntWritable.class, job); TableMapReduceUtil.initTableReducerJob( result_table, ReducerClass.class, job); job.waitForCompletion(true);initTableMapperJob和initTableReducerJob这两个方法会自动设置输入输出格式不需要像纯HDFS任务那样手动指定FileInputFormat和FileOutputFormat。它内部会处理好HBase和MapReduce之间的序列化转换使用时不用写那些繁琐的底层代码。跑任务之前也要提前建好HBase的输出表如果表不存在Reducer写数据时会报TableNotFoundException。这是一个很常见的坑解决办法是在跑任务前先通过hbase shell把表建好包括列族结构都定义好。6. 常见问题与排查技巧实录6.1 故障速查表实际搭建过程中我把遇到的各种报错整理成了下面这张速查表。每一条都是真实踩坑后的总结排查时可以对照着查。现象可能原因排查与解决方式NameNode启动失败日志中有NameNode format错误重复格式化VERSION文件不一致删掉所有节点的data目录重新格式化后启动DataNode启动后自行退出HDFS集群ID不一致或workers文件配错检查DataNode日志中的clusterID与NameNode的current/VERSION文件对照HBase启动后HMaster反复重启HDFS未就绪或hbase.rootdir端口写错确认NameNode正常后核对hbase-site.xml端口与core-site.xml一致HBase shell连接超时ZooKeeper数据目录冲突清空hbase.zookeeper.property.dataDir下的旧数据需要所有节点同步清空MapReduce任务卡在Shuffle阶段NodeManager内存不足或Shuffle服务异常检查yarn-site.xml的aux-services配置调大NodeManager内存MapReduce输出目录已存在异常未清理上次的输出路径每次任务前删除输出目录或使用不同的输出路径Java客户端访问HBase报NoServerForRegionException客户端hosts没配置解析不到RegionServer同步客户端hosts映射或关闭RegionServer的本地解析6.2 一套排查套路遇到问题先别慌着改配置遵循一个固定套路能大幅缩短排查时间。第一步是看日志。Hadoop和HBase的日志写得非常详细每个守护进程都有独立的log文件。报错信息里通常已经包含了根本原因比如Connection refused说明网络不通ClassNotFoundException说明jar包冲突TableNotFoundException说明表没建好。大多数人拿到报错就直接百度其实静下心把日志文件里的信息读一遍问题解决了一大半。第二步是核对版本。Hadoop和HBase版本不匹配、Java版本不兼容这类问题出现在配置错误之后是第二高频的问题。对照官方兼容矩阵检查基本能避开90%的版本坑。第三步是确认端口。集群内部各组件通过固定的RPC端口通信NameNode的RPC端口、ZooKeeper的2181端口、RegionServer的16020端口任何一个被防火墙拦截或配置不一致都会导致集群间通信失败。排查时用netstat -tlnp查看进程是否监听了预期端口再用telnet ip 端口验证网络连通性。第四步是检查数据目录权限。尤其是用root启动Hadoop时各级数据目录的所有者必须一致否则DataNode写数据块时会抛出Permission denied。建议统一用同一个系统用户启动所有组件避免权限纠纷。6.3 环境管理的小技巧集群启动顺序要固定先HDFS再YARN最后HBase。关闭顺序相反先HBase再YARN最后HDFS。这个顺序背后有逻辑HBase依赖HDFS存储数据依赖YARN跑协处理任务所以启动时必须先确保底层组件就绪关闭时先关上层应用防止写入过程中底层断开。建议写一个启停脚本用一个命令控制整个集群的启停顺序既省事又不容易出错。我习惯把脚本放到/opt/scripts/目录下内容大致是# start.sh start-dfs.sh start-yarn.sh start-hbase.sh # stop.sh stop-hbase.sh stop-yarn.sh stop-dfs.sh日志目录也要定时清理。Hadoop和HBase的日志默认不会自动滚动长时间运行后logs目录会越来越大占用不少空间。我隔一段时间就会手动手动清理一次只保留最近的日志文件。如果你有监控需求建议配置日志滚动策略再让它持续增长。7. 后续扩展与性能观察的方向整套环境跑通后你会拥有一个完整体验数据怎么存、计算怎么跑、结果怎么查的大数据闭环。在此基础上可以往三个方向扩展。第一个方向是数据导入与查询优化。尝试用HBase的BulkLoad工具把大批量数据直接导入HBase表对比普通put写入的性能差异。再尝试设计不同的RowKey比如加了盐值salt的RowKey和纯顺序RowKey观察Scan和Get的耗时差异。这个过程能真正理解HBase为什么说RowKey设计决定读写性能。第二个方向是把MapReduce换成Spark。Spark可以复用你部署好的HDFS和YARN环境通过spark-submit提交任务到YARN上运行。你会直观感受到内存计算和磁盘计算MapReduce的速度差异同时也能通过对比理解为什么企业里Spark逐渐取代MapReduce成为主流的离线计算引擎。第三个方向是加入Hive做SQL化查询。Hive可以把SQL翻译成MapReduce或者Spark任务执行底层依然跑在你现在的集群上。装上Hive之后你会发现从写Java代码跑MapReduce到写SQL查数据的体验跃迁也更容易向非技术同事解释大数据平台的能力边界。我个人在实际操作中的体会是这套环境最值钱的地方不在于装成功这个结果而在于整个搭建过程中你对HDFS的存储机制、MapReduce的计算模型、HBase的分布式表结构有了实操级的理解。只看文档和真正把集群跑起来完全是两码事。很多人在搭的过程中反复被端口、版本、路径这类基础问题折磨恰恰是这些折磨让后面排查生产环境问题时能更快定位到方向。另外再分享一个小技巧每完成一个阶段就把配置文件备份一份比如core-site.xml.bak这种遇到改乱配置导致集群起不来的时候能快速回滚到能用的状态这个习惯关键时刻能救你一命。