1.背景
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。
2.老 Assignment Mananger 的问题
相信深度使用过 HBase 的人一般都会被 Region RIT 的状态困扰过,长时间的 region in transition 状态简直令人抓狂。
除了一些确实是由于 Region 无法被 RegionServer open 的 case,大部分的 RIT, 都是 AM 本身的问题引起的。总结一下 HBase-1.x 版本中 AM 的问题,主要有以下几点:
3.region 状态变化复杂
这张图很好地展示了 region 在 open 过程中参与的组件和状态变化。可以看到, 多达 7 个组件会参与 region 状态的变化。并且在 region open 的过程中多达 20 多个步骤!越复杂的逻辑意味着越容易出 bug
4.region 状态多处缓存
region 的状态会缓存在多个地方,Master 中 RegionStates 会保存 Region 的状 态,Meta 表中会保存 region 的状态,Zookeeper 上也会保存 region 的状态,要保持这三者完全同步是一件很困难的事情。同时,Master 和 RegionServer 都会 修改 Meta 表的状态和 Zookeeper 的状态,非常容易导致状态的混乱。如果出现 不一致,到底以哪里的状态为准?每一个 region 的 transition 流程都是各自为政, 各自有各自的处理方法
5.重度依赖 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。
6.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 方案中,主要的改进有以下几点:
7.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 实现的。
8.去除 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 中没有了存在的必要。
9.减少状态冲突的可能性
之前我说过,在之前的 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 行左右。
10.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 产生一系列的子任务:
可以看到,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 状态的逻辑,然后进入睡眠状态。
当 target server 被指定时,Procedure 进入 REGION_TRANSITION_DISPATCH 状态,dispatch 了 region open 的请求,同时把 meta 表中 region 的状态改成了 OPENING,然后再次进入休眠状态。
最后,当 RegionServer 打开了这个 region 后,会发 RPC 通知 master,那么在通知过程中,这个 Procedure 再次被唤醒,开始执行 REGION_TRANSITION_FINISH 的逻辑,最后更新 meta 表,把这个 region 置为打开状态。
一路看下来,由于整个 region assign 的过程都是在 Procedure 中执行,整个过程 清晰明了,非常容易追述,也没有了 Zookeeper 一些 event 事件的干扰。
11.总结
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 Committer&HBase PMC