关于Kylin结果缓存的思考

由来

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的缓存机制存在一下几种弊端:

  • 缓存在本机内存,对查询最需要的内存资源就是一种消耗。
  • 机器间无法共享,导致查询可能时快时慢,并且造成不必要的资源浪费。
  • 无法自动过期,build完成之后需要手动清理,否则结果可能出现错误。例如查询select count(1) from xxx;当一个新的segment build完成之前和之后两次查询的结果是相同的,明显第二次是使用缓存的,并且build成功之后并没有将缓存清理,需要手动清除。

改进

了解了Kylin当前缓存的情况,针对以上前两点进行改进,最直接的方案便是将缓存移至外部缓存,首选的key-value缓存当然是redis,如下图,根据现在缓存的设计,可以将查询的SQL和所在的project作为key,查询结果以及一些查询中的统计信息作为value缓存。但是第三点需要针对kylin的实现设计出具体的自动过期方案。

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):

  • SQL中没有分区字段,例如select country, count(1) from table group by country,这种查询需要统计的数据设计全部预计算的时间区间和将来计算的时间区间,所以它需要扫描所有的htable才能完成。这种情况下build和refresh计算都会使得结果发生变化。
  • SQL中where子句使用了分区字段,例如select country, count(1) from table where dt >= ‘2016-01-01’ and dt < ‘2016-02-01’ 这种情况下可以根据时间区间确定只扫描部分htable。这种情况只有这段时间区间的refresh操作会改变查询结果,如果过滤的时间区间包含一个未来的时间,build操作会导致结果发生改变。
  • SQL中group by中使用了分区字段,这种情况下类似于1,需要扫描全部的htable以获取结果。
  • SQL中group by和where字段中都使用了分区字段,这种情况类似于2,毕竟SQL执行时首先执行where再进行group by的。

分析

既然数据计算和查询结果有了这种关系,那么就可以利用这种关系解决缓存中最困难的一个问题——缓存过期,即现状中的问题3,但是查询通常是非常复杂的,例如多个子查询join、多个子查询union之类的,并不能轻易地获取group by和where子句的内容,幸运的是,在Kylin每一个查询过程中会将本次查询或者每一个子查询设计的信息保存在OLAPContext对象中,一次查询可能生成多个OLAPContext对象,它们被保存在一个stack中,并且这个stack是线程局部变量,因此可以通过遍历这个stack中内容获取本次查询所有信息,包括使用的cube、group by哪些列、所有的过滤条件等。

缓存设计

  • 缓存的key:查询请求,包括SQL、project、limit、offset、isPartial参数,将该信息
  • 缓存的value:查询返回结果,包括结果数据和元数据,以及其他统计信息,包括使用了哪些cube,每一个cube的时间分区过滤条件是什么。以及缓存添加的时间。
  • 添加缓存:查询获取结果之后将key-value对加入到缓存中,Kylin原生的缓存会缓存异常,这里不进行缓存,主要是由于Kylin内部的异常主要分为三类:AccessDeny、语法错误和其它数据访问异常,第一种原生的缓存也不会进行存储,第二种异常查询一下元数据就可以判断也没必要缓存;第三种则是系统异常,缓存可能会导致修复之后的的查询继续出现错误。因此缓存中只存储执行成功的查询。
  • 缓存过期时间:可配置,其实这个值设置无所谓,只是为了将长时间不查询的key删除罢了,可以设置较久。
  • 缓存失效:根据上面的分析,数据计算中build和refresh都可能对缓存中的结果产生变化,因此这部分缓存需要自动失效,检查缓存失效的方式由两种,一种主动式,在每次数据计算任务结束之后遍历全部的缓存值判断是否失效,另外一种是被动式,真正读取缓存的时候再去判断它是否已经失效,不被读取的已失效缓存会随着过期时间的到达而自动删除。前者需要redis的keys命令的支持,后者只是在读取value之后根据上一次执行的统计信息执行单个记录的判断,很明显这种情况下被动的方式更加合适。判断的逻辑如下:当每一个数据计算任务完成之后(无论是build、refresh还是merge)都会记录这个segment的最后更新时间(更新cube元数据的时候记录),在查找缓存对的时候首先根据key获取缓存内容,即缓存的value,然后查看每一个使用的cube的分区字段过滤区间,查看该区间的segment是否存在最后更新时间大于缓存添加时间的。如果存在说明这段数据已经被更新,则释放该条缓存,否则说明数据没有被更新,可以继续使用当前缓存作为结果。

