6.1、概述

Kudu与Apache Impala紧密集成,允许您使用Impala使用Impala的SQL语法从Kudu平板插入、查询、更新和删除数据,作为使用Kudu api构建自定义Kudu应用程序的替代方案。此外,您可以使用JDBC或ODBC将用任何语言、框架或商业智能工具编写的现有或新的应用程序连接到Kudu数据,使用Impala作为代理。

6.2、需求

  • 本文档针对特定版本的Impala。所描述的语法只适用于以下版本:

    • CDH 5.10附带的Impala 2.7.0版本。SELECT VERSION()将报告impalad版本2.7.0-cdh5.10.0。
    • 从源代码编译的Apache Impala 2.8.0版本。SELECT VERSION()将报告impalad版本2.8.0。

旧版本的Impala 2.7(包括以前可用的特殊IMPALA_KUDU版本)语法不兼容。未来的版本可能与此语法兼容,但我们建议检查这是与您安装的适当版本相对应的最新可用文档。

  • 本文档没有描述Impala的安装过程。请参考Impala文档,并确保您能够在继续之前对HDFS上的Impala表运行简单的查询。

6.3、配置

在Kudu中不需要进行任何配置更改就可以从Impala访问。

虽然不是必须的,但建议将Impala配置为Kudu Master服务器的位置:

  • 在Impala服务配置中设置 —kudu_master_hosts=[:port],[:port],[:port]标志。

如果Impala服务中没有设置此标志,那么每次创建表时都需要通过指定kudu手动提供此配置。TBLPROPERTIES子句中的master_addresses属性。

本指南的其余部分假设已经设置了配置。

6.4、使用Impala Shell

这只是Impala Shell功能的一小部分。更多详细信息,请参见Impala Shell文档。

  • 使用impala-shell命令启动Impala Shell。默认情况下,impala-shell尝试在端口21000上连接本地主机上的Impala守护进程。要连接到另一个主机,请使用-i 选项。要自动连接到特定的Impala数据库,请使用-d 选项。例如,如果所有Kudu表都在数据库impala_kudu中的Impala中,则使用-d impala_kudu来使用该数据库。

  • 使用以下命令退出Impala Shell: quit;

6.4.1、内部和外部Impala表

当使用Impala创建新的Kudu表时,您可以将该表创建为内部表或外部表。

内部
内部表由Impala管理,当您从Impala中删除它时,数据和表就真正被删除了。当您使用Impala创建新表时,它通常是一个内部表。

外部
外部表(由CREATE external table创建)不受Impala管理,删除这样的表不会从源位置(这里是Kudu)删除表。相反,它只删除了羚羊和Kudu之间的映射。这是Kudu提供的将现有表映射到Impala的语法中使用的模式。

有关内部和外部表的更多信息,请参阅Impala文档。

6.4.2、使用Hive Metastore集成

从Kudu 1.10.0和Impala 3.3.0开始,Impala集成可以利用由Kudu的Hive Metastore集成所启用的自动Kudu- hms目录同步。由于Kudu表和外部表之间可能没有一对一的映射,因此只有内部表会自动同步。关于Kudu的Hive Metastore集成的更多细节,请参阅HMS集成文档。

当Kudu与Hive Metastore的集成未启用时,Impala将代表Kudu在HMS中创建元数据项。
当启用Kudu与Hive Metastore的集成时,需要配置Impala使用与Kudu相同的Hive Metastore。

6.4.3、查询已存在的羚羊Kudu表

如果不启用HMS集成,通过Kudu API或Apache Spark等其他集成创建的表在Impala中不会自动可见。要查询它们,您必须首先在Impala中创建一个外部表,将Kudu表映射到Impala数据库:

  1. CREATE EXTERNAL TABLE my_mapping_table
  2. STORED AS KUDU
  3. TBLPROPERTIES (
  4. 'kudu.table_name' = 'my_kudu_table'
  5. );

当启用Kudu-HMS集成时,在没有Impala的Kudu中创建表时,内部表项将自动在HMS中创建。要通过Impala访问这些表,请运行invalidate metadata以便Impala获取最新的元数据。

  1. INVALIDATE METADATA;

