Kudu系列-基础

Apache Kudu 支持Insert/Update/Delete 等写操作(Kudu 随机写效率也很高, 实测对一个窄表做全字段update, 其速度达到了Insert速度的88%, 而vertica的update效率比insert差很多), Kudu 表文件是列式数据格式(和Parquet格式类似), 所以Kudu是一个支持记录修改的分析型系统, Kudu+Impala组合起来就是一个很有竞争力的MPP架构.

SQL on kudu 不同与SQL on hadoop, Hive 和其他 SQL on hadoop都是read on schema方式, 而 impala on kudu 是 write on scheme(这和普通的DB一样), 这就导致了两个不同点:
1. impala 插入kudu 表, 将根据记录的分区键写到对应的kudu tablet中, 而SQL on hadoop无法精细控制记录写入到哪个data node中.
2. 不合法的记录将无法写入到kudu表, 而使用hive 却能写入到hdfs文件中, 比如一个int字段,我们无法将'abc'这样的字符串写入到kudu表; 但使用hive可以写入, 只是读取时该字段被处理为null.
3. 因为kudu知道一个记存放在哪里(通过主键), 如果我们的Select/delete/update语句能利用上主键(聚集主键), 执行效率比较高. 如果利用不上的话, 效率就差了.

Apache Kudu 虽然是Cloudera贡献的, 但并没有包含在CDH中, 需要单独安装, 因为 Kudu 其实并不依赖 Hadoop 运行环境, 文件并不是存放在hdfs上. 但如果搭配Impala使用, 就需要Hadoop环境(impala以来Hive).


---==============================
安装 kudu
---==============================
在CDH 5.5.10 VM版本以上(impala 2.7版以上), 使用下面命令安装kudu软件.
$ sudo yum install kudu # Base Kudu files
$ sudo yum install kudu-master # Kudu master init.d service script and default configuration
$ sudo yum install kudu-tserver # Kudu tablet server init.d service script and default configuration
$ sudo yum install kudu-client0 # Kudu C++ client shared library
$ sudo yum install kudu-client-devel # Kudu C++ client SDK


kudu安装包会自动在Linux alternatives 数据库下创建 kudu-conf 入口,
sudo alternatives --display kudu-conf

手动启动kudu
$ sudo service kudu-master start
$ sudo service kudu-tserver start

自动启动设置
$ sudo chkconfig kudu-master on # RHEL / CentOS / SLES
$ sudo chkconfig kudu-tserver on # RHEL / CentOS / SLES

---==============================
主键
---==============================
主键: 每个表都必须有一个Unique主键, 主键可以是单独字段也可以由多个字段组成, 但主键中的每个字段都应该是non null, 而且不能是boolean和浮点类型字段. 重复的PK的记录将无法Insert到Kudu表中, 而且主键值是不能修改的. Kudu主键是聚集索引, 也就是说每个tablet中的记录将按照主键排序存储, 在scan时候能利用这些排序加快记录查找.
通过Kudu API更新/删除记录必须提供主键, 通过Impala查询引擎没有这个限制.

注意:
1. 在建表DDL语句中, 主键要放到字段清单的前段.
2. 除了主键, kudu不支持建立其他索引
3. 主键值不能被update
4. insert语句中, 主键名是大小写敏感的, 其他语句大小写不敏感. [怀疑是kudu的bug]


主键的选择原则:
1. 选择性强的字段(比如id 类) 应该放在PK清单最前面, 这个规则对查询性能影响最大.
2. PK清单中只加必要的unique字段, 越少越好.
3. 如果查询针对PK中所有字段都加了条件, 其性能是最优的. 但只要有一个PK字段未加条件, 相当于完全用不上PK索引,性能就很差.
我专门又写了个文章讨论主键选择策略, http://www.cnblogs.com/harrychinese/p/kdu_pk.html

