文章目录


基于HTAP方式

kudu是基于hbase-hdfs之间,满足高并发的随机读写,兼顾大规模分析处理,具有OLTP以及OLAP特征,因此是典型的HTAP(在线事务处理/在线分析处理混合模式)

早期

KUDU(三)kudu的模式设计_数据

由于将OLTP以及OLAP拆分,事务性应用和分析型应用分开,但是分析型应用无法获取最新数据,OLTP横向扩展性不足,维护一套系统复杂度很高

2.Lambda架构

KUDU(三)kudu的模式设计_多级_02

Lambda架构将工作负载分为实时层和批处理层,我们是用实施层检索和分析最新的数据,使用批处理层分析历史数据。这样会带来两个特别的问题,两套系统、两份代码,开发、运维、测试都很复杂,整个处理链条中有一处出现问题就需要重跑数据

3.Kudu设计模式

非常易于跟其他组件整合以支持SQL或者进行分布式计算,非常利于从其他关系型数据库迁移数据,数据的读写均匀分散到每个Tablet Server,以充分挖掘集群的潜力(受分区设计影响),扫描时读取查询所需的最少数据量(主要受主键设计影响,但分区设计也会起到重要作用)

好的shema设计取决于要处理数据的特征、对数据的操作以及集群的拓扑结构。Schema设计对于kudu集群性能最大化来说是最重要的事情。shema设计包含三大块:

列设计

每个列选择合适的类型、编码和压缩方式

Kudu的每个列都必须指定明确的数据类型的,非主键可以为null,目前支持的数据类型如下:

类型

说明

bool

布尔

int8

8位有符号整数

int16

16位有符号整数

int32

32位有符号整数

int64

64位有符号整数

unixtime_micros

自1970-01-01T00:00:00Z.以来的Unix 64位微秒

float

单精度(32位) IEEE-754浮点数,非精确的

double

双精度(64位)IEEE-754浮点数,非精确的

double

双精度(64位)IEEE-754浮点数,非精确的

decimal(m,n)

精确的十进制数字,参数化,m代表精度,n代表刻度

string

UTF-8编码字符串,最多64KB未压缩

binary

二进制,最多64KB未压缩

Kudu利用强类型列和列式存储格式来提供高效的编码和序列化。为了充分利用这些功能,应将列指定为适当的类型,而不是使用字符串或二进制列来模拟“无模式”表。除了编码之外,Kudu还允许在每列的基础上指定压缩

同HBase不同,kudu没有提供version和timestamp来跟踪行的变化,如果需要的话,需要自行设计一列

Decimal类型

decimal是具有固定刻度和精度的十进制数字类型,适合于财务等算术运算,(float与double不精确有舍入行为)。decimal类型对于大于int64的整数和主键中具有小数值的情况也很有用

精度:表示该列可以表示的总位数,与小数点的位置无关。此值必须介于1和38之间,并且没有默认值。例如,精度为4表示最大值为9999的整数值,或者表示最多99.99带有两个小数位值。您还可以表示相应的负值,而不用对精度进行任何更改。例如,-9999到9999的范围仍然只需要4的精度。

刻度:表示小数位数。该值必须介于0和精度之间。刻度为0会产生整数值,没有小数部分。如果

精度和刻度相等,则所有数字都在小数点后面。例如,精度和刻度等于3的小数可以表示介于-0.999和0.999之间的值

decimal列类型编码默认

性能考虑:Kudu将每个值存储在尽可能少的字节中,具体取决于decimal指定的精度,。因此,不建议为了方便使用最高精度。这样做可能会对性能,内存和存储产生负面影响

在编码和压缩之前:

精度为9或更小的十进制值以4个字节存储。

精度为10到18的十进制值以8个字节存储。

精度大于18的十进制值以16个字节存储。

alter命令不能修改的decimal列的精度和刻度

列编码

数据类型-编码对照表

KUDU(三)kudu的模式设计_主键_03

编码

Plain


数据以其自然格式存储


Bitshuffle


重新排列一个值块以存储每个值的最高有效位,然后是第二个最高有效位,依此类推。最后,结果进行LZ4压缩。如果值重复的比较多,或者按主键排序时值的变化很小,Bitshuffle编码是一个不错的选择。


run length


对连续的重复值采用压缩存储,主要是通过只存储值和个数。该编码对按主键排序时具有许多连续重复值的列有效。


dictionary


创建一个字典存放所有的值,每个列值使用索引进行编码存储。如果值的个数较少,这种方式比较有效。如果RowSet的列值由于唯一值的数量过多而无法
压缩,则Kudu将透明地退回到Plain编码。这在flush期间进行评估计算


prefix


在连续的列值中对公共前缀进行压缩。对于有公共前缀的值或主键的第一列有效,因为tablet中的行是通过对主键排序并存储的。


列压缩

