Kudu可以完美的和Impala結合在一起使用,充分利用Impala提供的Insert,Update,Delete等語句。當然使用原生的Kudu API也是可以,只不過結果Impala標準SQL規(guī)范,可以利用JDBC等框架實現(xiàn)無縫的Kudu數(shù)據(jù)管理。
Kudu和Impala的打通,方式1)可以在Impala的服務配置文件加入--kudu_master_hosts=<master1>[:port],<master2>[:port],<master3>[:port],方式2)如果方式1不采用,可以通過創(chuàng)建impala創(chuàng)建kudu表的sql語句的TBLPROPERTIES 中加入,kudu_master_addresses property。
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 <database> 可以指定連接后,打開的database。
Impala的內(nèi)部和外部表
顧名思義,內(nèi)部表,數(shù)據(jù)有impala管理,當通過impala刪除數(shù)據(jù)時候,數(shù)據(jù)就刪除了;而外部表impala不負責管理,刪除的時候,不會刪除數(shù)據(jù)真正存放的位置,而是簡單刪除impala和外部存儲源之間的映射關系,外部表通過create external xxx來創(chuàng)建
CREATE EXTERNAL TABLE my_mapping_table
STORED AS KUDU
TBLPROPERTIES (
'kudu.table_name' = 'my_kudu_table'
);
kudu中通過kudu api或者spark等創(chuàng)建的表,無法直接被impala使用,需通過如上的語句完成impala和kudu表之間的映射。
從Impala創(chuàng)建Kudu表
和創(chuàng)建Kudu外部表類似,只不過你親自需要指定表的schema和分區(qū)信息,例如,
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一個表的時候,你需要指定分區(qū)的方案,具體分區(qū)的知識點見下問,上述語句在主鍵id上分成了16個分區(qū)信息。
默認每個分區(qū)的副本數(shù)為3,可以通過create table語句中加入TBLPROPERTIES 描述來設定不同的副本數(shù),
TBLPROPERTIES ('kudu.num_tablet_replicas' = 'n')
其中n表示副本factor的個數(shù),且n必須為odd number,注意,在alter table中修改TBLPROPERTIES ('kudu.num_tablet_replicas' = 'n')目前無效。
通過create table as select方式創(chuàng)建表,
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表中抽取數(shù)據(jù),生成新的表new_table并存儲于kudu中。
表的分區(qū)
Kudu中的table是分成多個tablet的,這些tablet,目前為止,kudu不支持自動以及手動的將已經(jīng)存在的tablet分割成多個tablet(Hbase可以實現(xiàn)region的自動分割),所以我們需要在創(chuàng)建表的時候,指定好分區(qū),那么數(shù)據(jù)在入庫的時候就會按照分區(qū)規(guī)則自動歸入到某個tablet中。
當create表時候,設置分區(qū)信息,需要根據(jù)primary key來設定分區(qū)的規(guī)則,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數(shù)據(jù)庫中,在create表的時候,可以修改默認database,使用表的時候,需要加上database的前綴訪問數(shù)據(jù)庫,如,my_database::table_name
Kudu table不支持的Impala關鍵詞
- PARTITIONED
- LOCATION
- ROWFORMAT
表分區(qū)
根據(jù)primary key來設置表的分區(qū),每個分區(qū)形成叫做tablet,每個tablet被一個或者多個tablet server管理,這些tablet均衡分布在多個tablet server中,形成負載支持訪問,具體的分區(qū)方案,需要根據(jù)數(shù)據(jù)類型和業(yè)務特點決定,兩種分區(qū)方案Hash和Range。
range分區(qū)
分區(qū)方案可以包含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
這里需要注意,當單調(diào)遞增的主鍵增長情況,如果使用range分區(qū)的方式,會出現(xiàn)一直只往一個tablet寫入的情況,限制了寫入的速率,即熱點寫問題,此時考慮使用hash的分區(qū),可以隨機的往不同的tablet寫入。
hash分區(qū)
替換range分區(qū)或者結合range分區(qū),hash分區(qū)可以通過指定筒的個數(shù),借助hash的方式,實現(xiàn)數(shù)據(jù)分筒。可以指定使用哪個primary key,以及分成多少個筒,數(shù)據(jù)會按照primary key的hash值分到不同的筒中。
可以使用多個hash分區(qū)策略,及時是復合的primary keys,但是,一個列,不能同時出現(xiàn)在多個hash分區(qū)規(guī)則中,例如,兩個列a,b,則,hash(a), hash(b)對,hash(a,b)對,hash(a),hash(a,b)錯,
如果hash方案沒有指定列,PARTITION BY HASH,則表明使用所有的primary keys作為hash方案,
Hash方案適合列的值在范圍內(nèi)分布均勻,或者很明顯不會出現(xiàn)數(shù)據(jù)傾斜,比如, timestamps or serial IDs。
下面的例子是通過未指定列的方式,實現(xiàn)16分區(qū),
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都要參與查詢,以為分區(qū)是id和sku的混合。
高級的分區(qū)方案
結合hash和range,0個或者多個hash,構面跟著0個或者多個range。
hash和range結合方案
考慮上面那個不成熟的列子,我們可以結合hash和range方式,來優(yōu)化sku的范圍查找(假設我們經(jīng)常進行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實現(xiàn)4個buckets,每個bucket根據(jù)sku的范圍又分成了4個部分,總共,也是16個tablet。二次sku分區(qū)的時候,其實可以理解為單獨的sku分區(qū),只不過事先按照id分成了四份而已,因為sku的分區(qū)相當于獨立一樣,故而范圍查找有優(yōu)勢,比hash(id,sku)同時分區(qū)打亂sku范圍好很多。寫入的時候,根據(jù)id的hash定位一次,然后按照sku的范圍寫入,使用id的hash方式,因為id值的特點,使用hash合適,可以保證data spread整個tablet中,避免數(shù)據(jù)傾斜;讀取的時候,畢竟sku是單獨分區(qū)的,所以16個tablet,在sku上每個tablet的sku有一定的范圍,故而,有機會縮小tablet查找數(shù)量(如果沒有給定id,則可以繞過id直接進行sku分區(qū),如果給定id,則直接定位1/4個bucket,再進行范圍sku查找)
hash和hash方式
如果對于sku的搜索模式無法預測,又想保證數(shù)據(jù)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;
未覆蓋的范圍分區(qū)
Kudu1.0或者更高支持未覆蓋的范圍分區(qū),此種情況一般發(fā)生在
- 時間序列或者持續(xù)遞增的primary key
- 產(chǎn)品類型等等,事先不知道具體的類型。
當我們讀取一個不存在的分區(qū),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的數(shù)據(jù)發(fā)現(xiàn)沒有,kudu會reject,此時,我們應該通過語句add新的分區(qū),
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
插入一條數(shù)據(jù)
INSERT INTO my_first_table VALUES (99, "sarah");
在同一個語句中插入多行數(shù)據(jù)
INSERT INTO my_first_table VALUES (1, "john"), (2, "jane"), (3, "jim");
Insert In Bulk
有幾種方式,各有好壞
- Multiple single INSERT statements:多個insert sql語句放在一起執(zhí)行,顯然會影響系統(tǒng)吞吐率
- 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語句,如下語句,第一步,如果數(shù)據(jù)不在impala或者kudu中,通過文件的形式映射成一張表,第二步,創(chuàng)建新的表,注意primary key非空對應原始文件中的數(shù)據(jù)不能為空,第三步,執(zhí)行語句如下。
INSERT INTO my_kudu_table
SELECT * FROM legacy_data_import_table;
- Ingest using the C++ or Java API
插入主鍵重復數(shù)據(jù)
不會報錯,但是會報警,后面的語句繼續(xù)被執(zhí)行。
Upsert
如果數(shù)據(jù)不存在則插入,否則為更新,可以使用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內(nèi)部表對應的底層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');
內(nèi)部表改為外部表
ALTER TABLE my_table SET TBLPROPERTIES('EXTERNAL' = 'TRUE');