ZooKeeper 在 HBase 完全分布式集群里的位置,很像一栋楼的总配电箱:平时没人注意它,可一旦它出问题,整栋楼的灯全灭。很多人第一次配 HBase 完全分布式,HDFS 配通了、RegionServer 也能起来,结果卡在hbase:meta定位不上、HMaster 互相抢主、客户端连上去几秒就掉线,翻来覆去查 hbase-site.xml,最后发现问题根本不在 HBase,而在前面那个三节点的 ZooKeeper 集群压根没配利索。这篇就专门把这一步拆开讲透,从环境对齐、zoo.cfg 逐行参数、myid 分发,到启动验证和现场排错,按着顺序做下来,基础薄弱的同学也能把一套可用的 ZK 集群立起来,并且知道每一步为什么这么做。
1. HBase 完全分布式为什么必须自建 ZooKeeper 集群
1.1 内置 ZK 只能撑伪分布式的场景
HBase 的安装包自带一个启动 ZooKeeper 的脚本,bin/hbase-daemon.sh start zookeeper敲下去,本机就能起一个单实例 ZK。这个设计初衷是为了让单机模式和伪分布式模式开箱即用,省去额外部署一套组件的麻烦。问题在于它天生只有一个节点,没有 quorum 的概念,也没有 leader 选举,进程一挂,挂在它上面的所有 HBase 组件立刻失去协调中心。
我见过不少人在配置完全分布式时,图省事直接在 Master 节点上把内置 ZK 拉起来,然后让另外几台机器的 RegionServer 指过来。这种结构在实验室里跑两小时演示没问题,但只要 Master 那台机器重启一次,或者 ZK 的 data 目录被误删,整个集群的元数据视图就等于从零开始,之前建的库、表在逻辑上还在 HDFS 里,但 HBase 已经不认识它们了。所以标题里强调“完全分布式”,第一步就得把 ZK 从 HBase 的安装目录里剥离出来,变成一个独立运维、独立生命周期的三节点集群。
1.2 ZooKeeper 在 HBase 集群里真正扛的四件事
搞不清楚 ZK 在 HBase 里到底存了什么,配置的时候就只能照抄,出问题也看不出门道。它承担的核心职责大致是这几块:
| 职责 | 存在 ZK 里的内容 | 出问题时的表现 |
|---|---|---|
| 元数据入口 | hbase:meta表所在的 RegionServer 地址 | 客户端连上 ZK 却找不到 meta,报NotAllMetaRegionsOnlineException |
| Master 选举 | /hbase/master临时节点,谁创建成功谁是 Active | 两个 Master 同时认为自己 active,或者都处于 backup 状态 |
| RegionServer 注册 | /hbase/rs下的临时节点,靠会话心跳维持 | RS 进程还活着,但被集群判定为下线,region 被反复重分配 |
| 集群与表状态 | 表启用/禁用状态、分裂合并事务状态、复制 peer 信息 | disable卡住不动,或者表状态和实际不一致 |
注意第二行和第三行都带“临时节点”三个字,这意味着这些信息是靠 ZooKeeper 的会话机制维持的,会话一断,节点自动消失,HBase 就会认为对应的服务挂了。这就解释了为什么 ZK 集群不稳定的时候,HBase 的表现往往不是“连不上”,而是“莫名其妙地频繁切换主节点、频繁重分配 region”,看起来像是 HBase 自己的毛病,实际根子在会话超时。
1.3 三节点为什么是起步配置
ZooKeeper 的选举要求集群中超过半数节点可用,也就是所谓 quorum。3 个节点允许挂 1 台,5 个节点允许挂 2 台,2 个节点和 1 个节点在容错能力上完全一样,都容忍不了任何一台故障,但 2 个节点还会引入投票平票的风险,反而更差。
所以实战里的最小可用配置就是 3 台,这也是标题这篇要讲的结构。机器紧张的时候,ZK 节点和 HBase 的 Master、RegionServer 混部在同一批机器上是完全可以的,只要内存和磁盘 IO 留够余量。真正不建议的是把三个 ZK 实例全塞在同一台物理机上,那样等于自己骗自己,节点数量看着是 3,实际可用性还是 1。
2. 三台机器动手之前的环境对齐
2.1 主机名与 hosts:ZK 寻址的第一道坎
ZooKeeper 的配置文件里,server.N这一行写的是主机标识,客户端连接串里写的也是主机标识。如果这几处一处写 IP、一处写主机名,或者主机名在不同机器上解析结果不一样,就会出现“配置看起来完全正确,就是连不上”的经典场景。
我的习惯是全程只用主机名,并且保证三台机器的/etc/hosts内容完全一致。假设三台机器规划如下,这里的地址段只是示意,实际按自己的内网规划来:
# 三台机器上 /etc/hosts 的内容保持完全一致 10.0.0.11 zk-node1 10.0.0.12 zk-node2 10.0.0.13 zk-node3主机名通过hostnamectl set-hostname zk-node1这类方式设置,改完记得重新登录或者执行bash让提示符刷新。设完之后用hostname和hostname -f各验证一遍,两个命令的输出要能对得上 hosts 里的记录。有些系统里hostname -f会返回localhost.localdomain,这种就是解析顺序有问题,得去看/etc/nsswitch.conf里 hosts 那一行是不是把files排在前面。
还有一个容易被忽略的点:/etc/hosts里如果残留了安装系统时自动生成的127.0.1.1那行映射到旧主机名的记录,可能会出现正向解析是新名字、反向解析是旧名字的情况。ZooKeeper 本身对反向解析不敏感,但 HBase 的一些组件会用到,趁早清理干净省事。
2.2 时钟同步这件小事,出问题就是大事
ZooKeeper 的会话机制、临时节点、事务顺序,全都依赖时间。三台机器的时钟如果偏差过大,典型症状是 leader 选举反复发起、follower 频繁重连、日志里出现大量Exception when following the leader。
配置方式不复杂,选一个能访问到的时间源,三台机器统一指向它就行。关键是要确认同步真的生效了,而不是配完就假设它工作。验证方式很简单,在三台机器上分别执行date看输出,前后差不能超过几秒。如果想看得更细,用chronyc sources -v或者ntpq -p查看偏移量,偏移在毫秒级才算健康。
还有一个隐性坑:虚拟机环境里如果宿主机休眠再恢复,客户机的时钟可能会跳变。这种情况下 ZK 进程最好重启一次,别指望它自己缓过来。我在自己的测试环境里就遇到过一次,笔记本合盖一夜,第二天起来 ZK 集群状态全乱,三台机器时间差了快半小时,重同步加重启才恢复。
2.3 JDK 选型与 JAVA_HOME 的落点
ZooKeeper 的启动脚本会去找JAVA_HOME,找不到就直接报Error: JAVA_HOME is not set and no 'java' command could be found in your PATH。这个问题看似低级,但因为它出现在zkServer.sh的最开头,很多人看到报错第一反应是去翻 zoo.cfg,结果方向完全错了。
版本上,较新的 ZooKeeper 3.5 以后的版本要求 JDK 8 及以上,实际使用中建议统一用 JDK 8 或 JDK 11 这类长期支持版本,别用太新的实验性版本,容易碰到模块系统相关的启动参数问题。三台机器的 JDK 大版本必须一致,主版本号不同步导致的序列化兼容问题排查起来非常痛苦。
JAVA_HOME的配置建议写在/etc/profile.d/下的独立脚本里,而不是直接改/etc/profile,这样后续卸载或者升级的时候好清理。配完之后用source加载,然后echo $JAVA_HOME确认路径正确,再$JAVA_HOME/bin/java -version确认可执行。注意zkServer.sh是通过非交互式 shell 启动的,如果只在~/.bashrc里配了JAVA_HOME,脚本可能读不到,这是很典型的一个坑。
2.4 目录规划与用户权限
ZooKeeper 需要两个目录:一个存数据快照,一个存事务日志。默认配置里常常把这两个都塞在安装目录下的data里,测试环境无所谓,但只要开始跑真实负载,就必须分开。
原因在于 ZooKeeper 的写入模型:每次事务提交前要先写事务日志并落盘,然后才更新内存数据结构,快照则是定期异步生成的。如果日志和数据放在同一块盘上,生成快照时的大量 IO 会和事务日志的同步写抢资源,直接表现为写入延迟抖动。分开存放,最好分到不同的物理盘或者至少不同的挂载点,能明显降低尾延迟。
目录权限这块,我一般单独建一个普通用户来跑 ZK,不用 root。安装目录、数据目录、日志目录的属主都设成这个用户,避免出现“用 root 启动过一次,后面用普通用户启动就报权限不足”的情况。这种问题的隐蔽性在于,报错信息往往指向 data 目录下的某个具体文件,看起来像文件损坏,实际是权限问题。目录建好之后,用ls -ld逐个确认属主和权限位,比启动失败后再回头查要省事得多。
3. zoo.cfg 逐行拆解与参数计算
3.1 tickTime 不是随便填的,它决定了会话超时的天花板
tickTime是 ZooKeeper 里所有时间参数的基本单位,单位是毫秒。它的默认值 2000 在很多教程里被原样照抄,但这个值直接决定了一个非常关键的边界:客户端会话超时的上限是20 * tickTime。
HBase 侧有一个参数zookeeper.session.timeout,默认值是 90000 毫秒,也就是 90 秒。客户端连接 ZK 时会带着这个期望值去协商,而服务端会把它夹在[2 * tickTime, 20 * tickTime]区间内。如果 tickTime 取默认的 2000,上限就是 40000 毫秒,HBase 想要的 90 秒会被强行压到 40 秒。
这个压缩带来的后果是:GC 停顿稍微长一点,或者网络抖一下,RegionServer 就会丢会话,被判定下线,然后触发 region 重分配。表现出来就是集群“隔一会儿抽一下”,日志里能看到Received HBase session expired之类的记录。所以我的做法是把tickTime设成 6000,这样20 * 6000 = 120000,90 秒的请求能落在区间内被原样接受。代价是 leader 选举的粒度变粗,但 HBase 场景下这个取舍是划算的。
3.2 initLimit 与 syncLimit 的换算
这两个参数都是以 tick 为单位,实际时间要乘上 tickTime。
initLimit控制的是 follower 启动时与 leader 完成首次同步的容忍时长。同步的内容包括全量快照,如果数据量大,这个时间会拉长。取默认的 10,在 tickTime=6000 的情况下就是 60 秒,对于一般规模的 HBase 元数据完全够用。如果你的 ZK 承载了其他业务、数据节点特别多,可以适当上调到 15 或者 20。
syncLimit控制的是正常运行期间 follower 与 leader 之间心跳请求的容忍时长。超过这个时间没响应,leader 就会把该 follower 踢出集群。默认 5,按 tickTime=6000 算是 30 秒。这个值设得太小,网络稍有波动就会误踢;设得太大,真正的故障节点又迟迟不被剔除。30 秒是个比较平衡的值。
这里要注意一个连带的计算:syncLimit * tickTime必须小于tickTime * 20,否则会出现 follower 还没被判超时,会话已经先过期了的矛盾状态。这一点在官方文档里说得比较含蓄,实际配置时值得自己算一遍。
3.3 dataDir 与 dataLogDir 分开的意义
前面提过 IO 争抢的问题,这里具体说配置方式。
dataDir=/data/zookeeper/data dataLogDir=/data/zookeeper/logsdataDir存的是内存数据结构的定期快照,文件形如snapshot.0x0000000000000001;dataLogDir存的是预写日志,文件形如log.100000001。快照文件通常是全量写,体积大;事务日志是追加写,频率高。两者分开后,快照生成时的顺序写不会打断事务日志的同步刷盘。
如果机器实在只有一块盘,那就只能放一起,但至少要做到定期清理,别让历史快照把盘塞满。ZooKeeper 有个autopurge机制可以做这件事,后面会讲。
还有一个细节:dataDir下面除了快照,还会放myid文件。所以这个目录不能随手清空,rm -rf data/*这种操作等于把节点身份也删了,重启的时候会直接报错退出。我在清洗环境的时候习惯单独写个脚本只删snapshot.*和log.*,不做通配符删除。
3.4 server.A=B:C:D 这一行的四段含义
这行是集群配置的核心,格式是server.服务器编号=主机标识:数据同步端口:选举端口。
server.1=zk-node1:2888:3888 server.2=zk-node2:2888:3888 server.3=zk-node3:2888:3888编号 1、2、3 就是每台机器的身份,必须和该机器dataDir下myid文件里的数字严格对应。主机标识用主机名或 IP 都行,但要和客户端连接串保持一致,别一边写名字一边写地址。
2888 是 follower 连到 leader 之后,用来接收数据同步和事务提案的端口;3888 是选举阶段用的通信端口,在 leader 挂掉或者集群刚启动还没选出 leader 的时候,节点之间靠这个端口互相投票。这两个端口只需要在集群内部互通,不对外暴露。
端口规划上,如果同一台机器上还跑了别的服务,要注意别撞。2888 和 3888 是社区惯例,改也不是不行,但改了三台机器得一起改,而且团队成员习惯性会去找默认端口,改动带来的沟通成本往往比收益大。我的建议是保持默认,除非确实冲突。
3.5 自动清理与客户端连接数
完整的zoo.cfg里还有几个值得显式写上的参数:
tickTime=6000 initLimit=10 syncLimit=5 dataDir=/data/zookeeper/data dataLogDir=/data/zookeeper/logs clientPort=2181 maxClientCnxns=300 autopurge.snapRetainCount=5 autopurge.purgeInterval=12 4lw.commands.whitelist=*maxClientCnxns限制的是单个客户端 IP 到单个 ZK 实例的连接数,默认 60。HBase 集群里 RegionServer 数量一多,或者有大量短连接客户端的时候,60 很容易被打满,表现为新的连接被拒绝。调到 300 是个比较宽松的值,代价只是多占一点文件描述符。
autopurge.purgeInterval单位是小时,设 12 表示每 12 小时清理一次;autopurge.snapRetainCount表示保留最近几份。这两个参数不配的话,快照和日志会无限累积,跑上几个月就能把磁盘吃满。这个坑我踩过,凌晨告警磁盘使用率 100%,排查半天发现是 ZK 的dataLogDir里堆了几万个日志文件。
4lw.commands.whitelist是 3.5 版本之后引入的,四字命令默认被禁用。不配这一行,echo stat | nc zk-node1 2181这类命令会返回stat is not executed because it is not in the whitelist。生产环境可以按需只开放srvr、mntr、ruok这几个,测试环境图省事直接写*。
4. myid 与配置分发:翻车率最高的一段
4.1 myid 的写法和三台机器的对应关系
myid就是一个纯文本文件,里面只有一个数字,不能有空格、换行以外的任何字符,最好连换行都不要。位置在dataDir下面,文件名就叫myid。
# zk-node1 上执行 echo 1 > /data/zookeeper/data/myid # zk-node2 上执行 echo 2 > /data/zookeeper/data/myid # zk-node3 上执行 echo 3 > /data/zookeeper/data/myid数字必须和zoo.cfg里server.N的 N 对应。三种错误最常见:一是三台写了同样的数字,导致其中一个节点在选举时被其他节点排斥;二是数字和配置对不上,比如 node2 上写了 3,那它就认为自己是 server.3,而真正的 server.3 也在争这个身份;三是用了echo "1 "这种带尾随空格的写法,ZooKeeper 解析时可能报NumberFormatException。
写完用cat -A myid看一眼,1$这样结尾是正常的,1 $就有问题了。这个命令能显示所有不可见字符,比cat直观得多。
4.2 分发配置的两种方式与各自的风险
配置分发无非两条路:手动在每台机器上改,或者在一台上改好再分发。前者慢但不容易错,后者快但有个致命的坑——myid也被一起覆盖了。
如果用scp复制整个目录,或者在安装目录层面同步,myid会跟着过去。结果就是三台机器的myid全变成同一个值。这个错误在启动阶段不一定立刻暴露,因为三台机器各自读到的myid都是合法的数字,只有在投票阶段才会发现身份冲突。
我的做法是:安装目录和zoo.cfg用分发的方式同步,dataDir和里面的myid各台单独建。分发完之后,逐台执行cat myid确认一次,这个动作花不了三十秒,但能省掉后面几小时的排查。
如果确实要在分发脚本里带上 dataDir,那就得在脚本末尾加一段按主机名写 myid 的逻辑。实现上可以按主机名做 case 分支,简单粗暴但可靠。
4.3 一致性自查清单
三台机器的配置分发完之后,按这张表逐项核对一遍,比出问题再回头查高效得多:
| 检查项 | 检查方法 | 期望结果 |
|---|---|---|
| 主机名解析 | 三台互 ping 对方主机名 | 都能通,延迟正常 |
| zoo.cfg 内容 | md5sum conf/zoo.cfg三台对比 | 三个值完全一致 |
| myid 数值 | cat data/myid | 分别为 1、2、3,无多余字符 |
| 端口监听规划 | ss -lntp | grep -E '2181|2888|3888' | 启动前应无输出 |
| 目录属主 | ls -ld data logs | 属主为运行 ZK 的用户 |
| 时间偏差 | 三台date对比 | 差在几秒以内 |
| 防火墙状态 | systemctl status firewalld或规则列表 | 三个端口对集群内放行 |
这张表看着啰嗦,但每一项对应的都是一类真实事故。尤其是 zoo.cfg 的 md5 对比,几秒钟就能确认三台配置真的一致,比自己肉眼逐行比对靠谱。
5. 启动、状态判读与客户端连通性验证
5.1 启动命令与输出解读
三台机器上分别执行:
cd /opt/zookeeper bin/zkServer.sh start正常输出类似这样:
ZooKeeper JMX enabled by default Using config: /opt/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED看到STARTED只是说明进程拉起来了,不代表它加入了集群。真正的状态要靠status命令看。启动顺序上没有硬性要求,三台一起启动、逐台启动都行,集群会自动完成选举。
有一点需要提醒:zkServer.sh的启动是非阻塞的,脚本返回的时候进程可能还在初始化。所以紧接着执行status,有时候会看到Error contacting service. It is probably not running.,这时候别急着判断失败,等三到五秒再看一次。如果你的tickTime设得比较大,这个等待时间会更长一些。
启动失败的时候,第一现场在logs/zookeeper.out。这个文件是zkServer.sh把标准输出重定向出来的,配置解析错误、myid读不到、端口被占用,都会直接打在这里。真正的业务日志在logs/zookeeper.log,但那是 log4j 配置生效之后才写的,进程起不来的时候只有zookeeper.out有内容。
5.2 status 输出的 leader/follower 判读
bin/zkServer.sh status正常情况下三台机器中会有一台输出:
Mode: leader另外两台输出:
Mode: follower这三个结果合起来才算集群正常。如果某台输出Mode: standalone,说明它没有读到zoo.cfg里的server.N配置,退化成单机模式了。常见原因是配置文件路径不对,或者zoo.cfg里的server.N那几行被注释掉了。
如果三台都显示follower,说明选举没完成,通常是节点之间连不上,去查 3888 端口和主机名解析。如果三台都显示leader,那是更奇怪的状态,一般出现在网络分区之后各自为政,俗称脑裂,实际上 ZooKeeper 的选举机制能避免真正的双主,看到的双 leader 往往是配置了不同的集群名或者挂载了不同的数据目录。
还有一种情况:status报Error contacting service,但进程明明在。这多半是因为clientPort或者客户端地址配错了,zkServer.sh内部是用nc或者 Java 客户端去探测本地端口的,探测不通就报这个错。先去ss -lntp看 2181 有没有真的监听。
5.3 四字命令的开启与使用
四字命令是排查问题的利器,但前提是4lw.commands.whitelist已经配好。几个常用的:
# 确认服务活着,返回 imok echo ruok | nc zk-node1 2181 # 查看节点角色、连接数、znode 数量等概要 echo srvr | nc zk-node1 2181 # 输出更详细的监控指标,适合接监控系统 echo mntr | nc zk-node1 2181 # 查看当前所有客户端连接 echo cons | nc zk-node1 2181srvr的输出里,Mode行给出角色,Znode count给出节点总数,Connections给出连接数。排查 HBase 连不上 ZK 的时候,先看Connections,如果一直是 0,说明问题在客户端侧;如果有大量连接但 HBase 还是报错,那可能是 znode 层级的问题。
mntr的输出是键值对形式,非常适合做监控采集。重点关注zk_avg_latency、zk_max_latency、zk_outstanding_requests,这几个值突然抬高,通常意味着磁盘 IO 有问题或者 GC 停顿变长。
5.4 zkCli 实测路径
命令行的连通性证明完之后,用客户端真正走一遍读写:
bin/zkCli.sh -server zk-node1:2181,zk-node2:2181,zk-node3:2181连上之后执行几条基本命令:
ls / create /demo test-value get /demo set /demo new-value delete /demo quit这里有个实用细节:连接串里把三个节点全写上,客户端连上一个之后,后续的会话迁移和故障切换都靠这份列表。只写一个地址的话,那台机器挂了客户端就彻底失联了,虽然可以重连,但少了自动切换的能力。
另外提醒一句,在 HBase 集群已经在跑的环境里,千万别手贱去deleteall /hbase或者rmr /hbase。这个节点下面挂的是整个集群的元数据视图,删掉之后 HBase 的表在逻辑上全部丢失,虽然 HDFS 上的数据文件还在,但恢复起来极其麻烦,需要走离线元数据修复流程。测试环境玩玩可以,生产环境这条命令应该被当成红线。
6. 现场排错:启动失败、连不上、假脑裂
6.1 端口与防火墙
启动失败最常见的原因就是端口被占。排查顺序是:先用ss -lntp | grep 2181看谁在占,再决定是改端口还是杀进程。有些系统自带的包管理版本会在开机时自动拉起一个 ZooKeeper,端口悄无声息地被占了,手动启动就报Address already in use。
防火墙这块,主机层面和云环境的网络策略层都要确认。主机上firewall-cmd --list-all看放行规则,或者干脆在测试环境关掉防火墙,避免它成为干扰项。但要注意,firewall-cmd关掉之后,某些系统上的服务重启会重新加载默认规则,所以要么永久关闭,要么把 2181、2888、3888 显式加进放行列表。
验证端口通不通,别用telnet,现在很多系统默认不装。用nc -zv zk-node2 2888更直接,返回succeeded就是通的。另外,从 node1 到 node2 和从 node2 到 node1 都要验证,单向放行的规则在某些策略配置下是存在的。
6.2 myid 冲突的典型日志特征
myid重复的时候,日志里会出现非常典型的记录:
Unexpected exception, exiting abnormally java.lang.RuntimeException: My id 2 not in the peer list或者
Have smaller server identifier, so dropping the connection还有一种情况是三台都写了 1,集群能起来,但只有一台能稳定运行,另外两台反复重启并重连。这种“看起来正常但只有一部分节点工作”的状态最有迷惑性,因为当你只看status那台正常的节点时,会觉得配置没问题。
排查方式很直接:逐台cat data/myid,并把结果和zoo.cfg里的server.N逐行对照。我习惯在节点规划阶段就画一张表,把主机名、IP、server 编号、myid 内容一一列清楚,配完逐项打钩,能从根本上避免这类问题。
6.3 会话超时引发的诡异现象
前面讲过tickTime和 HBase 会话超时的关系,这里说说实际症状。
现象一:HBase 集群能启动,hbase shell也能连上,但每隔几十秒就卡一下,日志里频繁出现 RegionServer 重新注册的记录。这种“间歇性抽搐”几乎都是会话超时被压得太短导致的。
现象二:某台 RegionServer 的进程明明在,jps能看到,但 HMaster 认为它已经下线,region 被搬走。用echo cons | nc zk-nodeX 2181看,会发现这台机器的连接数在周期性波动。
排查方向有两个:一是确认 ZK 侧tickTime的设置,算一下20 * tickTime是不是大于 HBase 的zookeeper.session.timeout;二是查 RegionServer 的 GC 日志,看有没有长时间停顿。如果 GC 单次停顿超过了会话超时的一半,那不管 ZK 怎么配都不够安全,得从内存参数上解决。
还有一个隐蔽因素:虚拟机的 CPU 超卖严重时,进程会被调度器长时间挂起,从外部看就像是进程假死。这种情况下 ZK 判超时是完全正确的行为,问题出在资源分配上。
6.4 磁盘与日志暴涨
autopurge没配的情况下,dataLogDir里的文件会一直累积。ZL 的事务日志默认不删,快照也不删。跑几个月之后,磁盘占用是线性增长的。
发现磁盘快满的时候,第一步先看是哪个目录占的,du -sh data/* logs/*一目了然。确认是历史日志之后,可以手动清理,但必须遵守一条原则:只删已经生成快照覆盖到的日志。具体做法是看最新的快照文件名里的 zxid,比它小的日志都可以安全删除。
更稳妥的方式是把autopurge.purgeInterval和autopurge.snapRetainCount配上,让 ZK 自己管理。这两个参数是在zoo.cfg里配的,改完重启生效。
日志文件本身也可能暴涨。默认的 log4j 配置里,zookeeper.log没有按大小滚动的话,长期运行会变成一个巨大的文件。虽然不影响功能,但出问题的时候打不开、搜不动,非常影响排查效率。建议在 log4j 配置里加上按天和按大小双维度滚动。
7. 交给 HBase 之前的最后一步核对
7.1 hbase-site.xml 里与 ZK 相关的四个参数
ZK 集群立起来并且三台状态自洽之后,才轮到 HBase 侧的对接。这一步在完整分布式配置里属于下一篇的内容,但这里先把和 ZK 直接相关的四个参数说清楚,方便回头验证。
<property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <property> <name>hbase.rootdir</name> <value>hdfs://mycluster/hbase</value> </property> <property> <name>hbase.zookeeper.quorum</name> <value>zk-node1,zk-node2,zk-node3</value> </property> <property> <name>hbase.zookeeper.property.clientPort</name> <value>2181</value> </property> <property> <name>zookeeper.znode.parent</name> <value>/hbase</value> </property>hbase.zookeeper.quorum里写的是主机名列表,中间用逗号分隔,不要带端口,端口由hbase.zookeeper.property.clientPort单独指定。这是个高频错误:把zk-node1:2181这种带端口的写法塞进 quorum,连接会失败。
zookeeper.znode.parent是 HBase 在 ZK 里使用的根节点,默认是/hbase。如果同一个 ZK 集群要供多套 HBase 使用,就得改成不同的路径做隔离。改这个参数的时候要注意,路径变了,之前的元数据就找不到了,相当于重新初始化。
另外,HBase 侧的zookeeper.session.timeout和 ZK 侧的tickTime要放在一起看。前面反复强调的20 * tickTime上限,就是在这里生效的。
7.2 znode 隔离与启动顺序
启动 HBase 之前,先用zkCli看一眼根节点:
bin/zkCli.sh -server zk-node1:2181 ls /此时应该只有/zookeeper一个节点,这是 ZK 自己用的。如果已经能看到/hbase,说明这台 ZK 之前跑过 HBase,或者别的集群指过来了。这种情况下要确认是不是同一个业务,别不小心两套集群共用一个 znode 路径,那会互相踩踏。
HBase 启动之后,ls /应该能看到/hbase;再ls /hbase能看到meta-region-server、master、rs、backup-masters等节点。这些节点在,说明 HBase 和 ZK 的对接是通的。
关闭顺序上有个不成文的规矩:先停 HBase,再停 ZK。反过来操作的话,HBase 组件会先经历一轮会话超时和重连风暴,日志里刷一堆异常,虽然最终也能停下来,但容易让人误判成故障。如果是计划内维护,按这个顺序来,日志干净,排查也方便。
我在实际配置中体会最深的一点是:ZK 这套东西的配置项统共就那么十几个,但每一个都对应着一类故障模式。照着教程抄下来能跑,不代表理解了为什么这么配;只有把tickTime和会话超时的换算、myid和server.N的对应、dataDir和dataLogDir分开的动机这三件事想明白,后面不管集群规模怎么扩、版本怎么升,都能自己推导出该调哪些参数。下一篇接着讲 HBase 主体配置的时候,这些前置知识会一直用到。