6.4.4、从Impala创建一个新的Kudu表

从Impala在Kudu中创建新表类似于将现有的Kudu表映射到Impala表,只是需要自己指定模式和分区信息。

使用下面的例子作为指导。Impala首先创建表,然后创建映射。

  1. CREATE TABLE my_first_table
  2. (
  3. id BIGINT,
  4. name STRING,
  5. PRIMARY KEY(id)
  6. )
  7. PARTITION BY HASH PARTITIONS 16
  8. STORED AS KUDU;

在CREATE TABLE语句中,必须首先列出组成主键的列。此外,主键列被隐式地标记为NOT NULL。

在创建一个新的Kudu表时,需要指定一个分布方案。看到分区表。为了简单起见,上面的表创建示例通过散列id列分布到16个分区中。关于分区的指导原则,请参见分区经验法则。

默认情况下,通过Impala创建的Kudu表使用的平板复制因子为3。要为Kudu表指定复制因子,在CREATE table语句中添加TBLPROPERTIES子句,如下所示,其中n是您想要使用的复制因子:

  1. TBLPROPERTIES ('kudu.num_tablet_replicas' = 'n')

复制因子必须为奇数。

改变kudu.num_tablet_replicas表属性使用ALTER table当前无效。

CREATE TABLE AS SELECT

您可以通过查询Impala中的任何其他表来创建表,使用create table…AS SELECT语句。下面的示例将现有表old_table中的所有行导入到Kudu表new_table中。new_table中的列的名称和类型将从SELECT语句的结果集中的列中确定。注意,您必须另外指定主键和分区。

  1. CREATE TABLE new_table
  2. PRIMARY KEY (ts, name)
  3. PARTITION BY HASH(name) PARTITIONS 8
  4. STORED AS KUDU
  5. AS SELECT ts, name, value FROM old_table;

Tablet指定分区

Tables被分成Tablets,每个tablet由一个或多个tablet server提供。理想情况下,平板电脑应该相对平均地分割一个表的数据。Kudu目前还没有自动(或手动)拆分现有平板电脑的机制。在实现此特性之前,必须在创建表时指定分区。在设计表模式时,请考虑允许您将表划分为以类似速度增长的分区的主键。使用Impala创建表时,可以使用PARTITION BY子句指定分区:

当Impala关键字(如group)没有按关键字意义使用时,它们由反勾号字符括起来。

  1. CREATE TABLE cust_behavior (
  2. _id BIGINT PRIMARY KEY,
  3. salary STRING,
  4. edu_level INT,
  5. usergender STRING,
  6. `group` STRING,
  7. city STRING,
  8. postcode STRING,
  9. last_purchase_price FLOAT,
  10. last_purchase_date BIGINT,
  11. category STRING,
  12. sku STRING,
  13. rating INT,
  14. fulfilled_date BIGINT
  15. )
  16. PARTITION BY RANGE (_id)
  17. (
  18. PARTITION VALUES < 1439560049342,
  19. PARTITION 1439560049342 <= VALUES < 1439566253755,
  20. PARTITION 1439566253755 <= VALUES < 1439572458168,
  21. PARTITION 1439572458168 <= VALUES < 1439578662581,
  22. PARTITION 1439578662581 <= VALUES < 1439584866994,
  23. PARTITION 1439584866994 <= VALUES < 1439591071407,
  24. PARTITION 1439591071407 <= VALUES
  25. )
  26. STORED AS KUDU;

如果有多个主键列,可以使用元组语法指定分区界限:(‘va’,1), (‘ab’,2)。表达式必须是有效的JSON。

Impala数据库和Kudu

每个Impala表都包含在一个名为数据库的名称空间中。默认数据库称为default,用户可以根据需要创建和删除其他数据库。

当从Impala中创建托管的Kudu表时,对应的Kudu表将被命名为Impala::database_name.table_name。前缀总是impala::,然后是数据库名和表名,中间用点分隔。

