本文主要介绍schema设计原则。

前提条件

使用HBase Shell或者Java API的HBaseAdmin来创建和编辑HBase的Schema,当修改列簇时,建议先将这张表下线。
Configuration config = HBaseConfiguration.create();
HBaseAdmin admin = new HBaseAdmin(config);
String table = "Test";
admin.disableTable(table);        // 将表下线
HColumnDescriptor f1 = ...;
admin.addColumn(table, f1);      // 增加新的列簇
HColumnDescriptor f2 = ...;
admin.modifyColumn(table, f2);   // 修改列簇
HColumnDescriptor f3 = ...;
admin.modifyColumn(table, f3);   // 修改列簇
admin.enableTable(table);

更新

当表或者列簇改变时(包括:编码方式、压力格式、block大小等等),都将会在下次major compaction时或者StoreFile重写时生效。

表模式设计经验

  • 地域最大的阈值取值建议在8GB到50GB之间,不宜过小或过大。
  • 单个cell不超过10MB,如果超过10MB,请使用mob,若再大可以直接存在HDFS中,在HBase内存储HDFS地址。
  • 列簇数量不建议过多,一般1个即可,不建议超过3个。
  • 列簇名应尽量简短,因为存储时每个value都包含列簇名(忽略前缀编码,prefix encoding)。
  • 对于时序场景,建议rowkey设计为设备ID加上时间,如果采用“时间+设备ID”的方案会导致如下:
    • 同一时间点的数据落入同一个地域,导致热点。
    • 较早数据随着时间推移、数据过期会留下大量的空地域,带来不必要的开销。

列簇数量

  • 现在HBase并不能很好的处理两个或者三个以上的列簇,所以尽量让列簇数量少一些。
  • 目前, flush和compaction操作是针对一个地域。所以当一个列簇操作大量数据的时候会引发一个flush。邻近的列簇也有进行flush操作,尽管它们没有操作多少数据。
  • compaction操作现在是根据一个列簇下的全部文件的数量触发的,而不是根据文件大小触发的。
  • 当很多的列簇在flush和compaction时,会造成很多没用的I/O负载。
    说明 减少没用的I/O负载需要将flush和compaction操作只针对一个列簇。
  • 尽量在模式中只针对一个列簇操作。将使用率相近的列归为一列簇,这样每次访问时就只用访问一个列簇,提高效率。

列簇基数

如果一个表存在多个列簇,要注意列簇之间基数(如行数)相差不要太大。例如:列簇A有100万行,列簇B有10亿行,按照行键切分后,列簇A可能被分散到很多地域(及RegionServer),这导致扫描列簇A十分低效。

版本数量

行的版本的数量是HColumnDescriptor设置的,每个列簇可以单独设置,默认是3。这个设置是很重要的,因为HBase不会覆盖一个值,只会在值的后面进行追加描述,用时间戳来区分。过早的版本会在执行major compaction时删除,这些在HBase数据模型有描述。这个版本的值可以根据具体的应用增加或减少。不推荐将版本最大值设到一个很高的水平(100或更多),除非历史数据很重要,因为这会导致存储文件变得极大。

最小版本数

和行的最大版本数一样,最小版本数也是通过HColumnDescriptor在每个列簇中设置的。最小版本数缺省值是0,表示该特性禁用。 最小版本数参数和存活时间一起使用,允许配置如“保存最后T秒有价值数据,最多N个版本,但最少约M个版本”(M是最小版本数,M<N)。该参数仅在存活时间对列簇启用,且必须小于行版本数。

支持数据类型

HBase通过Put和Result支持bytes-in/bytes-out接口,所以任何可被转为字节数组的东西可以作为值存入。输入可以是字符串、数字、复杂对象、甚至图像,它们能转为字节。

存在值的实际长度限制,例如:保存10-50MB对象到HBase对查询来说太长,搜索邮件列表获取本话题的对话。HBase的所有行都遵循HBase数据模型包括版本化。设计时需考虑到以上限制以及列簇的块大小。

存活时间

列簇可以设置TTL秒数,HBase在超时后将自动删除数据,HBase里面TTL时间时区是UTC。

存储文件仅包含有过期的行(expired rows),它们可通过minor compaction删除。将hbase.store.delete.expired.storefile设置为false,可禁用此功能;将最小版本数设置成非0值也可达到同样的效果。

HBase的最新版本还支持将设定的时间存放在每个结构单元。TTL单元通过Mutation#setTTL作为更变请求(Appends, Increments, Puts, etc.)的一个属性提交,如果TTL的属性被设定了,它将会应用到由于该变更操作更新的所有单元上。Cell TTL handling和ColumnFamily TTLs间有两个显著的差别:
  • Cell TTLs的数量级是毫秒而不是秒。
  • 一个Cell TTL不能超出ColumnFamily TTLs设置的有效时间。