ZooKeeper在大型分布式系统中的应用之HBase。

        HBase在实现上严格遵守了Google BigTable论文的设计思想。BigTable使用Chubby类负责分布式状态的协调,Chubby,这是Google实现的一种基于Paxos算法的分布式锁服务,而HBase则采用了开源的ZooKeeper服务来完成对整个系统的分布式协调工作。下图中展示了整个HBase架构及其与ZooKeeper之间的结构关系。

ZooKeeper在大型分布式系统中的应用之HBase。

         从上图中可以看到,在HBase的整个架构体系中,ZooKeeper是串联起HBase集群与Client的关键所在。有趣的是,在2009年以前的HBase代码中,还看不到ZooKeeper的影子,因为当时HBase的定位是离线数据库。随着HBase逐步向在线分布式存储方向发展,出现了一系列难以解决的问题,例如开发者发现如果有RegionServer服务器挂掉时,系统无法及时得到信息,客户端也无法知晓,因此服务难以快速迁移至其他RegionServer服务器上—— 类似问题都是因为缺少相应的分布式协调组件,于是后来ZooKeeper被加入到HBase的技术体系中。直到今天,ZooKeeper依然是HBase的核心组件,而且ZooKeeper在HBase中的应用场景范围也已经得到了进一步的扩展。下面我们从系统冗错、RootRegion管理、Region状态管理、分布式SplitLog任务管理和Replication管理五大方面来讲解ZooKeeper在HBase中的应用场景。

系统冗错

        当HBase启动的时候,每个RegionServer服务器都会到ZooKeeper的/hbase/rs节点下创建一个信息节点(下文中,我们称该节点为“rs状态节点”),例如/hbase/rs/[Hostname],同时,HMaster会对这个节点注册监听。当某个RegionServer挂掉的时候,ZooKeeper会因为在一段时间内无法接收其心跳信息(即Session失效),而删除掉该RegionServer服务器对应的rs状态节点。与此同时,HMaster则会接收到ZooKeeper的NodeDelete通知,从而感知到某个节点断开,并立即开始冗错工作——在HBase的实现中,HMaster会将该RegionServer所处理的数据分片(Region)重新路由到其他节点上,并记录到Meta信息*客户端查询。

        讲到这里,可能有的读者会发问:HBase为什么不直接让HMaster来负责进行RegionServer的监控呢?HBase之所以不适用HMaster直接通过心跳机制等来管理RegionServer状态,是因为在这种方式下,随着系统容量的不断增加,HMaster的管理负担会越来越重。另外他自身也有挂掉的可能,因此数据还需要有持久化的必要。在这种情况下,ZooKeeper就成为了理想的选择。

RootRegion管理

        对于HBase集群来说,数据存储的位置信息是记录在元数据分片,也就是RootRegion上的。每次客户端发起新的请求,需要知道数据的位置,就会去查询RootRegion,而RootRegion自身的位置则是记录在ZooKeeper上的(默认情况下,是记录在ZooKeeper的/hbase/root-region-server节点中)。当RootRegion发生变化,比如Region的手工移动、Balance或者是RootRegion所在服务器发生了故障等时,就能够通过ZooKeeper来感知到这一变化并做出一系列相应的容灾措施,从而保障客户端总是能拿到正确的RootRegion信息。

Region状态管理

        Region是HBase中数据的物理切片,每个Region中记录了全局数据的一小部分,并且不同的Region之间的数据是相互不重复的。但对于一个分布式系统来说,Region是会经常发生变更的,这些变更的原因来自于系统故障、负载均衡、配置修改、Region分裂与合并等。一旦Region发生移动,他必然会经历Offline和重新Online的过程。

        在Offline期间数据是不能被访问的,并且Region的这个状态变化必须让全局知晓,否则可能会出现某些事务性的异常。而对于HBase集群来说,Region的数量可能会多达10万级别,甚至更多,因此这样规模的Region状态管理也只有依靠ZooKeeper这样的系统才能做到。

