
1. 为什么 HBase 建表流程值得单独拎出来讲很多人第一次翻 HBase 源码都是从createTable这个入口进去的然后很快就被HMaster、AssignmentManager、ProcedureExecutor、MetaTableAccessor这一连串类名给劝退了。我当初也是这个状态点进去一层一层往下跳跳到最后自己都不知道从哪进来的了。后来我换了个思路不再顺着调用栈死磕而是先问自己一个问题一张表从客户端发出请求到真正能被读写中间到底经历了哪些必须发生的事把这几件事想清楚再回头看源码脉络一下就顺了。这篇内容就是围绕这个问题展开的。我会把 HBase 创建表的完整链路拆成几个阶段从客户端发起Admin.createTable()开始经过 RPC 到 Master再到 Master 内部的表描述校验、region 划分、状态机流转、Meta 表写入、region 上线最后回到客户端拿到结果。整个过程涉及的核心类、关键方法、状态转换、以及那些文档里不会写的坑我都会尽量讲透。适合谁看如果你已经写过 HBase 的读写代码知道Put、Get、Scan怎么用但对表是怎么被创建出来的这件事只有模糊印象那这篇就是给你准备的。如果你正在准备 HBase 相关的面试或者需要排查建表卡住、建表后 region 不上线这类问题这里的内容也能直接拿来用。我不会假设你读过 HBase 的论文但我会假设你至少知道 region 是什么、Meta 表是干什么的。先说结论性的认知HBase 建表本质上不是创建一张表而是在 Meta 表里登记一批 region 的归属关系并让这些 region 在 RegionServer 上真正上线。表本身在 HDFS 上只是一个目录真正让表活起来的是 Meta 表里的记录和 RegionServer 上的 region 实例。理解这一点后面所有的源码分析都是在验证这句话。2. 建表请求的入口与整体链路拆解2.1 客户端侧Admin 接口背后做了什么我们从最常见的写法开始Admin admin connection.getAdmin(); TableDescriptor desc TableDescriptorBuilder .newBuilder(TableName.valueOf(test_table)) .setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf)) .build(); admin.createTable(desc);这段代码看起来很简单但admin.createTable(desc)这一行背后做的事情并不少。Admin是一个接口实际实现类是HBaseAdmin。在HBaseAdmin.createTable(TableDescriptor desc)里它会先做一轮客户端侧的参数校验比如表名是否合法、列族是否为空、列族名是否以非法字符开头等。这些校验在客户端做一遍是为了尽早失败避免把明显不合法的请求发到 Master 再被拒绝浪费一次 RPC。校验通过之后HBaseAdmin会构造一个CreateTableRequest对象通过MasterRpcService把请求发给 Master。这里有个细节值得注意客户端并不是直接把TableDescriptor序列化发过去的而是先转成 protobuf 定义的CreateTableRequest。这个转换过程在ProtobufUtil.toCreateTableRequest()里完成它会把TableDescriptor里的列族信息、配置项、region 分割点等都编码进去。提示如果你在客户端看到TableExistsException那说明 Master 返回了表已存在的错误但如果你看到的是IllegalArgumentException那大概率是客户端侧校验就没过请求根本没发出去。排查问题时先分清是哪一侧抛的异常能省很多时间。还有一个容易被忽略的点createTable有多个重载版本。带splitKeys参数的版本允许你指定预分区不带的话就是默认一个 region。这两个版本在客户端侧的处理逻辑不同带splitKeys的会在请求里带上分割点数组Master 侧会根据这个数组来决定创建多少个 region。很多人建表时不指定预分区结果表建好后所有写入都打到一个 region 上后面再想 split 就很被动了。2.2 Master 侧从 RPC 到 Procedure 的转换请求到了 Master 之后入口是MasterRpcServices.createTable()。这个方法本身不干重活它主要做两件事一是把 protobuf 请求转回TableDescriptor二是调用HMaster.createTable()。HMaster.createTable()里有一个关键判断当前 Master 是否处于 active 状态。如果是 standby Master 收到请求会直接拒绝。这个判断在MasterRpcServices里通过checkInitialized()完成。然后请求会被交给ProcedureExecutor转换成一个CreateTableProcedure来执行。为什么 HBase 要用 Procedure 框架来做建表这是很多人第一次看源码时的疑问。原因在于建表是一个多步骤、可能失败、需要回滚的操作。它涉及创建表描述、写入 Meta 表、分配 region、上线 region 等多个步骤任何一步失败都需要清理已做的操作。如果用普通方法调用失败回滚的逻辑会散落在各处极难维护。Procedure 框架提供了状态持久化、失败重试、回滚机制正好适合这种场景。CreateTableProcedure继承自AbstractStateMachineTableProcedure它的状态机大致是这样的状态含义主要动作CREATE_TABLE_PRE_OPERATION操作前准备检查表是否存在、校验 descriptorCREATE_TABLE_WRITE_FS_LAYOUT写文件系统布局在 HDFS 上创建表目录CREATE_TABLE_ADD_TO_META写入 Meta 表在 Meta 表登记 region 信息CREATE_TABLE_ASSIGN_REGIONS分配 region触发 region 上线CREATE_TABLE_UPDATE_DESC_CACHE更新缓存刷新 Master 侧的 descriptor 缓存CREATE_TABLE_POST_OPERATION操作后收尾通知相关组件这个状态机的每一步都是可持久化的也就是说即使 Master 在中间挂掉重启后也能从上次的状态继续执行。这是 Procedure 框架相比普通方法调用的核心优势。2.3 为什么建表要走 Procedure 而不是直接同步执行我一开始也觉得建表这么简单的操作为什么要搞得这么复杂。后来在实际运维中遇到过一次 Master 在建表过程中重启的情况才理解这个设计的价值。当时表建到一半 Master 挂了重启后 Procedure 从持久化状态恢复继续把剩下的步骤走完表最终建成功了。如果当初是同步执行Master 一挂这个建表请求就丢了客户端只能超时重试而重试又可能遇到表已部分存在的尴尬状态。Procedure 框架的持久化是通过ProcedureStore实现的默认实现是MasterProcedureStore它把 Procedure 的状态写到 HDFS 上的一个独立目录里。每次状态转换都会先写日志再执行保证崩溃后能恢复。这个先写日志再执行的模式和数据库的 WAL 是一个思路。注意Procedure 的持久化目录默认在 HDFS 的/hbase/MasterProcWALs下。如果你发现这个目录堆积了大量文件可能是 Procedure 执行卡住或者清理逻辑出了问题需要重点关注。3. 表描述校验与 region 划分的核心细节3.1 TableDescriptor 里到底装了什么TableDescriptor是建表请求的核心数据结构它包含了这张表的全部元信息。很多人只把它当成表名加列族的容器实际上它里面的字段远比想象中多tableName表名必须是合法的TableName对象columnFamilies列族列表每个列族有自己的ColumnFamilyDescriptorconfiguration表级别的配置项会覆盖集群默认配置regionSplitPolicyClassNameregion 分裂策略maxFileSizeregion 最大文件大小readOnly是否只读durability持久化级别priority优先级flushPolicyClassNameflush 策略这些字段在TableDescriptorBuilder里都有对应的 setter。我见过不少团队在建表时只设了列族其他全用默认值结果后面发现 region 分裂策略不合适、或者表级别配置没法覆盖只能重建表。建表时多想一步比事后改表要省事得多因为 HBase 改表的限制比建表多得多。列族描述ColumnFamilyDescriptor里也有不少关键参数参数默认值作用maxVersions1保留的最大版本数minVersions0保留的最小版本数timeToLiveFOREVER数据过期时间blockSize64KBHFile 块大小compressionNONE压缩算法bloomFilterROW布隆过滤器类型inMemoryfalse是否常驻内存这些参数在建表时确定后后续修改需要 disable 表代价很大。所以建表前一定要根据业务场景想清楚比如写多读少的场景可以把blockSize调大读多写少的场景可以开启bloomFilter。3.2 预分区建表时最容易被忽视的关键决策预分区是建表流程里最值得单独拿出来讲的部分。如果你不指定splitKeysHBase 默认只创建一个 region所有数据都往这一个 region 写。在数据量小的时候没问题但一旦数据量上来这个 region 会成为写入瓶颈而且 split 是一个比较重的操作频繁 split 会影响集群稳定性。预分区的核心是选好分割点。分割点的选择取决于你的 rowkey 设计。假设你的 rowkey 是用户 ID且用户 ID 是均匀分布的那你可以用[1, 2, 3, 4, 5, 6, 7, 8, 9]这样的分割点把数据分成 10 个 region。但如果你的 rowkey 是时间戳前缀那分割点就要按时间范围来选。byte[][] splitKeys new byte[][] { Bytes.toBytes(1), Bytes.toBytes(2), Bytes.toBytes(3), Bytes.toBytes(4), Bytes.toBytes(5), Bytes.toBytes(6), Bytes.toBytes(7), Bytes.toBytes(8), Bytes.toBytes(9) }; admin.createTable(desc, splitKeys);这段代码会创建 10 个 region每个 region 负责一个区间的数据。分割点的数量决定了 region 的数量分割点越多region 越多并行度越高但管理开销也越大。一般来说单个 region 的大小控制在 10GB 到 50GB 之间比较合适你可以根据预估的数据总量来反推需要多少个 region。提示预分区的分割点必须是有序的而且不能重复。如果你传进去的分割点无序HBase 会抛IllegalArgumentException。这个校验在客户端侧就做了不会发到 Master。还有一个坑分割点的字节序问题。HBase 的 rowkey 是按字节序排序的不是按字符串字典序。如果你用Bytes.toBytes(10)和Bytes.toBytes(9)作为分割点实际排序是 10 在 9 前面因为字符 1 的字节值小于 9。这个细节在数字型 rowkey 上特别容易踩坑建议用固定长度的编码方式比如把数字补零到相同位数。3.3 表描述校验的完整清单Master 侧收到TableDescriptor后会做一轮比客户端更严格的校验。这些校验在CreateTableProcedure的prepareCreate()方法里完成主要包括表名合法性不能为空不能包含非法字符长度不能超过限制表是否已存在查 Meta 表和 Master 的 descriptor 缓存列族校验至少有一个列族列族名不能为空列族名不能重复列族参数校验maxVersions必须大于等于minVersionstimeToLive不能为负数分割点校验分割点必须有序且不重复命名空间校验如果指定了命名空间命名空间必须存在这些校验里表是否已存在是最容易出问题的一个。因为判断表是否存在需要查 Meta 表而 Meta 表本身也是一个 HBase 表查询它需要走 RPC。如果 Meta 表所在的 RegionServer 负载很高这个查询可能会超时导致建表请求失败。我遇到过几次建表报表已存在但实际表并不存在的情况最后发现是 Meta 查询超时后返回了错误的结果。注意如果你确认表不存在但建表一直报表已存在可以先检查 Meta 表是否健康用hbck工具看一下 Meta 表的状态。这种情况通常是 Meta 表有脏数据或者查询超时导致的。4. Meta 表写入与 region 上线的完整实操过程4.1 在 HDFS 上创建表目录校验通过后CreateTableProcedure进入CREATE_TABLE_WRITE_FS_LAYOUT状态这一步会在 HDFS 上创建表的目录结构。目录路径是/hbase/data/namespace/tableName/如果是默认命名空间路径就是/hbase/data/default/tableName/。在这个目录下每个 region 会有一个子目录命名格式是regionNameregionName 是一个经过编码的字符串包含了表名、起始 rowkey、regionId 等信息。这一步看起来简单但有个细节目录创建是幂等的。如果目录已经存在不会报错而是直接跳过。这是为了支持 Procedure 的重试——如果上一次执行到这一步失败了重试时目录可能已经创建了不能因为目录存在就失败。创建目录用的是 HBase 的MasterFileSystem类它封装了 HDFS 的操作。这里有个设计上的考虑Master 不直接操作 HDFS而是通过MasterFileSystem这个抽象层。这样做的好处是方便测试和替换底层存储实现。4.2 写入 Meta 表region 信息的登记CREATE_TABLE_ADD_TO_META是整个建表流程里最关键的一步。这一步会把每个 region 的信息写入 Meta 表具体来说是在 Meta 表的hbase:meta表里插入若干行记录每行对应一个 region。Meta 表的 rowkey 格式是tableName,startKey,regionId比如表test_table的第一个 region起始 key 为空regionId 是某个时间戳编码那 rowkey 就是test_table,,1234567890。这个 rowkey 的设计很巧妙按 tableName 前缀排序同一张表的所有 region 会连续排列这样扫描一张表的所有 region 信息时只需要一次范围扫描。写入 Meta 表用的是MetaTableAccessor.addRegionsToMeta()方法。这个方法会构造Put对象把 region 的详细信息包括 region 的 server 归属、起始 key、结束 key、region 状态等写进去。注意这时候 region 还没有被分配到具体的 RegionServer所以 server 字段是空的region 状态是OFFLINE。提示Meta 表的写入是批量进行的如果 region 数量很多比如预分区创建了几百个 region这一步可能会比较慢。我见过一次建表创建了 500 个 regionMeta 写入花了将近 10 秒。所以预分区不是越多越好要结合实际情况权衡。Meta 表写入完成后Master 会更新自己的 descriptor 缓存把新表的TableDescriptor加进去。这个缓存在TableStateManager里维护后续判断表是否存在、表的状态都会查这个缓存。4.3 region 上线从 OFFLINE 到 ONLINECREATE_TABLE_ASSIGN_REGIONS这一步会触发 region 的分配和上线。具体流程是Master 的AssignmentManager收到 region 分配请求AssignmentManager根据负载均衡策略选择一个 RegionServerMaster 向选中的 RegionServer 发送OpenRegion请求RegionServer 收到请求后打开 region读取 Meta 表信息初始化 region 实例RegionServer 向 Master 汇报 region 上线成功Master 更新 Meta 表把 region 的状态改为ONLINE并记录 server 归属这个过程是异步的Master 不会同步等待所有 region 上线。CreateTableProcedure在触发分配后会进入一个等待状态直到所有 region 都上线或者超时。这里有个关键点region 上线是并行的但 Meta 表更新是串行的。因为 Meta 表本身也是一个 HBase 表对它的写入需要保证顺序。如果 region 数量很多Meta 表的写入可能成为瓶颈。HBase 对此做了优化把多个 region 的上线信息批量写入 Meta 表减少 RPC 次数。我实际排查过一次建表后 region 长时间不上线的问题。最后发现是某个 RegionServer 的hlog目录满了导致 region 打开失败。Master 会重试分配但每次重试都选到同一个 RegionServer就一直卡在那里。后来清理了hlog目录region 才正常上线。所以建表后如果 region 一直不上线先检查 RegionServer 的日志和磁盘状态比盯着 Master 日志看更有效。4.4 客户端如何感知建表完成客户端调用admin.createTable()是一个同步调用它会一直阻塞直到 Master 返回结果。Master 返回结果的前提是CreateTableProcedure执行完成也就是所有 region 都上线成功。但这里有个细节客户端拿到的成功并不一定意味着所有 region 都已经 ONLINE。在某些版本里Master 会在 region 分配请求发出后就返回成功而不是等所有 region 都上线。这是为了减少客户端的等待时间。如果你建表后立刻写入数据可能会遇到RegionOfflineException因为 region 还没上线。提示如果你建表后需要立刻写入建议在createTable之后加一个短暂的等待或者用admin.isTableAvailable()轮询确认表可用。isTableAvailable会检查所有 region 是否都上线比直接写入更稳妥。5. 建表过程中的常见问题与排查技巧5.1 建表超时从哪几个方向排查建表超时是最常见的问题之一。客户端默认的 RPC 超时时间是 60 秒如果建表在 60 秒内没完成客户端会抛SocketTimeoutException。但这时候建表可能还在 Master 侧继续执行只是客户端不等了。排查建表超时我一般按这个顺序看Master 日志看CreateTableProcedure执行到哪一步卡住了Meta 表状态Meta 表是否健康查询是否超时RegionServer 状态是否有 RegionServer 挂了或者负载过高HDFS 状态HDFS 是否正常目录创建是否成功Procedure 状态用hbck或者 Master UI 看 Procedure 的执行状态有一次我遇到建表超时最后发现是 HDFS 的 NameNode 在做 checkpoint导致目录创建操作卡了将近一分钟。这种情况只能等或者调整 HDFS 的 checkpoint 周期。5.2 region 不上线常见原因速查表现象可能原因排查方法region 一直 OFFLINERegionServer 磁盘满检查 RegionServer 的磁盘使用率region 一直 OFFLINEhlog 目录异常检查 hlog 目录权限和空间region 一直 OFFLINERegionServer 进程挂了检查 RegionServer 进程状态region 反复上线又下线region 文件损坏检查 region 目录下的文件完整性region 分配超时负载均衡策略问题检查 AssignmentManager 日志region 分配超时Meta 表查询慢检查 Meta 表所在 RegionServer 负载这张表是我在实际运维中总结出来的覆盖了大部分 region 不上线的场景。其中磁盘满和hlog 异常是最常见的两个原因建议优先排查。5.3 建表后表结构不符合预期几个容易踩的坑建表时如果参数没设对表建好后可能不符合预期。我整理了几个常见的坑列族参数没设比如maxVersions默认是 1如果你需要保留多个版本建表时就要设成更大的值。建表后再改需要 disable 表代价很大。预分区没设默认一个 region写入压力全在一个 region 上。如果预估数据量大建表时就要设预分区。表级别配置没设比如regionSplitPolicy默认是IncreasingToUpperBoundRegionSplitPolicy如果你的 rowkey 是递增的这个策略会导致 region 分裂不均匀。可以考虑改成ConstantSizeRegionSplitPolicy或者自定义策略。命名空间没设默认在default命名空间下如果集群有命名空间规范建表时要指定。注意HBase 的alter操作比create限制多得多。比如修改列族的maxVersions需要先 disable 表修改表名基本不可能。所以建表时一定要把参数想清楚不要指望事后改。5.4 Procedure 卡住如何手动干预如果CreateTableProcedure卡在某个状态很久不动可以尝试手动干预。HBase 提供了hbck工具来查看和操作 Procedure# 查看所有 Procedure hbck -procedure list # 查看指定 Procedure 的详情 hbck -procedure describe procedureId # 尝试恢复卡住的 Procedure hbck -procedure recover procedureId但手动干预有风险在执行之前一定要先备份 Meta 表。我见过一次手动恢复 Procedure 导致 Meta 表数据不一致的情况最后只能通过hbck的修复功能来恢复过程非常痛苦。提示如果 Procedure 卡住是因为某个 RegionServer 挂了最安全的做法是先恢复 RegionServer让 Procedure 自己继续执行而不是手动干预。手动干预只适用于 RegionServer 无法恢复的情况。6. 从源码里能学到的设计思路6.1 状态机 持久化复杂操作的通用解法HBase 用 Procedure 框架处理建表本质上是用状态机 持久化来解决多步骤、可能失败、需要回滚的问题。这个思路在很多分布式系统里都能看到比如 Kafka 的 controller 用状态机管理分区 leader 选举Kubernetes 用 controller 循环管理资源状态。这个模式的核心是把一个大操作拆成若干个小状态每个状态转换前先持久化失败后从上次的状态恢复。这样做的好处是操作可以重试、可以恢复、可以回滚而且每一步的状态都是可观测的。缺点是实现复杂需要处理状态转换的各种边界情况。如果你在设计类似的系统这个模式值得借鉴。但要注意状态机的状态不能太多否则维护成本会很高。HBase 的CreateTableProcedure只有 6 个状态已经能覆盖建表的全部流程了。6.2 异步 批量提升吞吐的关键建表过程中region 上线是异步的Meta 表写入是批量的。这两个设计都是为了提升吞吐。如果 region 上线是同步的建一个 500 region 的表可能要等很久如果 Meta 表写入是逐条的RPC 次数会非常多。异步的代价是状态管理变复杂。Master 需要跟踪每个 region 的上线状态处理超时和失败。HBase 用AssignmentManager来管理这些状态它内部维护了一个 region 状态机跟踪每个 region 从 OFFLINE 到 ONLINE 的转换。批量的代价是失败处理变复杂。如果批量写入 Meta 表时部分成功部分失败需要处理这种不一致状态。HBase 的做法是先写 Meta 表再分配 region如果 Meta 写入失败整个建表操作回滚如果 region 分配失败Meta 表里的记录还在可以通过重试来恢复。6.3 幂等设计让重试变得安全建表流程里的很多操作都是幂等的比如创建 HDFS 目录、写入 Meta 表记录。幂等的意思是同一个操作执行多次和执行一次的效果相同。这个特性让重试变得安全不需要担心重复执行导致数据不一致。实现幂等的方式有几种一是先检查再执行比如创建目录前先判断目录是否存在二是用唯一标识去重比如 Meta 表写入时用 regionName 作为唯一键重复写入会覆盖而不是新增三是用版本号控制比如 Meta 表记录带版本号只有版本号匹配才更新。提示如果你在设计分布式操作幂等是一个必须考虑的特性。没有幂等重试就可能出问题有了幂等重试就是安全的。这是分布式系统设计的一条基本原则。7. 几个我踩过的坑和对应的解法7.1 预分区分割点选错导致数据倾斜我曾经建过一张表rowkey 是用户 ID我按[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]做了预分区想着用户 ID 首位是 0-9 应该均匀分布。结果实际数据里 80% 的用户 ID 首位是 1导致第一个 region 承担了绝大部分写入其他 region 基本空闲。后来我改成按用户 ID 的哈希值做预分区把哈希值的前几位作为分割点数据就均匀了。预分区的核心是让数据均匀分布而不是简单地按某个字段切分。如果你的 rowkey 分布不均匀可以考虑用哈希或者加盐的方式重新设计 rowkey。7.2 建表时没设 TTL 导致数据无限增长有一次建了一张日志表忘了设 TTL结果数据一直增长几个月后占了几个 TB 的空间。后来只能 disable 表改 TTL再 enable整个过程花了几个小时期间表不可用。TTL 是建表时就应该想清楚的参数。如果你的数据有时效性建表时就要设 TTL。TTL 的单位是秒比如设成86400就是保留一天。TTL 过期后数据会在 compaction 时被清理不会立刻删除所以实际保留时间会比 TTL 稍长。7.3 region 数量过多导致 Master 压力大我见过一个集群有人建表时预分区设了 1000 个 region结果 Master 管理这些 region 的状态就占用了大量内存和 CPU整个集群的响应都变慢了。后来把表删了重建预分区改成 100 个才恢复正常。region 数量不是越多越好。每个 region 都需要 Master 和 RegionServer 维护状态region 越多管理开销越大。一般来说单个 RegionServer 上的 region 数量控制在 100 到 200 之间比较合适。如果数据量确实很大可以通过增加 RegionServer 来分摊而不是无限增加 region 数量。7.4 建表后立刻写入遇到 RegionOfflineException前面提到过建表返回成功不代表所有 region 都上线了。我有一次建表后立刻写入就遇到了RegionOfflineException。后来改成建表后用admin.isTableAvailable()轮询确认表可用后再写入就没再出过这个问题。admin.createTable(desc, splitKeys); while (!admin.isTableAvailable(tableName)) { Thread.sleep(1000); } // 现在可以安全写入了这段代码虽然简单但能避免很多建表后立刻写入的问题。isTableAvailable会检查所有 region 是否都上线返回 true 才表示表真正可用。8. 建表流程的扩展思考8.1 从建表看 HBase 的整体架构建表流程其实串联了 HBase 的很多核心组件客户端、Master、RegionServer、Meta 表、HDFS、Procedure 框架。理解建表流程等于把 HBase 的主要组件都过了一遍。比如 Meta 表它在建表流程里扮演了登记处的角色。所有 region 的信息都要写到 Meta 表里客户端读写数据时也要先查 Meta 表找到 region 的位置。Meta 表本身也是一个 HBase 表它的 region 由 Master 管理。这种自举的设计是 HBase 架构的一个特点。再比如 Procedure 框架它不只用于建表还用于删表、改表、split、merge 等操作。理解 Procedure 框架等于理解了 HBase 处理复杂操作的通用模式。8.2 建表流程的优化空间从源码角度看建表流程有几个可以优化的点Meta 表写入可以进一步批量化目前是按 region 批量写入如果 region 数量特别多可以考虑分批次并行写入。region 上线可以更智能目前是 Master 选择 RegionServer可以考虑让 RegionServer 主动拉取 region减少 Master 的负担。建表结果可以更早返回目前是等所有 region 上线才返回可以考虑在 Meta 表写入完成后就返回让 region 上线在后台进行。这些优化在社区里都有讨论但考虑到兼容性和稳定性实际落地的并不多。如果你在做 HBase 的二次开发这些方向可以参考。8.3 建表流程对使用者的实际意义说了这么多源码最后回到实际使用。理解建表流程对日常使用 HBase 有几个实际帮助第一建表时知道该设哪些参数。知道了TableDescriptor里有哪些字段建表时就不会漏设关键参数。第二建表出问题时知道从哪排查。知道了建表的几个阶段出问题时就能定位到具体阶段而不是盲目看日志。第三知道建表的性能瓶颈在哪。知道了 Meta 表写入和 region 上线是耗时操作建表时就能合理预期不会因为建表慢而误判为故障。第四知道建表的限制在哪。知道了建表后改表代价大建表时就会更谨慎把参数想清楚。我个人在实际操作中的体会是建表是 HBase 使用中最重要的一步没有之一。表建得好后面读写都顺表建得不好后面各种问题。花时间理解建表流程比花时间处理建表遗留问题要划算得多。