Kudu允许列使用LZ4、Snappy或zlib压缩编解码器进行压缩。如果减少存储空间比扫描性能更重要,请考虑使用压缩,每个数据集的压缩方式都不同,但一般来说LZ4是性能最佳的编解码器,而zlib空间压缩比最大。

默认情况下,使用BitLuffle编码的列固有地使用LZ4压缩进行压缩(不建议修改),其他编码默认不进行压缩。

主键设计

每个Kudu表必须声明由一列或多列组成的主键。与RDBMS主键一样,Kudu主键强制执行唯一性,约束。尝试插入具有与现有行相同的主键值的行将导致重复键错误。主键列必须是非可空的,并且不可以是boolean,float或double类型。表创建指定主键后,主键中的列集就不能更改。

与RDBMS不同,Kudu不提供列的自增,因此应用程序必须提供完整的主键,删除和更新时必须指定完整主键。Kudu本身不支持范围删除或更新。即都是通过主键完成操作。

主键值无法修改,但是可以删除后重新插入来变相实现。

主键索引

Kudu中只有主键才会被索引,没有二级索引。

扫描Kudu行时,在主键列上使用等于或范围谓词来找行性能最佳,非主键列在数据量大的情况下性能不好,建议把查询用到的列尽量设置为主键列

主键索引优化可以使扫描跳过个别Tablet,要想使扫描操作跳过很多Tablet需要借助分区设计。

主键索引是有序的,如果主键有多列则按照组合排序,即先按第一列排序,第一列一样则按第二列,排序,以此类推

时间戳主键回填问题

回填场景

kudu每次插入数据的时候会根据主键索引查找主键,判断主键是否存在来决定插入还是报错

1.实时插入


数据产生立马就从数据源采集然后入库到Kudu,及时考虑有一段时间的延迟时间戳的范围也很小。这就意味着只有很小范围的主键是“热”的,它们会被频繁使用因此会被缓存在内存里,检查主键唯一性的操作会非常快,入库速度可以轻松达到百万条/秒。


2.导入历史数据(回填)


有些场景下我们需要将历史数据一次性导入Kudu,这个时间跨度可能很大,每插入一行都可能命中主键索引的冷数据,该部分主键索引存储在磁盘上,磁盘寻道和IO读写将会瞬间暴增,入库速度极有可能降低到数千条/秒


如何解决回填性能问题


使主键更具可压缩性主键压缩更小,则相同内存能够被缓存的主键索引就更多,从而减少磁盘IO
使用SSD,随机寻道要比机械旋转磁盘快几个数量级,更改主键结构,以使回填写入命中连续的主键范围


分区设计

kudu中的表被分成很多tablet分布在多个tserver上,每一行属于一个tablet,行划分到哪个tablet由分区决定,分区是在表创建期间设置的。

写入频繁时,考虑将写入动作平衡到所有tablet之间能够有效降低单个tablet的压力,对于小范围扫描操作比较多的情况,如果所扫描的数据都为一个tablet上则可以提高性能。

kudu没有默认分区,建议读写都较重的table可以设置和tserver服务器数量相同的分区数。

kudu提供两种类型的分区:范围分区和哈希分区。表可以有多级分区,组合使用范围和哈希或者多个哈希组合使用。

分区设计好坏由场景+三个维度去考量:


1.是否是读热点
2,是否写热点
3.Tablet可扩展性


范围分区

Kudu允许在运行时动态添加和删除范围分区,而不会影响其他分区的可用性。删除分区将删除属于该分区的平板电脑以及其中包含的数据,后续插入到已删除的分区中将失败。可以添加新分区,但它们不得与任何现有范围分区重叠。Kudu允许在单个事务更改表操作中删除和添加任意数量的范围分区。

动态添加和删除范围分区对于时间序列特别有用。随着时间的推移,可以添加范围分区以覆盖即将到来

的时间范围。例如,存储事件日志的表可以在每个月开始之前添加月份分区,以便保存即将发生的事件,可以删除旧范围分区,根据需要有效的删除历史数据

哈希分区

哈希分区按哈希值将行分配到存储桶中的一个。在单级散列分区表中,每个桶只对应一个tablet,在表创建期间设置桶的数量。通常,主键列用作要散列的列,但与范围分区一样,可以使用主键列的任何子集。

当不需要对表进行有序访问时,散列分区是一种有效的策略。散列分区对于在tablet之间随机写入非常有效,这有助于缓解热点和不均匀的tablet大小。

多级分区

Kudu允许表在单个表上组合多个级别的分区。零个或多个哈希分区可以与范围分区组合。除了各个分区类型的约束之外,多级分区的唯一附加约束是多级哈希分区不能散列相同的列。

如果使用正确,多级分区可以保留各个分区类型的好处,同时减少每个分区类型的缺点。多级分区表中的tablet总数是每个级别中分区数的乘积。

修剪分区

