深入解读HBase2.0新功能之AssignmentManagerV2
背景
AssignmentManager模块是HBase中一个非常重要的模块,Assignment Manager(之后简称AM)负责了HBase中所有region的Assign,UnAssign,以及split/merge过程中region状态变化的管理等等。在HBase-0.90之前,AM的状态全部存在内存中,自从HBASE-2485之后,AM把状态持久化到了Zookeeper上。在此基础上,社区对AM又修复了大量的bug和优化(见此文章),最终形成了用在HBase-1.x版本上的这个AM。
老Assignment Mananger的问题
相信深度使用过HBase的人一般都会被Region RIT的状态困扰过,长时间的region in transition状态简直令人抓狂。
除了一些确实是由于Region无法被RegionServer open的case,大部分的RIT,都是AM本身的问题引起的。总结一下HBase-1.x版本中AM的问题,主要有以下几点:
region状态变化复杂
这张图很好地展示了region在open过程中参与的组件和状态变化。可以看到,多达7个组件会参与region状态的变化。并且在region open的过程中多达20多个步骤!越复杂的逻辑意味着越容易出bug
region状态多处缓存
region的状态会缓存在多个地方,Master中RegionStates会保存Region的状态,Meta表中会保存region的状态,Zookeeper上也会保存region的状态,要保持这三者完全同步是一件很困难的事情。同时,Master和RegionServer都会修改Meta表的状态和Zookeeper的状态,非常容易导致状态的混乱。如果出现不一致,到底以哪里的状态为准?每一个region的transition流程都是各自为政,各自有各自的处理方法
重度依赖Zookeeper
在老的AM中,region状态的通知完全通过Zookeeper。比如说RegionServer打开了一个region,它会在Zookeeper把这个region的RIT节点改成OPEN状态,而不去直接通知Master。Master会在Zookeeper上watch这个RIT节点,通过Zookeeper的通知机制来通知Master这个region已经发生变化。Master再根据Zookeeper上读取出来的新状态进行一定的操作。严重依赖Zookeeper的通知机制导致了region的上线/下线的速度存在了一定的瓶颈。特别是在region比较多的时候,Zookeeper的通知会出现严重的滞后现象。
正是这些问题的存在,导致AM的问题频发。我本人就fix过多个AM导致region无法open的issue。比如说这三个相互关联的“连环”case:HBASE-17264,HBASE-17265,HBASE-17275。
Assignment Mananger V2
面对这些问题的存在,社区也在不断尝试解决这些问题,特别是当region的规模达到100w级别的时候,AM成为了一个严重的瓶颈。HBASE-11059中提出的ZK-less Region Assignment就是一个非常好的改良设计。在这个设计中,AM完全摆脱了Zookeeper的限制,在测试中,zk-less的assign比zk的assign快了一个数量级!
但是在这个设计中,它摒弃了Zookeeper这个持久化的存储,一些region transition过程中的中间状态无法被保存。因此,在此基础上,社区又更进了一步,提出了Assignment Mananger V2在这个方案。在这个方案中,仍然摒弃了Zookeeper参与Assignment的整个过程。但是,它引入了ProcedureV2这个持久化存储来保存Region transition中的各个状态,保证在master重启时,之前的assing/unassign,split等任务能够从中断点重新执行。具体的来说,AMv2方案中,主要的改进有以下几点:
Procedure V2
关于Procedure V2,我之后将独立写文章介绍。这里,我只大概介绍下ProcedureV2和引入它所带来的价值。
我们知道,Master中会有许多复杂的管理工作,比如说建表,region的transition。这些工作往往涉及到非常多的步骤,如果master在做中间某个步骤的时候宕机了,这个任务就会永远停留在了中间状态(RIT因为之前有Zookeeper做持久化因此会继续从某个状态开始执行)。比如说在enable/disable table时,如果master宕机了,可能表就停留在了enabling/disabling状态。需要一些外部的手段进行恢复。那么从本质上来说,ProcedureV2提供了一个持久化的手段(通过ProcedureWAL,一种类似RegionServer中WAL的日志持久化到HDFS上),使master在宕机后能够继续之前未完成的任务继续完成。同时,ProcedureV2提供了非常丰富的状态转换并支持回滚执行,即使执行到某一个步骤出错,master也可以按照用户的逻辑对之前的步骤进行回滚。比如建表到某一个步骤失败了,而之前已经在HDFS中创建了一些新region的文件夹,那么ProcedureV2在rollback的时候,可以把这些残留删除掉。
Procedure中提供了两种Procedure框架,顺序执行和状态机,同时支持在执行过程中插入subProcedure,从而能够支持非常丰富的执行流程。在AMv2中,所有的Assign,UnAssign,TableCreate等等流程,都是基于Procedure实现的。
去除Zookeeper依赖
有了Procedure V2之后,所有的状态都可以持久化在Procedure中,Procedure中每次的状态变化,都能够持久化到ProcedureWAL中,因此数据不会丢失,宕机后也能恢复。同时,AMv2中region的状态扭转(OPENING,OPEN,CLOSING,CLOSE等)都会由Master记录在Meta表中,不需要Zookeeper做持久化。再者,之前的AM使用的Zookeeper watch机制通知master region状态的改变,而现在每当RegionServer Open或者close一个region后,都会直接发送RPC给master汇报,因此也不需要Zookeeper来做状态的通知。综合以上原因,Zookeeper已经在AMv2中没有了存在的必要。
减少状态冲突的可能性
之前我说过,在之前的AM中,region的状态会同时存在于meta表,Zookeeper和master的内存状态。同时Master和regionserver都会去修改Zookeeper和meta表,维护状态统一的代价非常高,非常容易出bug。而在AMv2中,只有master才能去修改meta表。并在region整个transition中做为一个“权威”存在,如果regionserver汇报上来的region状态与master看到的不一致,则master会命令RegionServer abort。Region的状态,都以master内存中保存的RegionStates为准。
除了上述这些优化,AMv2中还有许多其他的优化。比如说AMv2依赖Procedure V2提供的一套locking机制,保证了对于一个实体,如一张表,一个region或者一个RegionServer同一时刻只有一个Procedure在执行。同时,在需要往RegionServer发送命令,如发送open,close等命令时,AMv2实现了一个RemoteProcedureDispatcher来对这些请求做batch,批量把对应服务器的指令一起发送等等。在代码结构上,之前处理相应region状态的代码散落在AssignmentManager这个类的各个地方,而在AMv2中,每个对应的操作,都有对应的Procedure实现,如AssignProcedure,DisableTableProcedure,SplitTableRegionProcedure等等。这样下来,使AssignmentManager这个之前杂乱的类变的清晰简单,代码量从之前的4000多行减到了2000行左右。
AssignProcedure
AMv2中有太多的Procedure对应各种不同的transition,这里不去详细介绍每个Procedure的操作。我将以AssignProcedure为例,讲解一下在AMv2中,一个region是怎么assign给一个RegionServer,并在对应的RS上Open的。
AssignProcedure是一个基于Procedure实现的状态机。它拥有3个状态:
- REGION_TRANSITION_QUEUE: Assign开始时的状态。在这个状态时,Procedure会对region状态做一些改变和存储,并丢到AssignmentManager的assign queue中。对于单独region的assign,AssignmentManager会把他们group起来,再通过LoadBalancer分配相应的服务器。当这一步骤完成后,Procedure会把自己标为REGION_TRANSITION_DISPATCH,然后看是否已经分配服务器,如果还没有被分配服务器的话,则会停止继续执行,等待被唤醒。
- REGION_TRANSITION_DISPATCH: 当AssignmentManager为这个region分配好服务器时,Procedure就会被唤醒。或者Procedure在执行完REGION_TRANSITION_QUEUE状态时master宕机,Procedure被恢复后,也会进入此步骤执行。所以在此步骤下,Procedure会先检查一下是否分配好了服务器,如果没有,则把状态转移回REGION_TRANSITION_QUEUE,否则的话,则把这个region交给RemoteProcedureDispatcher,发送RPC给对应的RegionServer来open这个region。同样的,RemoteProcedureDispatcher也会对相应的指令做一个batch,批量把一批region open的命令发送给某一台服务器。当命令发送完成之后,Procedure又会进入休眠状态,等待RegionServer成功OPen这个region后,唤醒这个Procedure
- REGION_TRANSITION_FINISH: 当有RegionServer汇报了此region被打开后,会把Procedure的状态置为此状态,并唤醒Procedure执行。此时,AssignProcedure会做一些状态改变的工作,并修改meta表,把meta表中这个region的位置指向对应的RegionServer。至此,region assign的工作全部完成。
AMv2中提供了一个Web页面(Master页面中的‘Procedures&Locks’链接)来展示当前正在执行的Procedure和持有的锁。
其实通过log,我们也可以看到Assign的整个过程。
假设,一台server宕机,此时master会产生一个ServerCrashProcedure 来处理,在这个Procedure中,会做一系列的工作,比如WAL的restore。当这些前置的工作做完后,就会开始assign之前在宕掉服务器上的region,比如56f985a727afe80a184dac75fbf6860c。此时会在ServerCrashProcedure产生一系列的子任务:
2017-05-23 12:04:24,175 INFO [ProcExecWrkr-30] procedure2.ProcedureExecutor: Initialized subprocedures=[{pid=1178, ppid=1176, state=RUNNABLE:REGION_TRANSITION_QUEUE; AssignProcedure table=IntegrationTestBigLinkedList, region=bfd57f0b72fd3ca77e9d3c5e3ae48d76, target=ve0540.halxg.cloudera.com,16020,1495525111232}, {pid=1179, ppid=1176, state=RUNNABLE:REGION_TRANSITION_QUEUE; AssignProcedure table=IntegrationTestBigLinkedList, region=56f985a727afe80a184dac75fbf6860c, target=ve0540.halxg.cloudera.com,16020,1495525111232}]
可以看到,ServerCrashProcedure的pid(Procedure ID)为1178,在此Procedure中产生的assign 56f985a727afe80a184dac75fbf6860c这个region的子Procedure的pid为1179,同时他的ppid(Parent Procedure ID)为1178。在AMv2中,通过追踪这些ID,就非常容易把一个region的transition整个过程全部串起来。
接下来,pid=1170这个Procedure开始执行,首先执行的是REGION_TRANSITION_QUEUE状态的逻辑,然后进入睡眠状态。
2017-05-23 12:04:24,241 INFO [ProcExecWrkr-30] assignment.AssignProcedure: Start pid=1179, ppid=1176, state=RUNNABLE:REGION_TRANSITION_QUEUE; AssignProcedure table=IntegrationTestBigLinkedList, region=56f985a727afe80a184dac75fbf6860c, target=ve0540.halxg.cloudera.com,16020,1495525111232; rit=OFFLINE, location=ve0540.halxg.cloudera.com,16020,1495525111232; forceNewPlan=false, retain=false
当target server被指定时,Procedure进入REGION_TRANSITION_DISPATCH状态,dispatch了region open的请求,同时把meta表中region的状态改成了OPENING,然后再次进入休眠状态
2017-05-23 12:04:24,494 INFO [ProcExecWrkr-38] assignment.RegionStateStore: pid=1179 updating hbase:meta row=IntegrationTestBigLinkedList,H\xE3@\x8D\x964\x9D\xDF\x8F@9'\x0F\xC8\xCC\xC2,1495566261066.56f985a727afe80a184dac75fbf6860c., regionState=OPENING, regionLocation=ve0540.halxg.cloudera.com,16020,1495525111232 2017-05-23 12:04:24,498 INFO [ProcExecWrkr-38] assignment.RegionTransitionProcedure: Dispatch pid=1179, ppid=1176, state=RUNNABLE:REGION_TRANSITION_DISPATCH; AssignProcedure table=IntegrationTestBigLinkedList, region=56f985a727afe80a184dac75fbf6860c, target=ve0540.halxg.cloudera.com,16020,1495525111232; rit=OPENING, location=ve0540.halxg.cloudera.com,16020,1495525111232
最后,当RegionServer打开了这个region后,会发RPC通知master,那么在通知过程中,这个Procedure再次被唤醒,开始执行REGION_TRANSITION_FINISH的逻辑,最后更新meta表,把这个region置为打开状态。
2017-05-23 12:04:26,643 DEBUG [RpcServer.default.FPBQ.Fifo.handler=46,queue=1,port=16000] assignment.RegionTransitionProcedure: Received report OPENED seqId=11984985, pid=1179, ppid=1176, state=RUNNABLE:REGION_TRANSITION_DISPATCH; AssignProcedure table=IntegrationTestBigLinkedList, region=56f985a727afe80a184dac75fbf6860c, target=ve0540.halxg.cloudera.com,16020,1495525111232; rit=OPENING, location=ve0540.halxg.cloudera.com,16020,1495525111232 2017-05-23 12:04:26,643 INFO [ProcExecWrkr-9] assignment.RegionStateStore: pid=1179 updating hbase:meta row=IntegrationTestBigLinkedList,H\xE3@\x8D\x964\x9D\xDF\x8F@9'\x0F\xC8\xCC\xC2,1495566261066.56f985a727afe80a184dac75fbf6860c., regionState=OPEN, openSeqNum=11984985, regionLocation=ve0540.halxg.cloudera.com,16020,1495525111232 2017-05-23 12:04:26,836 INFO [ProcExecWrkr-9] procedure2.ProcedureExecutor: Finish suprocedure pid=1179, ppid=1176, state=SUCCESS; AssignProcedure table=IntegrationTestBigLinkedList, region=56f985a727afe80a184dac75fbf6860c, target=ve0540.halxg.cloudera.com,16020,1495525111232
一路看下来,由于整个region assign的过程都是在Procedure中执行,整个过程清晰明了,非常容易追述,也没有了Zookeeper一些event事件的干扰。
总结
Assignment Mananger V2依赖Procedure V2实现了一套清晰明了的region transition机制。去除了Zookeeper依赖,减少了region状态冲突的可能性。整体上来看,代码的可读性更强,出了问题也更好查错。对于解决之前AM中的一系列“顽疾”,AMv2做了很好的尝试,也是一个非常好的方向。
AMv2之所以能保持简洁高效的一个重要原因就是重度依赖了Procedure V2,把一些复杂的逻辑都转移到了Procedure V2中。但是这样做的问题是:一旦ProcedureWAL出现了损坏,或者Procedure本身存在bug,这个后果就是灾难性的。事实上在我们的测试环境中,就出现过PRocedureWAL损坏导致region RIT的情况。
另外需要注意的是,截止目前为止,HBCK仍然无法支持AMv2,这会导致一旦出现问题,修复起来会比较困难。
当然,新的事务还是要有一段成熟期,相信经过一段时间的bug修复和完善后,我相信AMv2一定会完美解决之前的一些问题,给HBase的运维上带来一些不同的体验。愿世界不再被HBase的RIT困扰 :-)。
云端使用
阿里HBase目前已经在阿里云提供商业化服务,任何有需求的用户都可以在阿里云端使用深入改进的、一站式的HBase服务。云HBase版本与自建HBase相比在运维、可靠性、性能、稳定性、安全、成本等方面均有很多的改进。
同时,云HBase2.0 在2018年6月6日正式发布,点击了解更多!