例如,如果在Impala的数据库bar中创建了一个名为foo的表,并且它存储在Kudu中,那么它将被称为Impala::bar。foo在Kudu和bar。在黑斑羚foo。

Kudu表不支持Impala关键字
创建Kudu表时不支持以下Impala关键字:- PARTITIONED - LOCATION - ROWFORMAT

6.4.5、优化SQL谓词的性能

如果查询的WHERE子句包含与操作符=、<=、’\<’、’>‘、>=、BETWEEN或IN的比较,Kudu会直接计算条件,并只返回相关的结果。这提供了最佳性能,因为Kudu只将相关结果返回给Impala。对于谓词!=、LIKE或Impala支持的任何其他谓词类型,Kudu不直接计算谓词,而是将所有结果返回给Impala,并依赖Impala计算其余谓词并相应地过滤结果。这可能会导致性能上的差异,具体取决于计算WHERE子句之前和之后结果集的增量。

6.4.6、分区表

根据主键列上的分区模式,将表划分为片。每个片剂由至少一个片剂服务器提供。理想情况下,一个表应该被分割成分布在多个平板服务器上的平板,以最大化并行操作。您使用的分区模式的细节将完全取决于存储的数据类型和访问它的方式。有关Kudu中模式设计的完整讨论,请参见模式设计。

Kudu目前没有在创建表后拆分或合并药片的机制。在创建表时,必须为表提供分区模式。在设计你的表格时,考虑使用主键,这将允许你把你的表格划分成平板电脑,这些平板电脑的增长速度相似。

您可以使用Impala的partition BY关键字对表进行分区,该关键字支持按RANGE或HASH进行分布。分区方案可以包含0个或多个HASH定义,后跟一个可选的RANGE定义。RANGE定义可以引用一个或多个主键列。基本分区和高级分区的示例如下所示。

1、基本分区

范围分区
可以为一个或多个主键列指定范围分区。在Kudu中,范围分区允许基于特定值或所选分区键值的范围来分割表。这使您能够平衡写操作的并行性和扫描效率。

假设有一个表,其中列state、name和purchase_count。下面的例子创建了50个平板电脑,每个美国州一个。

单调递增的值
如果在一个值单调递增的列上按范围进行划分,那么最后一个片将比其他片增长得大得多。此外,所有插入的数据将一次写入一个平板电脑,这限制了数据摄取的可伸缩性。在这种情况下,考虑使用HASH而不是RANGE进行分布。

  1. CREATE TABLE customers (
  2. state STRING,
  3. name STRING,
  4. purchase_count int,
  5. PRIMARY KEY (state, name)
  6. )
  7. PARTITION BY RANGE (state)
  8. (
  9. PARTITION VALUE = 'al',
  10. PARTITION VALUE = 'ak',
  11. PARTITION VALUE = 'ar',
  12. -- ... etc ...
  13. PARTITION VALUE = 'wv',
  14. PARTITION VALUE = 'wy'
  15. )
  16. STORED AS KUDU;

哈希分区

不是通过显式的范围分布,或者结合范围分布,您可以通过散列分布到特定数量的“桶”。您可以指定希望按主键列进行分区,以及希望使用的存储桶数量。通过散列指定键列来分布行。假设被散列的值本身没有明显的倾斜性,这将有助于将数据均匀地分布到各个存储桶。

您可以指定多个定义,并且可以指定使用复合主键的定义。但是,不能在多个散列定义中提到一个列。考虑两列a和b: HASH(a), HASH(b) HASH(a,b) * HASH(a), HASH(a,b)

不指定列的PARTITION BY HASH是一种通过散列所有主键列来创建所需桶数的快捷方式。

如果主键值均匀分布在它们的域中,并且没有明显的数据倾斜,比如时间戳或串行id,那么散列分区是一种合理的方法。