---==============================
分区
---==============================
分区键: Kudu要求分区键必须属于主键. 如果没有显式指定分区键的时候, 主键就是分区键, 即按照主键Hash分区, 分区数量为1个, 而且只有缺省方式才能建立1 个 partition,  专门用语句指定的话, partition数量必须大于1.
既然分区键一定属于主键, 所以Kudu表的主键最好除了包含业务主键, 最好再加上也写常用的查询字段, 比如时间字段和userId这样的字段, 这样能更好地设计分区策略.

分区数如何确定:

Kudu每个表在tablet server上的切片数量不能大于60个(含replica), 按照20个tablet server算, 一个表最多400个partition, Impala的限制是100K个, Vertica的限制是512个.
通常增加分区(切片)数, 对于写入是有利的, 但对于读取可能有利有弊(会提升读取的并行度, 但会增加扫描切片的数量).
总的原则是: 切片数不要大于集群中CPU的总core数, 如果切片数超过CPU core数, 性能反倒会下降.
具体为:
1. Fact表, 切片数<=集群中CPU的总core数, 并是机器数量的倍数, 以防止数据倾斜.
2. Dim表, 切片数应<<集群中CPU的总core数, 保证每个tablet的size至少1GB.


分区方法:
Kudu分区方法只能在建表的时候确定, 所以确定分区方法一定要仔细考虑. Kudu支持Hash和Range分区, 而且支持使用Hash+Range作分区.
1. hash 分区: 写入压力较大的表, 比如发帖表, 按照帖子自增Id作Hash分区, 可以有效地将写压力分摊到各个tablet中. 因为hash的分区键是自增性的, 就不会有 hot 切片(瓶颈). 可见, 纯hash分区比较适合选择性好的分区键.
2. range 分区: 如果时间字段可以作为主键的一部分, 可以考虑将时间字段作为range分区键, 往往会得到较好的查询效果. 因为Kudu分区数不能超过1000个, 所以通常不适合用日期作分区键, 因为这样仅能存放3年数据, 往往使用月份作为分区键. 纯range分区的表一般不能太大, 否则当前月的分区往往会是hot分区, 影响性能.
3. hash + range 分区: 对于较大的事实表, 推荐使用 hash+range分区方法, 使用选择性强的字段作为hash键, 使用月份作为range分区键, 这样能避免纯range分区表的缺点.

---==============================
Kudu的一些限制:
---==============================

压缩前的字段长度应该小于64KB.
集群最多100 个tablet server.
每个tablet server推荐最多1000个tablet(含replica).
每个tablet server最多管理4TB的数据(压缩后的数据量, 含replica).
每个tablet server存储的数据最大为4T.
每个tablet 最好控制在20GB内, 1000万笔以内.
每个表在单个tablet server上最多60个partition(含replica). 按照20个tablet server算(replica三份), 一个表最多400个partition; 按照50个tablet server算, 一个表最多1000个partition.
更多信息: https://kudu.apache.org/docs/known_issues.html



---==============================
SQL on Kudu 工具:
---==============================
impala 操作kudu的文档: https://impala.apache.org/docs/build/html/topics/impala_kudu.html
impala 或 Spark SQL

虽说 Kudu 提供了高效的单行插入和更新功能, 但Impala还是侧重数据分析, 并没有对单行Insert/Update进行优化, 所以如果有大量小型DML的操作, 推荐使用Kudu API而不是Impala SQL方式. 当一定要使用Impala SQL时, 最好采用批处理方式, 即一个Insert 语句一次性插入多行数据.  测试Insert 写到Vertica小集群速度近为7000笔/秒, 而Impala(通过Hive JDBC Driver)写到Kudu仅仅70笔/秒, Impala(通过Impala JDBC Driver)写到Kudu仅仅40笔/秒, 


---==============================
Kudu 建表
---==============================
Kudu的数据类型:
BOOL, INT8, INT16, INT32, BIGINT, INT64, FLOAT, DOUBLE, STRING, BINARY, TIMESTAMP.
但 DECIMAL,CHAR,VARCHAR,DATE 和 ARRAY 等复杂类型不受支持.

Impala 的数据类型:
BIGINT,BOOLEAN,CHAR,DECIMAL,DOUBLE,FLOAT,INT,REAL,SMALLINT,STRING,TIMESTAMP,TINYINT,VARCHAR
还有Array,Struct,Map等复杂类型

