HBase因hostname可能引起的RIT问题
HBase的RegionServer会将自己的hostname上报到zookeeper,客户端连接zookeeper时,获取的是regionserver的hostname,再由hostname获得regionserver的ip地址。基于hbase的这种名称上报机制,客户端连接hbase时,需要能够ping通hbase的hostname,但是如果把hbase的hostname分发到所有的服务器上,毕竟是不现实的。一种可选择的方法,就是以hbase的hostname作为域名配置到DNS,这样客户端服务器也能够通过ping hostname获取服务器的实际ip。是不是这样就够了?所有服务器的/etc/hosts都不用再做更改了吗?本文就是基于上面的背景介绍一个使用HBase过程中可能会遇到一个大深坑。而这也是hbase的一个bug。
我们创造这样一个场景:
在master节点的/etc/hosts中去掉regionserverA(假设其ip地址为10.0.0.1)的hostname,而在regionserverA本地的/etc/hosts中保留了它的hostname,然后重启该regionserver,随后可以发现在master的web页面上,该regionserver上报的名称为其ip地址,也就是10.0.0.1。而在zookeeper的rs路径下,我们看到zookeeper所登记的regionserver名字是regionserverA。这就是说该节点在zk和master的视角有不同的名字,在zookeeper上它是自己的hostname,而在master看来,它报过去的是自己的ip。
针对上述情况,集群有什么风险。我们继续试验,把10.0.0.1节点上的regionserver停掉。此时登录zookeeper可以看到对应的zookeeper节点已经被删掉,但是master认为这个zk节点所代表的regionserver不属于自己,master自己看到的是10.0.0.1而不是regionserverA,所以master不会去处理这台regionserver的shutdown。也就不会针对这台regionserver上的region做rebalance,造成该regionserver上的region实际不可用。
原因分析:
Regionserver的名字会在两处被加载到HMaster中,HMaster包含一个成员变量RegionServerTracker,这个Tracker继承了ZookeeperListener接口,用于当zookeeper上rs节点下的子节点发生变化时,通知master去处理这些子节点(包括删除/新增regionserver)。
RegionServer启动时,首先以自己的servername为名在zookeeper上创建一个临时节点。servername是由如下几个部分构成:
String hostName = shouldUseThisHostnameInstead()?useThisHostnameInstead : rpcServices.isa.getHostName();
serverName = ServerName.valueOf(hostname, rpcServer.isa.getPort(), startcode);
Regionserver启动时通过InetSocketAddress获得了本地的hostanme,以hostname作为servername的一部分写到zookeeper上。
regionserver启动之后会向master汇报自身已经startup,通过调用reportForDuty(),将regionserver的名字和当前regionserver上的load汇报给master,master如何接收这些汇报信息的代码比较关键,我们把master上的处理代码列出在下面:
ServerName regionServerStartup(RegionServerStartupRequest request, InetAddress ia) throws IOException {
final String hostname = request.hasUseThisHostnameInstead() ?
request.getUseThisHostnameInstead() :ia.getHostName(); ServerName sn = ServerName.valueOf(hostname, request.getPort(), request.getServerStartCode());
checkClockSkew(sn, request.getServerCurrentTime());
checkIsDead(sn, "STARTUP");
if (!checkAndRecordNewServer(sn, ServerLoad.EMPTY_SERVERLOAD)) {
LOG.warn("THIS SHOULD NOT HAPPEN, RegionServerStartup" + " could not record the server: " + sn);
}
return sn;
}
Master调用本地的InetAddress去获取hostname,如果regionserver的hostname没有写到master的/etc/hosts,那么在master看来,regionserver的名字就是ip:port:startcode,而不是hostname:port:startcode。regionServerStartup将返回的servername写到zookeeper上。
如上造成master所看到的regionserver名字与其regionserver进程自身注册到zookeeper上的不一致。这带来的风险就是当regionserver挂掉时,regionserver写到zookeeper上rs路径下的临时节点会自动删掉,master是通过RegionserverTracker去监听rs路径下节点变化的
@Override
public void nodeDeleted(String path) {
if (path.startsWith(watcher.rsZNode)) {
String serverName = ZKUtil.getNodeName(path);
LOG.info("RegionServer ephemeral node deleted, processing expiration [" + serverName + "]");
ServerName sn = ServerName.parseServerName(serverName);
if (!serverManager.isServerOnline(sn)) {
LOG.warn(serverName.toString() + " is not online or isn't known to the master."+ "The latter could be caused by a DNS misconfiguration.");
return;
}
remove(sn);
this.serverManager.expireServer(sn);
}
}
master处理regionserver临时节点消失的代码如上所示,其会调用serverManager的expireServer处理消失servername所对应的regionserver,在expireServer中会发起一个ServerCrashProcedure,Procedure是HBase中实现了2PC的分布式执行框架,有协调者和参与者两种角色,两种角色分别运行在HMaster和HRegionServer上,关于procedure的实现细节不是本文的重点,这里只需理解ServerCrashProcedure中master将dead regionserver上的region分配给其它的live regionserver,由这些live regionserver继续对外提供服务。
需要注意的的是serverManager.isServerOnline(sn)这句代码,如果zookeeper上获得的servername不在master所认为的onlineServers里面,那么根据这里的判断函数就会直接短路返回。也就不会执行到后续的expireServer,这台挂掉regionserver上的region也就不会被处理。
而当我们修改了hosts,并重启了regionserver之后,这台regionserver会以新的hostname注册上来,master看到一台新的regionserver,会开始region balance,这时master可能就会去那台旧的以ip注册的regionserver上去close region,但是实际上这台regionserver已经shutdown了,master已经连不上这台服务器,所以master试图去close这台机器上的region时会报failed close错误。
处理措施:
重启master节点,由备master接管之后,问题消失。
切换master之后,所有regionserver重新汇报自己的region,这时master会发现哪些region不在线,进而去分配这些region,重新让它们上线。
总结与反思:
RIT问题可以分两大类,一类是由于hdfs上的文件错误导致,比如写入了格式不准确的文件、文件的.regioninfo丢失或者瞬间大量小文件造成compact阻塞,一般此类问题深层次原因是由于硬盘故障或者服务器的突然断电造成。解决方式需要删除引起错误的文件。
还有一类是由于master上的信息与zookeeper或者hdfs上持久化的不一致,导致master出现错误的行为,比如去close已经不在线regionserver上的region,此类问题的解决需要切换master,此时regionserver会重新上报自己的信息,master可以在这时更新自己的roadmap,修正错误。