下面的示例通过散列id和sku列创建16个片区。这将在所有16个平板电脑上进行书写。在本例中,一个sku值范围的查询可能需要读取所有16个tablet,因此这可能不是该表的最佳模式。有关扩展示例,请参见高级分区。

  1. CREATE TABLE cust_behavior (
  2. id BIGINT,
  3. sku STRING,
  4. salary STRING,
  5. edu_level INT,
  6. usergender STRING,
  7. `group` STRING,
  8. city STRING,
  9. postcode STRING,
  10. last_purchase_price FLOAT,
  11. last_purchase_date BIGINT,
  12. category STRING,
  13. rating INT,
  14. fulfilled_date BIGINT,
  15. PRIMARY KEY (id, sku)
  16. )
  17. PARTITION BY HASH PARTITIONS 16
  18. STORED AS KUDU;

2、高级分区

您可以结合使用HASH和RANGE分区来创建更复杂的分区模式。可以指定零个或多个HASH定义,后跟零个或一个RANGE定义。每个定义可以包含一个或多个列。虽然枚举每个可能的分发模式超出了本文的范围,但一些示例说明了一些可能性。

按哈希和范围划分
考虑上面的简单哈希示例,如果您经常查询sku值的范围,那么可以通过结合哈希分区和范围分区来优化这个示例。

下面的示例仍然创建了16个片区,首先将id列散列为4个桶,然后根据sku字符串的值应用范围分区将每个桶划分为4个片区。写操作至少分布在4个平板上(可能最多16个)。当您查询一个连续的sku值范围时,您很可能只需要从四分之一的tablet中读取即可完成查询。

默认情况下,当使用PARTITION By HASH时,整个主键都是散列的。要对主键的一部分进行散列,可以使用类似PARTITION by hash (id, sku)的语法来指定它。

  1. CREATE TABLE cust_behavior (
  2. id BIGINT,
  3. sku STRING,
  4. salary STRING,
  5. edu_level INT,
  6. usergender STRING,
  7. `group` STRING,
  8. city STRING,
  9. postcode STRING,
  10. last_purchase_price FLOAT,
  11. last_purchase_date BIGINT,
  12. category STRING,
  13. rating INT,
  14. fulfilled_date BIGINT,
  15. PRIMARY KEY (id, sku)
  16. )
  17. PARTITION BY HASH (id) PARTITIONS 4,
  18. RANGE (sku)
  19. (
  20. PARTITION VALUES < 'g',
  21. PARTITION 'g' <= VALUES < 'o',
  22. PARTITION 'o' <= VALUES < 'u',
  23. PARTITION 'u' <= VALUES
  24. )
  25. STORED AS KUDU;
  26. Multiple PARTITION BY HASH Definitions
  27. Again expanding the example above, suppose that the query pattern will be unpredictable, but you want to ensure that writes are spread across a large number of tablets You can achieve maximum distribution across the entire primary key by hashing on both primary key columns.
  28. CREATE TABLE cust_behavior (
  29. id BIGINT,
  30. sku STRING,
  31. salary STRING,
  32. edu_level INT,
  33. usergender STRING,
  34. `group` STRING,
  35. city STRING,
  36. postcode STRING,
  37. last_purchase_price FLOAT,
  38. last_purchase_date BIGINT,
  39. category STRING,
  40. rating INT,
  41. fulfilled_date BIGINT,
  42. PRIMARY KEY (id, sku)
  43. )
  44. PARTITION BY HASH (id) PARTITIONS 4,
  45. HASH (sku) PARTITIONS 4
  46. STORED AS KUDU;

多哈希分区定义
再次扩展上面的示例,假设查询模式是不可预测的,但您希望确保写分布在大量的药片。您可以通过对两个主键列进行散列来实现整个主键的最大分布。

  1. CREATE TABLE cust_behavior (
  2. id BIGINT,
  3. sku STRING,
  4. salary STRING,
  5. edu_level INT,
  6. usergender STRING,
  7. `group` STRING,
  8. city STRING,
  9. postcode STRING,
  10. last_purchase_price FLOAT,
  11. last_purchase_date BIGINT,
  12. category STRING,
  13. rating INT,
  14. fulfilled_date BIGINT,
  15. PRIMARY KEY (id, sku)
  16. )
  17. PARTITION BY HASH (id) PARTITIONS 4,
  18. HASH (sku) PARTITIONS 4
  19. STORED AS KUDU;