当我们使用Impala创建Kudu格式的表, 必须使用kudu数据类型建表, 而不能使用impala支持的数据类型.

kudu 表文件是列式数据格式, 另外kudu允许为每个字段指定不同的压缩的encoding 算法和压缩算法.


强烈推荐 impalad 服务加上启动参数 -kudu_master_hosts, 这样每个impala定义kudu表的DDL语句中, 就不需要在TBLPROPERTIES中指定kudu.master_addresses属性.
kudu.table_name 表属性, 可以理解为在kudu中的表名(物理表名), 而建表语句的表名可以理解为impala catalog中的名称(逻辑名称).

drop table if exists kudu_somedb.kudu_test ;
CREATE TABLE kudu_somedb.kudu_test
(

ActionDate timestamp ENCODING BIT_SHUFFLE COMPRESSION LZ4,
UID string ENCODING PLAIN_ENCODING COMPRESSION SNAPPY,
Operation string ENCODING DICT_ENCODING COMPRESSION SNAPPY,
EUTIME timestamp ENCODING BIT_SHUFFLE COMPRESSION LZ4,-- default now(),
EID_kudu string ENCODING PLAIN_ENCODING COMPRESSION SNAPPY,-- concat(cast(unix_timestamp() as string),uuid()) ,
PRIMARY KEY (ActionDate,Uid,Operation)
)
PARTITION BY HASH (Uid) PARTITIONS 6 ,
RANGE (ActionDate) (
PARTITION '2018-03-16' <= VALUES < '2018-03-17',
PARTITION '2018-03-17' <= VALUES < '2018-03-18',
PARTITION '2018-03-18' <= VALUES < '2018-03-19',
PARTITION '2018-03-19' <= VALUES < '2018-03-20'
)

STORED AS KUDU
TBLPROPERTIES (
'kudu.table_name' = 'somedb.kudu_test',
'kudu.master_addresses' = '10.205.6.121:7051,10.205.6.122:7051,10.205.7.134:7051' ,
'kudu.num_tablet_replicas' = '3'
);

下面是增加和删除 range 分区的语法:
alter table kudu_somedb.kudu_test add  IF NOT EXISTS range partition '2018-03-20' <= VALUES < '2018-03-21' ;
alter table kudu_somedb.kudu_test drop  IF EXISTS  range partition '2018-03-20' <= VALUES < '2018-03-21' ;

-- Create select 方式建表语法, 注意这里的primary key()中的字段名必须使用小写字母.
create table temptable.test
Primary key(id,name)
STORED AS KUDU
TBLPROPERTIES (
'kudu.table_name' = 'temptable.test',
'kudu.master_addresses' = '10.205.6.121:7051,10.205.6.122:7051,10.205.7.134:7051',
'kudu.num_tablet_replicas' = '1'
)
as
select 1 id, 'name' name, 20 ag
;

---==============================
善用 if exists 语句
---==============================
很多时候我们需要在程序中创建临时表, 逻辑一般是, 如果临时表存在先drop, 然后新建这个临时表. 因为是在程序中, 所以删除动作不希望报错, 这时候可以在drop table后加上 if exists. 下面用视图说明这个技巧.

drop view if exists kudu_guba.v1
;

create view if not exists kudu_guba.v1
as
select 1 a
;

---==============================
蛋疼的大小写规则:
---==============================
1. 使用Impala insert SQL时候, 主键字段名必须是小写; 而update/delete无限制.
2. 使用 impala 创建kudu表, kudu.table_name名称会保持DDL语句中的大小写, 字段名会自动转成小写字母.
3. 使用 kudu API 访问表时候, 表名称大小写敏感的(必须按照kudu.table_name名的写法), 字段名也是大小写敏感的(也就是必须使用小写字母).
4. 采用Create select 方式建表语法, 注意这里的primary key()中的字段名必须使用小写字母.
5. 普通的DDL建表, primary key()中的字段名大小写都可以.

原文地址:https://www.cnblogs.com/harrychinese/p/kudu_basics.html