在 HBase 上使用 Big SQL 实现快速单点查询

pjjc3716 9年前

来自: http://www.ibm.com/developerworks/cn/analytics/blog/using-big-sql-hbase-fast-point-queries/index.html?ca=drs-

随着一些组织开始探索将其数据转移到分布式文件系统中的可能性,他们被如今丰富多样的工具惊呆了。挑选出与其访问模式匹配的正确工具可能很难。如果过去使用 SQL 和 JDBC 作为主要客户端接口,现在想继续使用它们,该怎么办?好消息是有许多解决方案在 Hadoop 数据上都提供了 SQL 功能。Big SQL 由于能够支持最广泛的 SQL 并具有高性能和可伸缩性,很快就倍受青睐。现在,如果您的数据访问需要快速的亚秒级响应时间,该怎么办?Big SQL 可以满足这一需求。借助 HBase 上对 SQL 的支持,您可以在其他文件格式上获得所有可用特性,并获得对小型查询的快速响应时间的额外优势。我们计划通过一系列文章介绍 HBase 的一些特性,并解释 Big SQL 如何利用这些特性。

如果打算挑选一个特性,使用行键快速查找的能力使得 HBase 成为需要亚秒级响应时间的查询的不错的候选方案。无论您的 HBase 表有多大,您都可以期望在数毫秒内返回所需的行,只要您知道整个行键的范围以及与较小的行集合匹配的某个键的范围。在介绍 SQL 方面的功能之前,让我们看看如何将数据存储在 HBase 表中。作为一个列式数据库,HBase 基本上是一种键值存储。这意味着一个 HBase 表中的每个值都由一个键唯一地表示,这个键由行键、列限定符、列族和时间戳组成。

这是 HBase 中的数据的示例。

rowkey_columnfamily_columnqualifier_timestamp_value   row1_cf1_cq1_ts1_val#1111   row1_cf1_cq1_ts2_val#1112   row1_cf1_cq2_ts1_val#1121   row2_cf1_cq3_ts1_val#2131   row2_cf2_cq4_ts1_val#2231

存储的数据按行键进行排序和建立索引,整个键与每个值一起持久保存在磁盘上。在想要检索某个较小的行集合时,可以了解此特性有何帮助。另一方面,这使得 HBase 成为了一种非常冗长的数据存储。您在存储和读取数据时将会花费一些成本。

让我们来了解此特性如何使您不再厌恶 HBase。Big SQL 可以帮助您减轻我上面提到的问题。为此,它提供一个列映射子句来将一个表中的 SQL 列映射到 HBase 实体。用户可以在他们的表中拥有想要的任意多个列。诀窍在于在相应的 HBase 表中不要有许多列。请记住,每个列都有大量有效负载。

探索不同的映射可能性之前,我们在这里提供了一个示例 Big SQL DDL,用于创建一个存储在 HBase 中的包含数据的表。这将在 HBase 中创建一个名为 schemaname.tablename 表。在这个示例中,表名称将为 bigsql.mixed_encodings。

CREATE HBASE TABLE mixed_encodings (c1 int,c2 int,c3 int,c4 varchar(20),c5 varchar(40),c6 int)   COLUMN MAPPING   (   KEY MAPPED BY (c1,c2,c3),   cf1:cq1 MAPPED BY (c4,c5) ENCODING DELIMITED FIELDS TERMINATED BY '#',   cf1:cq2 MAPPED BY (c6) ENCODING USING SERDE 'com.ibm.biginsights.bigsql.serde.JSONSerDeForNonUTFStrings'   );