本例创建了16个分区。您也可以使用HASH (id, sku) PARTITIONS 16。然而,对sku值的扫描几乎总是会影响所有16个分区,而不是可能被限制为4个分区。

Non-Covering范围分区
Kudu 1.0及更高版本支持使用非覆盖范围分区,解决如下场景:

  • 如果没有不覆盖的范围分区,对于时间序列数据或其他需要考虑不断增加的主键的模式,提供旧数据的平板电脑的大小将相对固定,而接收新数据的平板电脑将无限制地增长。

  • 在这种情况下,你想分区数据根据其类别,如销售区域或产品类型,没有non-covering范围分区你必须知道所有的分区提前或手动重新创建表如果需要添加或删除分区,如引入或消除产品类型。

有关非覆盖分区的注意事项,请参见模式设计。

本例每年创建一个片区(总共5片),用于存储日志数据。该表只接受2012 - 2016年的数据。超出这些范围的键将被拒绝。

  1. CREATE TABLE sales_by_year (
  2. year INT, sale_id INT, amount INT,
  3. PRIMARY KEY (year, sale_id)
  4. )
  5. PARTITION BY RANGE (year) (
  6. PARTITION VALUE = 2012,
  7. PARTITION VALUE = 2013,
  8. PARTITION VALUE = 2014,
  9. PARTITION VALUE = 2015,
  10. PARTITION VALUE = 2016
  11. )
  12. STORED AS KUDU;

2017年的档案将会被拒绝。届时,应将2017年的区间增加如下:

  1. ALTER TABLE sales_by_year ADD RANGE PARTITION VALUE = 2017;

在需要滚动数据保留窗口的用例中,范围分区也可能被删除。例如,如果不再保留2012年的数据,则可能会批量删除:

  1. ALTER TABLE sales_by_year DROP RANGE PARTITION VALUE = 2012;

请注意,就像删除表一样,这将不可恢复地删除存储在已删除分区中的所有数据。

经验划分规则

  • 对于大型表(如事实表),目标是尽可能多地使用集群中的核心。
  • 对于小表(如维度表),确保每个tablet的大小至少为1gb。

一般来说,在当前的实现中,要注意平板的数量限制了读取的并行性。增加平板电脑数量远远超过核心数量可能会导致收益递减。

6.4.7、插入数据到Kudu表

Impala允许您使用标准SQL语法将数据插入到Kudu中。

1、插入单个值
这个例子插入了一行。

  1. INSERT INTO my_first_table VALUES (99, "sarah");

这个例子使用一条语句插入了三行。

  1. INSERT INTO my_first_table VALUES (1, "john"), (2, "jane"), (3, "jim");

2、批量插入

在批量插入时,至少有三种常见的选择。每一种都有其优缺点,这取决于你的数据和环境。

多个单个INSERT语句
这种方法的优点是易于理解和实现。这种方法可能效率不高,因为与Kudu的插入性能相比,Impala的查询启动成本较高。这将导致相对较高的延迟和较差的吞吐量。

一个INSERT语句包含多个VALUES
如果包含超过1024个VALUES语句,Impala会在将请求发送给Kudu之前将它们分成1024个组(或batch_size的值)。通过在Impala端摊销查询启动的代价,这种方法可能比多个顺序INSERT语句执行得稍好一些。要设置当前Impala Shell会话的批大小,请使用以下语法:set batch_size=10000;

增加Impala批大小将导致Impala使用更多内存。您应该验证对集群的影响并进行相应的调优。

批量插入
从Impala和Kudu的角度来看,通常性能最好的方法是使用Impala中的SELECT from语句导入数据。
1、如果数据尚未在Impala中,一个策略是从文本文件(如TSV或CSV文件)导入数据。
2、创建Kudu表,注意指定为主键的列不能有空值。
3、通过查询包含原始数据的表,向Kudu表中插入值,示例如下:

  1. INSERT INTO my_kudu_table
  2. SELECT * FROM legacy_data_import_table;

