设计您的架构

本页面介绍了如何设计 Cloud Bigtable 表的架构。 在阅读本页内容之前,您应先熟悉 Cloud Bigtable 概览

一般概念

Cloud Bigtable 架构设计与关系数据库架构设计差异很大。在设计 Cloud Bigtable 架构时,请牢记以下概念:

  • 每个表只有一个索引(即行键)。没有二级索引。
  • 行按行键的字典顺序排列,即从最低字节字符串到最高字节字符串。行键以 big-endian 字节顺序(有时称为网络字节顺序)排序,这是字母顺序的二进制版本。这意味着整数不会自动按数字顺序排序。例如,3 大于 20。如果要存储整数并按数字顺序对其进行排序,请对它们进行填充,以便按数字顺序进行排序:20 大于 03。 在需要基于范围的查询时,时间戳尤其如此。
  • 列按列族分组,并在列族中按字典顺序排序
  • 所有行级操作都属于原子操作。例如,如果您对表中的两个行进行更新,那么有可能其中一行成功更新,而另一行的更新失败。因此,请遵循以下准则:
    • 在设计架构时,请避免跨行原子性要求。
    • 一般情况下,将一个实体的所有信息保存在一行中。 但是,如果您的用例不要求您对实体进行原子更新或读取,则可以将实体拆分为多行。对于数百 MB 的大实体数据,建议将数据拆分到多个行中。
  • 理想情况下,读取和写入操作都应均匀分布于表行空间中。
  • 相关实体应存储在相邻行中,这可以提高读取效率。
  • Cloud Bigtable 表属于稀疏表。空列不会占用任何空间。因此,创建大量列通常是一个合理选择,即使大多数行中的大多数列都是空的。
  • 最好使用少数几个大型表而不要使用许多小型表。 Cloud Bigtable 以其在处理大型表方面的出色性能而闻名。您可能有充分的理由使用单独的表来处理需要完全不同架构的不同用例,但不应对类似的数据使用不同的表。例如,您不应只是为了新的一年或新的客户而创建新表。

大小限制

Cloud Bigtable 会对您在表中存储的数据施加大小限制。在设计架构时,请务必考虑这些限制。

由于 Cloud Bigtable 以原子方式读取行,因此限制一行中存储的总数据量尤其重要。按照最佳做法,单个单元中最多可存储 10 MB,单行中最多可存储 100 MB。您还必须确保不超出单元格和行内数据大小的硬性限额

以上大小限制是以二进制兆字节 (MB) 为单位来计量,其中 1 MB 等于 220 个字节。此计量单位也称为兆比字节 (MiB)

选择行键

为了使 Cloud Bigtable 达到最佳性能,请仔细考虑如何编写您的行键,这一点至关重要。这是因为最有效的 Cloud Bigtable 查询会使用行键、行前缀或行范围来检索数据。其他类型的查询会触发全表扫描,使效率显著降低。现在就选择正确的行键,可以避免日后进行麻烦的数据迁移。

首先问问自己将如何使用计划存储的数据。例如:

  • 用户信息:您是否需要快速访问用户间关联情况的信息(例如,用户 A 是否关注用户 B)?
  • 用户生成的内容:如果您要向用户显示包含大量用户生成内容(如状态更新)的示例,您将如何决定向给定用户显示哪些状态更新?
  • 时间序列数据:您是否经常需要检索最近的 N 条记录或者某一特定时间范围的记录?如果您存储的是多种事件的数据,您是否需要根据事件类型进行过滤?

通过预先了解自己的需求,您可以确保所设计的行键和整体架构能够非常灵活而高效地完成数据查询。

行键类型

本部分介绍了一些最常用的行键类型,并说明了何时使用每种键类型。

一般来说,行键应该是比较简短的。冗长的行键不但会占用额外的内存和存储空间,而且还会增加 Cloud Bigtable 服务器的响应时间。

反向域名

如果您要存储的实体相关数据可以用域名来表示,请考虑使用反向域名(例如 com.company.product)作为行键。如果每行的数据很可能与相邻行重叠,使用反向域名就特别理想。在这种情况下,Cloud Bigtable 可以更有效地压缩您的数据。

当您的数据分布在许多不同的倒序域名中时,此方法效果最佳。如果您预期会在少量的倒序域名中存储大部分的数据,请考虑使用其他值作为行键。否则,大部分写入内容可能会被推送到集群中的单个节点,从而导致片过载。