这个示例表有 6 个 SQL 列:前 3 列映射到 HBase 行键;接下来的两列映射到一个 HBase 列 cf1:cq1;最后一列映射到 cf1:cq2。您还可以注意到一条 ENCODING 子句,它指明了如何在将数据存储到 HBase 中之前对它们进行编码。如果您不在意,可以将此工作留给 Big SQL,它将选择最佳的编码,这恰好是它内置的二进制编码。使用二进制编码,您会最大限度地实现下推和数字排序。DELIMITED 编码实际上是一种字符串编码,它使得 HBase 中的数据变得可读,而在 USING SERDE 编码中,用户可以指定一个 SerDe 类来编码/解码数据。我提到过 HBase 不关心数据类型或您存储数据的方式吗?对它而言,所有数据都是原始字节。这为应用程序提供了存储它们想要的任何数据并以它们喜欢的任何方式来解释它的灵活性。这是 Big SQL 利用的特性之一。它允许您将多个 SQL 列打包到一个 HBase 实体中,该实体可能是行键或列。它还允许您为 HBase 选择短名称,以便您不会向列的有效负载中添加太多内容,其中已包含列限定符和列族名称。

提示:保持列族和限定符简短!使用您的命名技能来为这些 SQL 列选择一些漂亮的名称。

这是展示了在使用以下插入语句时,HBase 中的数据的外观。

点击查看代码清单

关闭 [x]

INSERT INTO mixed_encodings values (1,2,3,'four','five','six');  hbase(main):001:0> scan 'bigsql.mixed_encodings'  ROW COLUMN+CELL   \x00\x80\x00\x00\x01\x00\x80\x00\x00\x02\x00\x80\ column=cf1:cq1, timestamp=1447974104744, value=four#five   x00\x00\x03   \x00\x80\x00\x00\x01\x00\x80\x00\x00\x02\x00\x80\ column=cf1:cq2, timestamp=1447974104744, value={"c6":6}   x00\x00\x03  1 row(s) in 0.1870 seconds

当包装列时,需要做出一些选择,要求您掌握数据访问工作负载的一些知识。这很可能是您在选择 HBase 作为数据存储之前已学过的知识。如果需要使用一组谓词来选择小范围的数据,您很可能已经选择将数据存储在 HBase 中。仔细观察您的谓词。选择大多数查询中使用的或最常使用的查询中的谓词。将这些谓词映射到行键。Big SQL 支持将多个 SQL 列映射到行键,可以将查询谓词下推到列上来形成合成的行键。在示例中,这些谓词是 c1、c2 和 c3,其中 c1 是最常用和最受限的谓词。现在,如果一个查询包含谓词 c1、(c1,c2) 或 (c1, c2, c3),那么您非常幸运。

上面提到的谓词下推 ( predicate pushdown) 会让工作在离数据更近的 HBase 区域服务器中执行。作为一种客户端/服务器数据库,HBase 客户端需要向区域服务器发送请求来读/写数据。在某些情况下,可以绕过此限制,这将在以后的文章中介绍。就现在而言,可以将谓词下推视为一种避免高成本的网络和磁盘 I/O 的方式。Big SQL 将映射到行键的列上的谓词转换为键范围,并将它们应用到 HBase 扫描请求。如果该范围与一到数千行匹配,那么查询会在亚秒级的响应时间内完成。

提示:可以使用合成的行键和二进制编码来最大化谓词下推。

与大多数索引实现一样,您需要在行键的前导部分拥有谓词,Big SQL 才能下推并获得快速的响应。如果仅查询 c2,结果如何?或者说查询另一个未映射到行键的列,比如 c4?在这些情况下,您可以在这些列上创建一个辅助索引。以后会对此进行更详细的介绍。

将关注点转移回行键中的列,Big SQL 也使用了这些列来减少它需要扫描的区域数量。可以将某个区域视为一个由开始键和结束键限定的定义明确的数据切片。BHase 中的表是区域的集合。在查询没有任何谓词时,将扫描整组区域。借助行键的前导部分上的谓词,Big SQL 可以避免执行这种昂贵的全表扫描,只实际处理一个区域子集。进行区域消除的能力,可以保证在 Big SQL 工作节点之间调度和分配查询所需的工作更少,这些节点可以并行地处理区域。

提示:决定行键是 HBase 表设计的最重要部分之一。提出列映射时,尝试将想要快速响应时间的查询中使用的 SQL 列映射到行键。