使用c++或Java API摄取
在许多情况下,适当的摄取路径是使用c++或Java API直接插入到Kudu表中。与其他Impala表不同,通过API插入到Kudu表中的数据可以在Impala中查询,而不需要任何INVALIDATE METADATA语句或其他Impala存储类型所需的语句。

3、INSERT和主键唯一性违反

在大多数关系数据库中,如果试图插入已经插入的行,则插入会失败,因为主键会重复。请参阅INSERT、UPDATE和DELETE操作失败。但是,Impala不会使查询失败。相反,它将生成一个警告,但继续执行insert语句的其余部分。

如果插入的行要替换现有的行,则可以使用UPSERT代替INSERT。

  1. INSERT INTO my_first_table VALUES (99, "sarah");
  2. UPSERT INTO my_first_table VALUES (99, "zoe");
  3. -- the current value of the row is 'zoe'

6.4.8、更新一行

  1. UPDATE my_first_table SET name="bob" where id = 3;

UPDATE语句只在目标表为Kudu时在Impala中有效。

批量更新
您可以使用与批量插入相同的方法批量更新。

  1. UPDATE my_first_table SET name="bob" where age > 10;

6.4.9、删除一行

  1. DELETE FROM my_first_table WHERE id < 3;

您也可以使用更复杂的语法来删除。FROM子句中的逗号是Impala指定连接查询的一种方式。有关Impala join的更多信息,请参见https://impala.apache.org/docs/build/html/topics/impala_joins.html

  1. DELETE c FROM my_second_table c, stock_symbols s WHERE c.name = s.symbol;

DELETE语句只在目标表为Kudu时在Impala中有效。

批量删除
您可以使用与批量插入相同的方法批量删除。

  1. DELETE FROM my_first_table WHERE id < 3;

6.4.10、INSERT、UPDATE、DELETE操作失败

INSERT、UPDATE和DELETE语句不能作为一个整体被视为事务性语句。如果其中一个操作部分失败,则键可能已经被创建(对于INSERT来说),或者记录可能已经被另一个进程修改或删除(对于UPDATE或DELETE来说)。在设计应用程序时应该牢记这一点。

6.4.11、改变表属性

您可以通过更改表的属性来更改与给定Kudu表相关的Impala元数据。这些属性包括表名、Kudu主地址列表,以及表是由Impala(内部)管理还是由外部管理。

重命名Impala映射表

  1. ALTER TABLE my_table RENAME TO my_new_table;

在Impala 3.2及更低版本中,使用ALTER table…RENAME语句重命名一个表只重命名一个Impala映射表,而不管这个表是内部表还是外部表。从Impala 3.3开始,重命名表也将重命名底层的Kudu表。

将底层Kudu表重命名为内部表
在Impala 2.11及以下版本中,可以通过更改Kudu来重新命名底层的Kudu表。table_name属性:

  1. ALTER TABLE my_internal_table
  2. SET TBLPROPERTIES('kudu.table_name' = 'new_name')

将一个外部表重新映射到一个不同的Kudu表
如果另一个应用程序在Impala下重命名了一个Kudu表,则可以重新映射一个外部表,使其指向不同的Kudu表名。

  1. ALTER TABLE my_external_table_
  2. SET TBLPROPERTIES('kudu.table_name' = 'some_other_kudu_table')

修改Kudu master地址

  1. ALTER TABLE my_table
  2. SET TBLPROPERTIES('kudu.master_addresses' = 'kudu-new-master.example.com:7051');

将一个内部管理表更改为外部管理表

  1. ALTER TABLE my_table SET TBLPROPERTIES('EXTERNAL' = 'TRUE');

当启用Hive Metastore集成时,不允许更改表类型,以避免可能引入Kudu和HMS目录之间的不一致性。

6.4.12、使用Impala删除Kudu表

如果该表在Impala中创建为一个内部表,则使用CREATE table,标准的DROP table语法将删除底层的Kudu表及其所有数据。如果使用CREATE external table将表创建为外部表,则会删除Impala和Kudu之间的映射,但保留Kudu表及其所有数据。

  1. DROP TABLE my_first_table;