标签:
Apache Kylin定位是大数据量的秒级SQL查询引擎,原理是通过预计算所有可能的维度组合存储在Hbase中,查询时解析SQL获取维度和度量信息,然后再从hbase中扫描获取数据返回,个人认为Kylin最强大的地方在于实现了SQL引擎,如果使用自定义的格式化查询语言也可以完成相应的数据访问操作,无非是指定查询的维度、度量、聚合函数、过滤条件,排序列等等。
但是这种描述较之于SQL太弱了,SQL很灵活的将一些复杂的语义转换,例如kylin中不支持select xxx where xx in (selext xxx)的语句,但是可以通过子查询join的方式实现,这样的局限导致了大量复杂的SQL。除此之外,由于Kylin中可能存在某几个维度的cardinality比较大,当使用该列进行group by的时候会导致需要从hbase中读取大量的记录进行聚合运算甚至排序。Kylin中SQL引擎使用的是Calcite进行SQL解析、优化和部分算子的运算,Calcite的计算是完全基于内存的,所以当Kylin中一个查询需要从hbase中获取大量记录的情况下,内存逐渐会成为瓶颈。
OLAP查询往往是基于历史数据的,历史数据最重要的特性是不可变的,即便偶尔由于程序BUG导致数据需要修复,对这种不经常会变化的数据的查询,并且每一个查询可能消耗大量资源的情况下,缓存是最常用也是最有效的提升性能的办法。
Kylin并不是不对查询结果进行缓存的,对于每一个查询会根据该查询扫描的记录总数是否超过阀值(以此判断是否值的缓存)判断是否缓存结果。但是这部分缓存是基于本机内存的,并且是实例间不可共享的,而一般Kylin查询服务器的架构是多个独立的服务器通过前面的负载均衡器进行请求转发,如下图,所以这部分缓存是无法共享的。由此目前Kylin的缓存机制存在一下几种弊端:
了解了Kylin当前缓存的情况,针对以上前两点进行改进,最直接的方案便是将缓存移至外部缓存,首选的key-value缓存当然是redis,如下图,根据现在缓存的设计,可以将查询的SQL和所在的project作为key,查询结果以及一些查询中的统计信息作为value缓存。但是第三点需要针对kylin的实现设计出具体的自动过期方案。
Kylin是基于预计算的,计算的是所有定义的维度组合的聚合结果(SUM、COUNT等),既然需要聚合肯定需要一段数据量的积累,Kylin通过在创建Cube时定义一个或者两个(新版本,支持分钟级别粒度)分区字段,根据这个字段来获取每次预计算的输入数据区间,Kylin中将每一个区间计算的结果称之为一个Segment,预计算的结果存储在hbase的一个表中。通常情况下这个分区字段对应hive中的分区字段,以天为例子,每次预计算一天的数据。这个过程称之为build。
除了build这种每个时间区间向前或者向后的新数据计算,还存在两种对已完成计算数据的处理方式。第一种称之为Refresh,当某个数据区间的原始数据(hive中)发生变化时,预计算的结果就会出现不一致,因此需要对这个区间的segment进行刷新,即重新计算。第二种称之为Merge,由于每一个输入区间对应着一个Segment,结果存储在一个htable中,久而久之就会出现大量的htable,如果一次查询涉及的时间跨度比较久会导致对很多表的扫描,性能下降,因此可以通过将多个segment合并成一个大的segment优化。但是merge不会对现有数据进行任何改变。
说句题外话,在kylin中可以设置merge的时间区间,默认是7、28,表示每当build了前一天的数据就会自动进行一个merge,将这7天的数据放到一个segment中,当最近28天的数据计算完成之后再次出发merge,以减小扫描的htable数量。但是对于经常需要refresh的数据就不能这样设置了,因为一旦合并之后,刷新就需要将整个合并之后的segment进行刷新,这无疑是浪费的。
说完了数据计算,接下来讲一下这部分预计算的数据是如何被使用的,Calcite完成SQL的解析并回调kylin的回调函数来完成每一个算子参数的记录,这里我们只需要关心查询是如何定位到扫描的htable的。当一个查询中如果涉及到建cube中使用的分区字段,由于分区字段一般是维度字段,否则每次扫描都需要扫描全部的分区。那么这里涉及表示该字段出现在where子句或者group by子句中,下面分几种情况分别讨论(假设分区字段为dt):
既然数据计算和查询结果有了这种关系,那么就可以利用这种关系解决缓存中最困难的一个问题——缓存过期,即现状中的问题3,但是查询通常是非常复杂的,例如多个子查询join、多个子查询union之类的,并不能轻易地获取group by和where子句的内容,幸运的是,在Kylin每一个查询过程中会将本次查询或者每一个子查询设计的信息保存在OLAPContext对象中,一次查询可能生成多个OLAPContext对象,它们被保存在一个stack中,并且这个stack是线程局部变量,因此可以通过遍历这个stack中内容获取本次查询所有信息,包括使用的cube、group by哪些列、所有的过滤条件等。
//首先检查key是否存在
if exists key
value = get key
//如果key存在可能存在两种情况:已经有结果或者正在查询
if value is running
waiting for finish(loop and check) //轮询是个更好的办法,效率稍低
else
//即使有结果也可能存在结果已经失效的情况
if value is valid
return value
else
//为了保证互斥,使用setnx语句设置
set if exist the value is running
if return true run the query
else wait for finish(loop and check)
else
//不存在则直接表示running
set if exist the value is running
if return true run the query
eles waiting for finish(loop and check)
本文基于分析Kylin中现有的缓存策略,提出一种使用外部缓存的方案,接下来可以基于此进行编码和测试,希望能够取得较好的效果。
标签:
原文地址:http://blog.csdn.net/yu616568/article/details/51870953