条件和说明

  • 一个相同的SQL两次查询使用相同的cube。
  • 每一个segment的最后更新时间总是有效的,不会出现错误的时间。

    * 每一个查询服务器的元数据是相同的,实际情况下不同机器之间可能存在短时间的误差。

  • 只统计第一层时间分区(天)的过滤周期,如果where条件中没有出现过滤(情况1,3)则说明过滤区间为[0, Long.MAX_VALUE]
  • 虽然merge不会导致数据的修改,但是merge之前可能出现其中某一个segment被build或者refresh,而这部分信息无从获取,所以merge需要和refresh相同对待。

优化

  • 缓存作为锁,相同的查询可以通过外部缓存顺序化,如果缓存可以命中则直接从缓存中获取结果,否则首先将该查询作为key加入,value设置为isRunning表示加锁,后面相同的查询等待之前查询结果的返回,可以通过订阅-发布模式或者轮询isRunning标识的方式判断第一次查询是否执行完成,放置同时执行多个相同的查询。
  • SQL标准化,查询的SQL可能存在只多一个空格的情况,虽然语义是完全一样的,但是作为key则是不相同的,可以通过修剪SQL中多余的空格(字符串中的除外)完成SQL写法的标准化,更充分的利用缓存。
  • 只缓存执行成功的查询结果。不缓存异常。
  • 手动清除异常接口。

伪代码

//首先检查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中现有的缓存策略,提出一种使用外部缓存的方案,接下来可以基于此进行编码和测试,希望能够取得较好的效果。

时间: 2024-10-26 04:56:13

关于Kylin结果缓存的思考的相关文章

讨论过后而引发对EF 6.x和EF Core查询缓存的思考

前言 最近将RabbitMQ正式封装引入到.NET Core 2.0项目当中,之前从未接触过这个高大上的东东跟着老大学习中,其中收获不少,本打算再看看RabbitMQ有时间写写,回来后和何镇汐大哥探讨了一点关于EF和EF Core的内容,于是乎本文就出来了.EF 6.x和EF Core中的查询缓存想必大家都有耳闻或者了解,从数据库中查询出来的实体会形成快照在内存中Copy一份且被上下文跟踪,接下来我们要讲的内容就是这个,我们来看看. EF 6.x和EF Core查询缓存思考 首先我利用EF Co

关于服务器缓存的思考

我们在开发中,经常会用到各种缓存,比如Session.Application.HttpRuntime.Cache.Redis.Memcached.MongoDB.Riak等.而一般项目中使用缓存时,都是比较初级的,大多都是常见的Key-Value方式,通过依赖.时间.同步更新或直接删除方法来管理缓存的过期.当然网上对于缓存的介绍绝大部分都是这方面的,而对于多级缓存.缓存与缓存相互关联.表记录与多缓存关联.后端缓存与前端页面缓存关联.缓存名称动态生成的缓存与其他缓存联动处理.频繁更新的缓存与其他缓

06 HTTP协议缓存控制

一:HTTP协议缓存控制 第1次请求时 200 ok 第2次请求时 304 Not Modified 未修改状态 解释: 在网络上,有一些缓存服务器,另, 浏览器自身也有缓存功能. 当我们第一次某图片时,正常下载图片,返回值200 基于一个前提--图片不会经常改动, 服务器在返回200的同时,还返回该图片的”签名”-- Etag  ,(签名可以理解图片的”指纹”). 当浏览再次访问该图片时,去服务器校验”指纹”, 如果图片没有变化,直接使用缓存中的图片,这样减轻了服务器负担. 二:抓包观察分析

