Kudu可以完美的和Impala结合在一起使用,充分利用Impala提供的Insert,Update,Delete等语句。当然使用原生的Kudu API也是可以,只不过结果Impala标准SQL规范,可以利用JDBC等框架实现无缝的Kudu数据管理。
Kudu和Impala的打通,方式1)可以在Impala的服务配置文件加入--kudu_master_hosts=
xx PARTITION BY HASH (user_id) PARTITIONS 3 |
STORED AS KUDU
TBLPROPERTIES ('kudu.table_name'='test', 'kudu.master_addresses'='localhost', 'kudu.num_tablet_replicas'='3')
Using Impala Shell
使用impala-shell命令,即可启动shell,默认连接本机的21000端口,使用 -i host:port可以连接远端的impala server, 使用-d
Impala的内部和外部表
顾名思义,内部表,数据有impala管理,当通过impala删除数据时候,数据就删除了;而外部表impala不负责管理,删除的时候,不会删除数据真正存放的位置,而是简单删除impala和外部存储源之间的映射关系,外部表通过create external xxx来创建
CREATE EXTERNAL TABLE my_mapping_table
STORED AS KUDU
TBLPROPERTIES (
'kudu.table_name' = 'my_kudu_table'
);
kudu中通过kudu api或者spark等创建的表,无法直接被impala使用,需通过如上的语句完成impala和kudu表之间的映射。
从Impala创建Kudu表
和创建Kudu外部表类似,只不过你亲自需要指定表的schema和分区信息,例如,
CREATE TABLE my_first_table
(
id BIGINT,
name STRING,
PRIMARY KEY(id)
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
主要以下几点:
- store as kudu
- primary key的列放在第一位置,入id
- 被生成为primary key的列,潜在默认为not null
当create一个表的时候,你需要指定分区的方案,具体分区的知识点见下问,上述语句在主键id上分成了16个分区信息。
默认每个分区的副本数为3,可以通过create table语句中加入TBLPROPERTIES 描述来设定不同的副本数,
TBLPROPERTIES ('kudu.num_tablet_replicas' = 'n')
其中n表示副本factor的个数,且n必须为odd number,注意,在alter table中修改TBLPROPERTIES ('kudu.num_tablet_replicas' = 'n')目前无效。
通过create table as select方式创建表,
CREATE TABLE new_table
PRIMARY KEY (ts, name)
PARTITION BY HASH(name) PARTITIONS 8
STORED AS KUDU
AS SELECT ts, name, value FROM old_table;
new_table的列自动推断于select后面跟着的列,从old_table表中抽取数据,生成新的表new_table并存储于kudu中。
表的分区
Kudu中的table是分成多个tablet的,这些tablet,目前为止,kudu不支持自动以及手动的将已经存在的tablet分割成多个tablet(Hbase可以实现region的自动分割),所以我们需要在创建表的时候,指定好分区,那么数据在入库的时候就会按照分区规则自动归入到某个tablet中。
当create表时候,设置分区信息,需要根据primary key来设定分区的规则,primary key的hash方式,或者primary key的range方式。
CREATE TABLE cust_behavior (
_id BIGINT PRIMARY KEY,
salary STRING,
edu_level INT,
usergender STRING,
`group` STRING,
city STRING,
postcode STRING,
last_purchase_price FLOAT,
last_purchase_date BIGINT,
category STRING,
sku STRING,
rating INT,
fulfilled_date BIGINT
)
PARTITION BY RANGE (_id)
(
PARTITION VALUES < 1439560049342,
PARTITION 1439560049342 <= VALUES < 1439566253755,
PARTITION 1439566253755 <= VALUES < 1439572458168,
PARTITION 1439572458168 <= VALUES < 1439578662581,
PARTITION 1439578662581 <= VALUES < 1439584866994,
PARTITION 1439584866994 <= VALUES < 1439591071407,
PARTITION 1439591071407 <= VALUES
)
STORED AS KUDU;
Database
Kudu表默认在default数据库中,在create表的时候,可以修改默认database,使用表的时候,需要加上database的前缀访问数据库,如,my_database::table_name
Kudu table不支持的Impala关键词
- PARTITIONED
- LOCATION
- ROWFORMAT
表分区
根据primary key来设置表的分区,每个分区形成叫做tablet,每个tablet被一个或者多个tablet server管理,这些tablet均衡分布在多个tablet server中,形成负载支持访问,具体的分区方案,需要根据数据类型和业务特点决定,两种分区方案Hash和Range。
range分区
分区方案可以包含0个或者多个hash方式,后面跟着一个可选的range方案。range方案可以涉及1个或者多个primary key列。
CREATE TABLE customers (
state STRING,
name STRING,
purchase_count int,
PRIMARY KEY (state, name)
)
PARTITION BY RANGE (state)
(
PARTITION VALUE = 'al',
PARTITION VALUE = 'ak',
PARTITION VALUE = 'ar',
-- ... etc ...
PARTITION VALUE = 'wv',
PARTITION VALUE = 'wy'
)
STORED AS KUDU;
以上0个hash,后面跟着range
这里需要注意,当单调递增的主键增长情况,如果使用range分区的方式,会出现一直只往一个tablet写入的情况,限制了写入的速率,即热点写问题,此时考虑使用hash的分区,可以随机的往不同的tablet写入。
hash分区
替换range分区或者结合range分区,hash分区可以通过指定筒的个数,借助hash的方式,实现数据分筒。可以指定使用哪个primary key,以及分成多少个筒,数据会按照primary key的hash值分到不同的筒中。
可以使用多个hash分区策略,及时是复合的primary keys,但是,一个列,不能同时出现在多个hash分区规则中,例如,两个列a,b,则,hash(a), hash(b)对,hash(a,b)对,hash(a),hash(a,b)错,
如果hash方案没有指定列,PARTITION BY HASH,则表明使用所有的primary keys作为hash方案,
Hash方案适合列的值在范围内分布均匀,或者很明显不会出现数据倾斜,比如, timestamps or serial IDs。
下面的例子是通过未指定列的方式,实现16分区,
CREATE TABLE cust_behavior (
id BIGINT,
sku STRING,
salary STRING,
edu_level INT,
usergender STRING,
`group` STRING,
city STRING,
postcode STRING,
last_purchase_price FLOAT,
last_purchase_date BIGINT,
category STRING,
rating INT,
fulfilled_date BIGINT,
PRIMARY KEY (id, sku)
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
这个列子不是最好的方案,因为sku的range查询会导致16个tablet都要参与查询,以为分区是id和sku的混合。
高级的分区方案
结合hash和range,0个或者多个hash,构面跟着0个或者多个range。
hash和range结合方案
考虑上面那个不成熟的列子,我们可以结合hash和range方式,来优化sku的范围查找(假设我们经常进行sku的范围查找),
CREATE TABLE cust_behavior (
id BIGINT,
sku STRING,
salary STRING,
edu_level INT,
usergender STRING,
`group` STRING,
city STRING,
postcode STRING,
last_purchase_price FLOAT,
last_purchase_date BIGINT,
category STRING,
rating INT,
fulfilled_date BIGINT,
PRIMARY KEY (id, sku)
)
PARTITION BY HASH (id) PARTITIONS 4,
RANGE (sku)
(
PARTITION VALUES < 'g',
PARTITION 'g' <= VALUES < 'o',
PARTITION 'o' <= VALUES < 'u',
PARTITION 'u' <= VALUES
)
STORED AS KUDU;
上述代码,通过id实现4个buckets,每个bucket根据sku的范围又分成了4个部分,总共,也是16个tablet。二次sku分区的时候,其实可以理解为单独的sku分区,只不过事先按照id分成了四份而已,因为sku的分区相当于独立一样,故而范围查找有优势,比hash(id,sku)同时分区打乱sku范围好很多。写入的时候,根据id的hash定位一次,然后按照sku的范围写入,使用id的hash方式,因为id值的特点,使用hash合适,可以保证data spread整个tablet中,避免数据倾斜;读取的时候,毕竟sku是单独分区的,所以16个tablet,在sku上每个tablet的sku有一定的范围,故而,有机会缩小tablet查找数量(如果没有给定id,则可以绕过id直接进行sku分区,如果给定id,则直接定位1/4个bucket,再进行范围sku查找)
hash和hash方式
如果对于sku的搜索模式无法预测,又想保证数据spread到整个tablet,可以采用多个hash方式在每个primary key上。
CREATE TABLE cust_behavior (
id BIGINT,
sku STRING,
salary STRING,
edu_level INT,
usergender STRING,
`group` STRING,
city STRING,
postcode STRING,
last_purchase_price FLOAT,
last_purchase_date BIGINT,
category STRING,
rating INT,
fulfilled_date BIGINT,
PRIMARY KEY (id, sku)
)
PARTITION BY HASH (id) PARTITIONS 4,
HASH (sku) PARTITIONS 4
STORED AS KUDU;
未覆盖的范围分区
Kudu1.0或者更高支持未覆盖的范围分区,此种情况一般发生在
- 时间序列或者持续递增的primary key
- 产品类型等等,事先不知道具体的类型。
当我们读取一个不存在的分区,kudu会reject,提示,
CREATE TABLE sales_by_year (
year INT, sale_id INT, amount INT,
PRIMARY KEY (sale_id, year)
)
PARTITION BY RANGE (year) (
PARTITION VALUE = 2012,
PARTITION VALUE = 2013,
PARTITION VALUE = 2014,
PARTITION VALUE = 2015,
PARTITION VALUE = 2016
)
STORED AS KUDU;
当我们,查询2017的数据发现没有,kudu会reject,此时,我们应该通过语句add新的分区,
ALTER TABLE sales_by_year ADD RANGE PARTITION VALUE = 2017;
当然,每个range范围不需要了,也可以删除
ALTER TABLE sales_by_year DROP RANGE PARTITION VALUE = 2012;
Insert Data
标准的SQL语句方式
Insert Single Values
插入一条数据
INSERT INTO my_first_table VALUES (99, "sarah");
在同一个语句中插入多行数据
INSERT INTO my_first_table VALUES (1, "john"), (2, "jane"), (3, "jim");
Insert In Bulk
有几种方式,各有好坏
- Multiple single INSERT statements:多个insert sql语句放在一起执行,显然会影响系统吞吐率
- Single INSERT statement with multiple VALUES:一条语句中加入多个value,当我们累加到1024个value list后,impala会将1024个value list组织到一起,请求kudu,显然比方案1号。并且通过更改当前impala shell session对应的batch_size来改变1024这个默认值,增大batch的size大小。
- Batch Insert:借助insert语句,如下语句,第一步,如果数据不在impala或者kudu中,通过文件的形式映射成一张表,第二步,创建新的表,注意primary key非空对应原始文件中的数据不能为空,第三步,执行语句如下。
INSERT INTO my_kudu_table
SELECT * FROM legacy_data_import_table;
- Ingest using the C++ or Java API
插入主键重复数据
不会报错,但是会报警,后面的语句继续被执行。
Upsert
如果数据不存在则插入,否则为更新,可以使用upsrt语句
INSERT INTO my_first_table VALUES (99, "sarah");
UPSERT INTO my_first_table VALUES (99, "zoe");
-- the current value of the row is 'zoe'
Update/Delete/Drop table
ignore
SQL Failed
insert,update,delete等语句,不支持事物,batch语句,由于没有事物控制,当中间出错,前面的失效了,后面的忽略了。
修改表的属性
修改表名
ALTER TABLE my_table RENAME TO my_new_table;
修改Impala内部表对应的底层kudu表
ALTER TABLE my_internal_table
SET TBLPROPERTIES('kudu.table_name' = 'new_name')
修改Impala外部表映射到不同的kudu表
ALTER TABLE my_external_table_
SET TBLPROPERTIES('kudu.table_name' = 'some_other_kudu_table')
修改表的kudu master地址
ALTER TABLE my_table
SET TBLPROPERTIES('kudu.master_addresses' = 'kudu-new-master.example.com:7051');
内部表改为外部表
ALTER TABLE my_table SET TBLPROPERTIES('EXTERNAL' = 'TRUE');