字符串标识符

如果要存储的实体相关数据可用简单字符串来标识(例如,用户 ID),您可能最好使用该字符串标识符作为行键,或者将其用作行键的一部分。

过去,此页面推荐使用字符串标识符的哈希值,而不是实际的字符串标识符。我们已不再推荐使用哈希值。我们发现使用经过哈希处理的行键很难排查 Cloud Bigtable 问题,因为经过哈希处理的行键毫无实际意义。例如,如果您的行键是用户 ID 的哈希值,那么您很难或无法找出哪个用户 ID 与此行键相关联。

使用简明易懂的值,而不是对行键进行哈希处理。另外,如果您的行键包含多个值,请使用分隔符将这些值分开。根据这些做法,使用 Key Visualizer 工具可以更轻松地解决 Cloud Bigtable 问题。

时间戳

如果您经常需要根据数据的记录时间来检索数据,则最好将时间戳包含在行键中。建议您不要将时间戳独立用作行键,因为大多数写入内容都会被推送到单个节点上。出于同样的原因,请避免将时间戳置于行键的开头位置。

例如,您的应用可能需要每秒记录一次大量机器的性能相关数据(如 CPU 和内存使用率)。 对于这种数据,您可以组合使用机器的标识符与数据的时间戳作为行键(例如 machine_4223421#1425330757685)。

如果您通常会先检索最新记录,则可以在行键中使用反向时间戳,方法是从所用编程语言的最大长整数值(在 Java 中为 java.lang.Long.MAX_VALUE)中减去时间戳。使用反向时间戳后,记录将按照从最新到最旧的顺序进行排序。

在单一行键中使用多个值

高效查询 Cloud Bigtable 的唯一方法是使用行键,因此在行键中包含多个标识符通常会很有用。如果您的行键包含多个值,清楚自己将如何使用数据尤为重要。

例如,假设您的应用允许用户发布消息,并允许用户在帖子中相互提及。您需要通过一种有效的方式列出在帖子中标记了特定用户的所有用户。实现此目标的一种方法是使用如下形式的行键:即依次包含所标记的用户名和执行此标记操作的用户名,并用分隔符分隔这两个名称(例如 wmckinley#gwashington)。若要找出谁标记了特定用户名,或显示标记该用户名的所有帖子,您只需检索行键以此用户名开头的一系列行。

创建行键时,请确保该行键仍然可以检索一个明确定义的范围内的所有行。否则,您的查询将需要执行表扫描,而此操作要比检索特定行的速度慢得多。例如,假设您每秒存储一次与性能相关数据。如果您的行键包含时间戳并后跟机器标识符(例如 1425330757685#machine_4223421),您将无法有效地将查询限制到特定机器,而是只能根据时间戳来限制查询。

行键前缀

多值行键中的第一个值称为行键前缀。 有了精心设计的行键前缀,您将可以利用 Cloud Bigtable 的排序顺序将相关数据存储在一系列连续行中。 如此一来,您便可通过一定范围的行来访问相关数据,而无需运行低效的表扫描。

对多租户使用行键前缀

行键前缀为“多租户”使用场景提供了可扩缩的解决方案,让您可以代表多位客户使用相同的数据模型来存储类似的数据。对所有租户使用同一个表是存储和访问多租户数据的最有效方式

例如,假设您代表多个公司存储和跟踪购买记录。您可以使用每家公司的专属 ID 作为行键前缀。 一个租户的所有数据都会存储在同一个表的连续行中,方便您使用行键前缀进行查询或过滤。此后,如果某家公司不再是您的客户,并且您需要删除为该公司存储的购买记录数据,您可以删除使用该客户的行键前缀的一系列行

相比之下,如果将代表每家公司的数据存储在其各自的表中,那么您将有可能遇到性能和可扩缩性问题,也更有可能会无意中达到 Cloud Bigtable 的限制(每个实例 1000 个表)。当某个实例达到此限制后,Cloud Bigtable 会阻止您在该实例中创建更多表。

需避免使用的行键

某些类型的行键可能会使数据查询变得困难或导致性能不良。本部分介绍了在 Cloud Bigtable 中应避免使用的一些行键类型。

域名

避免使用标准的非反向域名作为行键。使用标准域名时,您将无法高效地检索部分域名中的所有行(例如,与 company.com 相关的所有行将属于多个不同的行范围,如 services.company.comproduct.company.com 等等)。另外,使用标准域名会使系统在排序行时无法将相关数据分组在一起,从而导致压缩效率低下。

顺序数字 ID

假设您的系统为应用的每个用户都分配了一个数字 ID。 您可能想要使用用户的数字 ID 作为表的行键。 但是,由于新用户更可能成为活跃用户,因此这种方法可能会将大部分流量推送到少数节点。

一种更安全的方法是使用用户数字 ID 的反向版本,这样可以将流量更均匀地分布到 Cloud Bigtable 表的所有节点中。

频繁更新的标识符

避免使用单个行键来标识更新极为频繁的值。例如,如果您每秒存储一次内存使用情况数据,请不要使用名为 memusage 的单一行键并重复更新该行。这类操作会使存储了常用行的片发生过载, 也可能导致行超过其大小限制,因为单元格的先前值会占用空间一段时间。

应在每行存储一个值,并使用包含指标类型、分隔符和时间戳的行键。例如,如需跟踪一段时间内的内存使用率,您可以使用类似于 memusage#1423523569918 这样的行键。这种策略非常有效,因为在 Cloud Bigtable 中,创建新行不会比创建新单元格花费更多的时间。此外,该策略可以计算适当的开始键和结束键,可让您快速读取特定日期范围内的数据。

对于变化非常频繁的值(例如每分钟更新数百次的计数器),最好只将数据保留在应用层的内存中,并定期向 Cloud Bigtable 写入新行。

哈希值

如前文所述,本页面的早期版本推荐在行键中使用哈希值。我们已不再推荐这种做法。这一方法生成的行键本质上没有任何意义,使得通过 Key Visualizer 工具来排查 Cloud Bigtable 问题变得非常困难。

使用简明易懂的值,而不是经过哈希处理的行键。如果您的行键包含多个值,请使用分隔符将这些值分开。

列族和列限定符

本部分介绍了如何考虑表中的列族和列限定符。

列族

与 HBase 不同,在 Cloud Bigtable 中,您可以使用多达约 100 个列族,并同时保持出色的性能。因此,如果一行中包含多个彼此相关的值,那么最好将这些值分组到同一列族中。通过按列族分组数据,您可以从单个族或多个族中检索数据,而不是检索每一行中的所有数据。尽可能将数据按您的确切需要进行分组,不要包含其它数据,通过您最常用的 API 调用读取。

此外,列族的名称应简短一些,因为名称包含在为每个请求传输的数据中。

列限定符

由于 Cloud Bigtable 表属于稀疏表,因此您可以在每行中创建所需数量的列限定符。行中的空单元格不会占用任何空间。因此,将列限定符视为数据通常是一个合理选择。例如,如果您的表存储的是用户帖子,则可以将每个帖子的唯一标识符用作列限定符。

尽管如此,您应该避免将数据拆分到过多不必要的列限定符,造成行中有大量的非空单元格。 Cloud Bigtable 需要一些时间来处理一行中的每个单元格。此外,每个单元格也会对存储在表中并通过网络发送的数据量增加一些开销。例如,如果您要存储 1 KB(1024 字节)的数据,那么将这些数据存储在一个单元格中,要比将数据分布在 1024 个均包含一个字节的单元格中更具空间效率。如果您经常一次读取或写入若干个相关值,请考虑将所有这些值存储在一个单元格中,而使用的格式要允许您日后提取个别值(例如协议缓冲区二进制格式)。

同样,最好使用简短的列限定符名称,这有助于减少每个请求传输的数据量。

表设计

大型表

在 Cloud Bigtable 中,每个实例的表数不能超过 1000 个。在大多数情况下,需要使用的表数应该远远低于这一限制。在其他数据库系统中,您可能会根据主题和列数选择将数据存储在多个表中。不过,在 Cloud Bigtable 中,您最好将所有数据存储在一个大型表中。您需要为每个数据集指定一个独一无二的行键前缀,让 Cloud Bigtable 能够将相关数据存储在一定连续范围的行中,从而方便您按行键前缀查询这些行。

在 Cloud Bigtable 中,创建许多小型表属于一项反模式操作,原因如下:

  • 向许多不同表发送请求会增加后端连接开销,导致尾延迟增加。

  • 拥有多个不同大小的表可能会破坏后台负载平衡,使得 Cloud Bigtable 无法提供出色的性能。

后续步骤