分布式SplitLog任务管理

        当某台RegionServer服务器挂掉时,由于总有一部分新写入的数据还没有持久化到HFile中,因此在迁移该RegionServer的服务时,一个重要的工作就是从HLog汇总恢复这部分还在内存中的数据,而这部分工作最关键的一步就是SplitLog,即HMaster需要遍历该RegionServer服务器的HLog,并按Region切分成小块移动到新的地址下,并进行数据的Replay。

        由于单个RegionServer的日志量相对庞大(可能有数千个Region,上GB的日志),而用户又往往希望系统能够快速完成日志的恢复工作。因此一个可行的方案是将这个处理HLog的任务分配给多台RegionServer服务器来共同处理,而这就又需要一个持久化组件来辅助HMaster完成任务的分配。当前的做法是,HMaster会在ZooKeeper上创建一个splitlog的节点(默认情况下,是/hbase/splitlog节点),将“哪个RegionServer处理哪个Region”这样的信息以列表的形式存放到该节点上,然后由各个RegionServer服务器自行到该节点上去领取任务并在任务执行成功或失败后再更新该节点的信息,以通知HMaster继续进行后面的步骤。ZooKeeper在这里担负起了分布式集群中相互通知和信息持久化的角色。

Replication管理

        Replication是实现HBase中主备集群间的实时同步的重要模块。有了Replication,HBase就能实现实时的主备同步,从而拥有了容灾和分流等关系型数据库才拥有的功能,从而大大加强了HBase的可用性,同时也扩展了其应用场景。和传统关系型数据库的Replication功能所不同的是,HBase作为分布式系统,他的Replication是多对多的,且每个节点随时都有可能挂掉,因此在这样的场景下做Replication要比普通数据库复杂的多。

        HBase同样借助ZooKeeper来完成Replication功能。做法是在ZooKeeper上记录一个replication节点(默认情况下,是/hbase/replication节点),然后把不同的RegionServer服务器对应的HLog文件名称记录到相应的节点上,HMaster集群会将新增的数据推送给Slave集群,并同时将推送信息记录到ZooKeeper上(我们将这个信息称为“断点记录”),然后再重复以上过程。当服务器挂掉时,由于ZooKeeper上已经保存了断点信息,因此只要有HMaster能够根据这些断点信息来协调用来推送HLog数据的主节点服务器,就可以继续复制了。

ZooKeeper部署

        下面我们再来看下HBase中是如何进行ZooKeeper部署的。HBase的启动脚本(hbase-env.sh)中可以选择是由HBase启动其自带的默认ZooKeeper,还是使用一个已有的外部ZooKeeper集群。一般的建议是使用第二种方式,因为这样就可以使得多个HBase集群需要被几个HBase复用的话,那么务必为每一个HBase集群明确指明对应的ZooKeeper根节点配置(对应的配置项是zookeeper.znode.parent),以确保各个HBase集群间互不干扰。而对于HBase的客户端来说,只需要指明ZooKeeper的集群地址以及对应的HBase根节点配置即可,不需要任何其他配置。当HBase集群启动的时候,会在ZooKeeper上诸葛添加相应的初始化节点,并在HMaster以及RegionServer进程中进行相应节点的Watcher注册。

小结

        以上就是一些HBase系统中依赖ZooKeeper完成分布式协调功能的典型场景。但事实上,HBase对于ZooKeeper的依赖还不止这些,比如HMaster依赖ZooKeeper来完成ActiveMaster的选举、BackupMaster的实时接管、Table的enable/disable状态记录,以及HBase中几乎所有的元数据存储都是放在ZooKeeper上的。有趣的是,HBase甚至还通过ZooKeeper来实现DrainingServer这样的增强功能(相当于降级标志)。事实上,由于ZooKeeper出色的分布式协调能力以及良好的通知机制,HBase在个版本的演进过程中越来越多的增加了ZooKeeper的应用场景,从趋势上来看两者的交集越来越多。HBase中所有对ZooKeeper的操作都封装在了org.apache.hadoop.hbase.zookeeper这个包中。