电子笔记本的思考(1)(ver0.2)

章节:电子笔记本的思考(1)   陶哲轩在<解题·成长·快乐——陶哲轩教你学数学>中着重强调,用纸笔来“缓存”思维对于数学解题的重要性: 用选定的符号表达你所知道的信息,并画一个示意图.把所有信息写在纸上,有三点好处: a)解题时,便于思考: b)陷入困境时,可以盯着纸进行思考: c)把知道的写下来,这个过程本身可以激发新的灵感和联想. 但请注意,不要写下过多的信息和细节.一种折中的办法是着重强调那些你认为最有用的事实,而把那些令人怀疑的.冗杂的或异想天开的想法写在另一张草稿纸上. 同样地,刘

cdh5.14.2中集成安装kylin与使用测试

cdh5.14.2中集成安装kylin与使用测试 标签(空格分隔): 大数据平台构建 一:kylin 简介 二:安装配置kylin 三:kylin 运行实例 一:kylin 简介 Apache Kylin?是一个开源的分布式分析引擎,提供Hadoop/Spark之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay Inc. 开发并贡献至开源社区.它能在亚秒内查询巨大的Hive表. kylin 软件下载: 社区版kylin下载地址:https://archive.apa

对于高并发的一些思考

夜宵时的思考 刚刚排错了一个小时,终于搞定了,奖励自己一碗泡面,在等微波炉前等待的时候想了挺多事的. 并发似乎是所有程序员的噩梦,也是所有程序员的"粮食",如果计算机不支持并发,世界上的Error Log应该能少一半,同时程序员的"魔力"也少了三分. 所谓的秒杀系统,其实是一种泛指,代表着多并发,高可用系统.为什么说至今没有一个类似Spring一样的框架,能在并发领域脱颖而出呢,因为每个领域,各个场景的情况不一样,每个解决并发的方案,脱离了实际,就没了价值. 面熟了

WebService推送数据,数据结构应该怎样定义?

存放在Session有一些弊端,不能实时更新.server压力增大等... 要求:将从BO拿回来的数据存放在UI Cache里面,数据库更新了就通过RemoveCallback "告诉"UI Cache.实现更新. 环境:BO 提供一个WebService给UI取数据.UI也有一个WebService,提供给BO 通知UI更新数据.数据结构的原生类始终在BO层. 本来是想在数据库Update 后,在BO将Cache的数据推送至UI Cache,但当中遇到了自己解决不了的问题: 数据结构

一文教你迅速解决分布式事务 XA 一致性问题

欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯云数据库团队 近日,腾讯云发布了分布式数据库解决方案(DCDB),其最明显的特性之一就是提供了高于开源分布式事务XA的性能.大型业务系统有着用户多.并发高的特点,在这方面,集中式数据库(单机数据库)的性能很难支持,因此主流的互联网公司往往采用分布式(架构)数据库,物理上利用更多的低端设备,逻辑上对大表水平拆分支撑业务的需要. 虽然分布式数据库能解决性能难题,但事务一致性(Consistency)的问题,却很难在分布式数据库上

WEB性能优化【资料】

为了解决近期项目遇到的性能瓶颈,花费不少功夫恶补了web性能的相关优化方案,整理了一些资料,分享给大家. 博客 网页性能管理详解 - 阮一峰的网络日志 页面性能优化的利器 - Timeline - 云加社区 - 博客园 什么是CDN,为什么你的网站需要它? 网站开发动静分离实践 - BruceFeng 网页性能优化 - 小方. - 博客园 16毫秒的优化--Web前端性能优化的微观分析--O'Reilly Velocity China 2013 Web 性能与运维大会 全新Chrome Devt