建立行键映射后,可以如何处理其他所有列?在示例中只有 3 列,但实际上,列数可能会达到数百个或更多。如果建立对列的一对一映射,则需要考虑所有需要存储和传递的有效负载。在这个时候,使用密集列映射会很方便。您可以将其他所有列放在单个 HBase 列中。如果您知道您的查询使用的投影,您可能知道一起检索的列集。将一起查询的列放在一个 HBase 列中是一种更好的选择。在 Big SQL 中,可以在 HBase 实体级别指定一种编码。这意味着可以对行键和每个 HBase 列使用不同的编码。在大部分情况下,最好对整个表使用单一的编码。

提示:使用密集列映射最小化存储空间并改善扫描时间。查看您的工作负载中的查询的投影列表,以决定将哪些列打包在一起。

在编写列映射时,您可能会想为什么不使用 a:b、c:d 作为 HBase 列名称?根据本文中的一条提示,它们很短,应该非常适合。但是等等!您需要理解这对 HBase 表而言有何含义。HBase 中的表有两个列族 a 和 b。一般最好将列族的数量保持最少,最好是一个。这是因为 HBase 将每个列族中的数据持久化到单独的 HFile 中。HFile 是 HBase 中的基本存储单元。为了最小化磁盘 I/O,您可能希望读取较少的文件。拥有一个列族是最好的情况,除非您有一些很少查询和/或总是单独查询的列。在这个示例中,使用了 a:b、a:c 作为列名称可能更有效。这会在 HBase 表中创建一个名为 a 的列族。

提示:尝试使用单个列族,除非您的工作负载要求更多。

对于映射到 HBase 列的 SQL 列,如果一个是密集列映射的前导部分的列(比如 c4)上有一个查询谓词,你会获得一定的下推能力。在这种情况下,Big SQL 会将一个列过滤器下推到 HBase 中。服务器端过滤没有键范围那么高效,因为 HBase 仍需要扫描所有数据,但它有助于减少发送回客户端的数据。

借助上面描述的密集列映射,我们最小化了为每个值返回的 rowkey_columnfamily_columnqualifier_timestamp_value。如果我们可以避免更多的有效负载该多好?这可以通过定义只包含键映射的表来实现。

CREATE HBASE TABLE keyonly(k0 int, k1 int, k2 int)   COLUMN MAPPING   (   key mapped by (k0,k1,k2)   );

使用这样一个表,Big SQL 可以应用特殊过滤器来仅检索行键。这些仅包含键的表非常适合通常会一起查询所有列的窄表。但是请记住,HBase 限制了行键大小,而且行键始终会被检索。考虑将足够多的列包装到行键中,但也要尝试保持它简短。

提示:您可以为窄表定义一个仅包含键的 Big SQL HBase 表。

现在讨论一下注意事项。如果您已开始将 HBase 中的行键视为关系数据库中的主键,那么做一下对比在某种程度上讲是非常合理的。但是,在尝试为现有的行键插入不同的值时,您不会获得任何违反限制的错误。HBase 会创建该值的另一个版本。在 Big SQL 检索一个值的最新版本时,这意味着您会丢失一些数据,但却不会获得任何错误。在传递数据之前,一定要确保您拥有一个唯一的行键。如果不确定,Big SQL 有一条特殊的子句来应对这些情形。

CREATE HBASE TABLE force_key_unique(k0 int, c0 int)   COLUMN MAPPING   (   key mapped by (k0) force key unique,   f:q mapped by (c0)   );

如果没有 FORCE KEY UNIQUE 子句,如果数据包含两个具有相同的 k0 值的行,那么只有最后一行对查询是可见的。使用该子句,Big SQL 会在将行键存储到 HBase 中之前向它附加一个唯一值,而且两行均对查询可见。请注意,使用该子句会向 HBase 中存储更多的数据。因此仅在真正需要时使用此子句。

提示:请注意 HBase 的列版本特性,确保映射到行键的列是唯一的,以预防数据丢失。Big SQL 提供了一个 FORCE KEY UNIQUE 子句,但尽在真正需要时才使用它。

对于行键设计,还有更多内容可以介绍,而且在 HBase 中建模数据时还有其他区域需要考虑。就现在而言,我希望这些技巧能帮助您携带轻便的、正确的有效负载,使您能快速而又高效地检索所需的数据。

</div>