HBase:创建多个 table 或多个列的单个 table?

HBase: Create multiple tables or single table with many columns?

何时创建多个 table 相对于具有大量列的单个 table 有意义。据我所知,通常 tables 只有几个列族 (1-2),并且每个列族可以支持 1000 多个列。

当 HBase 似乎在单个 table 中具有潜在大量列时表现良好时,何时创建单独的 table 有意义?

在回答问题本身之前,让我首先说明一些发挥作用的主要因素。我将假设正在使用的文件系统是 HDFS。

  1. A table 被划分为称为区域的键空间的非重叠分区。

  2. key-range -> region 映射存储在一个名为 meta 的特殊单一区域 table 中。

  3. 一个区域的一个HBase列族中的数据存储在一个HDFS目录中。它通常是几个文件,但出于所有意图和目的,我们可以假设列族的区域数据存储在 HDFS 上称为 StoreFile / HFile 的单个文件中。

  4. StoreFile 本质上是一个包含 KeyValue 的排序文件。 KeyValue 在逻辑上按顺序表示以下内容:(RowLength、RowKey、FamilyLength、FamilyName、Qualifier、Timestamp、Type)。例如,如果您的区域中只有两个 KV 用于 CF,其中键相同但值在两列中,这就是 StoreFile 的样子(除了它实际上是字节编码的,并且像长度等元数据也是按照我上面提到的方式存储):

    Key1:Family1:Qualifier1:Timestamp1:Value1:Put
    
    Key1:Family1:Qualifier2:Timestamp2:Value2:Put
    
  5. StoreFile分为(默认64KB),每个数据块包含的key范围由多级索引索引。可以使用索引+二进制搜索来完成单个块内的随机查找。但是,在扫描所需的第一个块中定位起始位置后,必须连续扫描特定块。

  6. HBase 是一个基于 LSM 树的数据库,这意味着它有一个内存日志(称为 Memstore),它会定期刷新到文件系统创建存储文件。 Memstore 为特定列系列的单个区域内的所有列共享。

在处理 reading/writing 数据 from/to HBase 时涉及多项优化,但上面给出的信息在概念上是正确的。鉴于上述陈述,以下是具有多个列与多个 tables 相对于其他方法的优点:

单Table多列

  1. 由于前缀编码,更好的磁盘压缩,因为一个键的所有数据都存储在一起,而不是跨 table 秒存储在多个文件中。由于数据量较小,这也会导致磁盘减少 activity。
  2. 元table 上的负载较小,因为区域总数将会变小。您将只有一个 table 有 N 个区域,而不是 M table 有 N*M 个区域。这意味着更快的区域查找和元 table 上的低争用,这是大型集群的一个问题。
  3. 当您需要为单个行键读取多个列时,读取速度更快,IO 放大率更低(导致磁盘更少activity)。
  4. 在为单个行键写入多个列时,您可以利用行级事务、批处理和其他性能优化。

什么时候使用这个:

  1. 如果您想跨多个列执行行级事务,您必须将它们放在一个 table.
  2. 即使您不需要行级事务,但您经常写入或查询多个列以获取相同的行键。一个好的经验法则是,如果平均而言,超过 20% 的列具有单行值,您应该尝试将它们放在一个 table.
  3. 当你的列太多时。

多个Tables

  1. 每个 table 的扫描速度更快,如果扫描主要只关注一列,则 IO 放大率较低(请记住,扫描中的顺序查找将不必要地读取它们不需要的列)。
  2. 良好的数据逻辑分离,尤其是当您不需要跨列共享行键时。一种类型的行键有一个 table。

何时使用:

  1. 当数据有明确的逻辑分离时。例如,如果您的行键架构在不同的列组中不同,请将这些列组放在单独的 table 中。
  2. 当只有一小部分列具有行键的值时(查看下面的更好方法)。
  3. 您想为不同的列集设置不同的存储配置。例如。 TTL、压缩率、阻塞文件数、memstore 大小等(在此用例中查看下面的更好方法)。

另一种选择:单个 CF 中的多个 table

从上面可以看出,这两种方法各有利弊。如果您对多个列具有相同的行键结构(因此,您希望共享行键以提高存储效率或需要跨列事务)但数据非常稀疏(这意味着您 write/read 只有一小部分列用于行键)。 在这种情况下,您似乎需要两全其美。这就是列族的用武之地。如果您可以将列集划分为逻辑子集,其中您大部分 access/read/write 只划分为一个子集,或者您需要每个子集的存储级别配置(如 TTL、存储 class、写重压缩计划等),然后你可以让每个子集成为一个列族。 由于特定列族的数据存储在单个文件(文件集)中,因此您在读取列的子集时可以获得更好的局部性,而不会减慢扫描速度。

但是,有一个问题:

不要尝试不必要地使用列族。它们会产生相关成本,并且由于区域级写锁、监控等在 HBase 中的工作方式,HBase 不能很好地处理 10+ CF。仅当跨 CF 的列之间具有逻辑关系但通常不跨 CF 执行操作或需要为不同的 CF 具有不同的存储配置时才使用 CF。 如果你在它们之间共享行键模式,那么只使用一个包含所有列的 CF 是完全没问题的,除非你有一个非常稀疏的数据集,在这种情况下你可能需要不同的 CFs 或不同的 tables 基于上面提到的要点。