当通过扫描条件能够完全确定分区的时候,kudu就会自动跳过整个分区的扫描要确定哈希分区,扫描条件必须包含每个哈希列的等值判定条件。多级分区表的扫描可以单独利用每一级的分区界定。

分区案例

CREATE TABLE metrics (
host STRING NOT NULL, -- 主机
metric STRING NOT NULL, -- 度量指标
time INT64 NOT NULL, -- 时间戳
value DOUBLE NOT NULL, -- 值
PRIMARY KEY (host, metric, time), -- 主键
);
  1. 采用范围分区
    对time列进行范围分区,假如每年对应一个分区,数据包括2014,2015和2016,至少可以使用两种分区方式:有界范围分区和无界范围分区。但是如果后续时间不断增大,导致一个数据写入最后一个tablet中,导致tablet太大,无法容纳单个tablet
    2.采用哈希分区
    host和 metric列上的哈希分区为四个桶。与之前的范围分区示例不同,此分区策略将均匀地在表中的所有tablet上进行写入,这有助于整体写入吞吐量。扫描特定host和metric可以通过指定等式来利用分区修剪,将扫描的tablet数量减少到一个。使用纯哈希分区策略时要注意的一个问题是,随着越来越多的数据插入表中,tablet可能会无限增长。最终tablet将变得太大,无法容纳单个tablet服务器。
    3.哈希+范围组合分区
    哈希分区可以最大限度地提高写入吞吐量,而范围分区可以避免无限制的tablets增长问题。这两种策略都可以利用分区修剪来优化不同场景下的扫描。使用多级分区,可以将这两种策略结合起来,以获得两者的好处,同时最大限度地减少每种策略的缺点
    4.双哈希组合分区
    要没有共同的哈希列,Kudu就可以在同一个表中支持任意数量的散列分区级别。在上面的示例中,表被host散列为4个桶,并将散列分区metric为3个桶,产生12个tablet。尽管在使用此策略时,写入将倾向于在所有Tablet中传播,但与多个独立列上的散列分区相比,它更容易出现热点,因为单个主机或度量标准的所有值将始终属于单个tablet。扫描可以分别利用host和metric列上的等式谓词来修剪分区。
    多级散列分区也可以与范围分区相结合,从逻辑上增加了分区的另一个维度。

模式变更

Kudu1.10.0能够支持的模式更改:


1.重命名表
2.重命名主键列
3.重命名,添加或删除非主键列
4,添加和删除范围分区


局限性

Kudu目前有一些已知的局限性可能会影响到架构设计

列数


默认情况下,Kudu不允许创建超过300列的表。我们建议使用较少列的架构设计以获得最佳性能。


cell大小


在编码或压缩之前,单个单元不得大于64KB。在Kudu完成内部复合密钥编码之后,构成复合密钥的单元限制为总共16KB。插入不符合这些限制的行将导致错误返回给客户端


行大小


虽然单个单元可能高达64KB,而Kudu最多支持300列,但建议单行不要大于几百KB。


有效标识符


表名和列名等标识符必须是有效的UTF-8序列且不超过256个字节。


主键值不可变


Kudu不允许更新主键列得值。


不可更改主键列


Kudu不允许您在创建表后修改主键列


不可更改的分区


除了添加或删除范围分区之外,Kudu不允许您在创建后更改表的分区方式。


不可改变的列类型


Kudu不允许更改列的数据类型。


分区拆分


创建表后,无法拆分或合并分区.


总结

分区


一般哈希+范围分区组合在一起,只有范围分区的情况极少,因为不能避免写热点,除非有哈希分区,典型的例子就是时间序列。


大对象


string, binary在未压缩之前不能大于64K,虽然有配置可以调大这个值,但千万不要这么做,避免出现未知错误。
如果确实要存储超过64K的JSON、XML大对象,有两个办法:
1.先对json、XML压缩再存储,编码方式设置为Plain且关闭压缩;
2.如果远远超过64K,则可以把对象保存到HBase或者HDSF中,然后再去Kudu这边保存该对象的"外键",即HBase的Rowkey、HDFS的路径。


decimal(十进制数)


Kudu1.7开始的版本推荐用decimal代替float和double,且可以出现在主键中(float和double就不可以),查询性能更佳,且更适合算数运算


不重复的字符串


如果一个表的主键只有一个string列推荐采用Prefix压缩;如果是多个string列构成主键,则推荐Plain编码+LZ4压缩


压缩


bitshuffle编码的列会自动使用LZ4压缩进行压缩,其他编码的列可以根据情况选择是否采用LZ4压缩,LZ4通常比Snappy快。


对象命名


表名和列名都小写可以避免混乱(Impala不区分大小写,API操作区分大小写)。表名必须唯一,如果在Impala中创建内部Kudu表,则表名会默认加上前缀,如impala:default.person


列的数量


列数不能超过300个,如果你在迁移数据时确实有300个以上的列,则可以拆分为多个表,每个表都要保留主键,以便可以通过视图将它们合并在一起。