【开发实践】思科大数据团队如何将 Kylin 吞吐率提高 5 倍?

本文作者李宗伟,系思科工程师,是思科大数据架构团队成员,目前主要负责OLAP平台搭建及客户业务报表系统的研发。

我们是来自于Cisco大数据团队的开发小组,其中一项业务是为客户提供BI报表:客户会登录报表系统查询Cisco业务的使用情况,也会将它作为计费账单的参考,这些报表对客户而言是非常重要的业务功能。

这些报表数据来自于多张Oracle数据库中的表,单张表单月的数据量在亿级,也就是说,如果客户想查询一年的报表,至少需要对十亿到二十亿的数据做聚合查询等操作,同时需要在很短的时间内得出结果。在我们的调研选型过程中发现了Apache Kylin这一基于预计算思想实现的海量数据分布式预处理引擎,它的一个亮点就是可以实现超大数据集的亚秒级查询。

经过初步的数据模拟测试,我们发现Kylin确实可以在1秒内反馈十亿数据量的聚合查询结果,很好地满足了我们的业务需求。但是测试并没有到此为止,我们展示给客户的报表页面包含了15张图表,每一张图表的展示BI系统都会异步的发送REST API请求到Kylin查询数据,基于产线规模分析,如果短时间内有20个客户(这个数据很保守)在单节点上同时查询报表,会触发15*20 = 300个请求,那么Kylin在短时间内的并发响应性能就是我们需要测试的对象。

01 初步测试阶段

前提

为了降低网络开销对并发性能测试结果的影响,我们将并发测试工具与Kylin部署在相同的网络环境内。

测试工具

除了选用传统的压力测试工具Apache JMeter外,我们还使用了另一款开源工具Gatlin (https://gatling.io/) 测试相同的用例,对比排除测试工具的影响。

测试策略

通过累加并发线程数来模拟不同量级的用户请求,观察60秒内平均响应时间,确定Kylin的并发响应瓶颈,同时也需要观察最大响应时间和成功率。为了确保不被缓存影响,整个测试我们都关闭了Kylin的query cache,确保每个查询都被发送到底层执行。

测试结果

根据结果绘出趋势图:

测试结论

当并发数达到75时,Kylin的查询响应数达到峰值90,即使进一步提高并发数,单秒的查询响应数也并没有提高。单个节点每秒90的并发查询响应数只能满足此场景中90/15=6个客户同时查询报表,考虑到集群内Kylin query node的数量为3,每秒18个客户的查询能力也远远不能满足我们的业务需求。

02 定位问题

通过对Kylin Query模块代码的阅读和分析,我们了解到Kylin的查询是通过启动HBase Coprocessor在HBase的region server中并行执行过滤和计算。基于这个信息我们最初排查了测试环境HBase集群的资源使用情况,观察后发现高并发请求发生时,region server上处理的RPC Task数量并没有与Kylin查询请求数成线性增长,于是初步定位问题应该出在Kylin端,可能存在线程阻塞。

我们选用了火焰图和JProfile对Kylin Query server进行了数据收集分析,结果都不是很理想,没有定位到问题的源头。之后我们尝试通过jstack抓取Kylin的线程快照,分析jstack log后我们最终发现了造成并发查询瓶颈问题的原因。这里用其中一次测试的结果举个例子(Kylin 版本 2.5.0)。

在一次快照中一个线程lock在sun.misc.URLClassPath.getNextLoader。此线程的 TID 是0x000000048007a180:

"Query e9c44a2d-6226-ff3b-f984-ce8489107d79-3425" #3425 daemon prio=5 os_prio=0 tid=0x000000000472b000 nid=0x1433 waiting }}{{for monitor entry [}}\\\{{0x00007f272e40d000}}{{]  java.lang.Thread.State: BLOCKED (on object monitor)    at sun.misc.URLClassPath.getNextLoader(URLClassPath.java:469)    - locked <0x000000048007a180> (a sun.misc.URLClassPath)    at sun.misc.URLClassPath.findResource(URLClassPath.java:214)    at java.net.URLClassLoader$2.run(URLClassLoader.java:569)    at java.net.URLClassLoader$2.run(URLClassLoader.java:567)    at java.security.AccessController.doPrivileged(Native Method)    at java.net.URLClassLoader.findResource(URLClassLoader.java:566)    at java.lang.ClassLoader.getResource(ClassLoader.java:1096)    at java.lang.ClassLoader.getResource(ClassLoader.java:1091)    at org.apache.catalina.loader.WebappClassLoaderBase.getResource(WebappClassLoaderBase.java:1666)    at org.apache.kylin.common.KylinConfig.buildSiteOrderedProps(KylinConfig.java:338)

同一时刻有43 个其它线程waiting to lock <0x000000048007a180> 

"Query f1f0bbec-a3f7-04b2-1ac6-fd3e03a0232d-4002" #4002 daemon prio=5 os_prio=0 tid=0x00007f27e71e7800 nid=0x1676 waiting }}{{for monitor entry [}}\\\{{0x00007f279f503000}}{{]  java.lang.Thread.State: BLOCKED (on object monitor)    at sun.misc.URLClassPath.getNextLoader(URLClassPath.java:469)    - waiting to lock <0x000000048007a180> (a sun.misc.URLClassPath)    at sun.misc.URLClassPath.findResource(URLClassPath.java:214)    at java.net.URLClassLoader$2.run(URLClassLoader.java:569)    at java.net.URLClassLoader$2.run(URLClassLoader.java:567)    at java.security.AccessController.doPrivileged(Native Method)    at java.net.URLClassLoader.findResource(URLClassLoader.java:566)    at java.lang.ClassLoader.getResource(ClassLoader.java:1096)    at java.lang.ClassLoader.getResource(ClassLoader.java:1091)    at org.apache.catalina.loader.WebappClassLoaderBase.getResource(WebappClassLoaderBase.java:1666)    at org.apache.kylin.common.KylinConfig.buildSiteOrderedProps(KylinConfig.java:338)

分析代码栈我们可以追溯到最近的Kylin的逻辑在org.apache.kylin.common.KylinConfig.buildSiteOrderedProps(KylinConfig.java:338),然后再结合Kylin源代码进一步分析,成功就离我们不远了。

03 代码分析

Kylin query engine 构建查询请求时, 会导出Kylin properties (Kylin里的各种配置)发送给HBase Coprocessor,在KylinConfig.class中有这么一个方法:

function private static OrderedProperties buildSiteOrderedProps()

它的执行逻辑是这样的:

1. 对于每个线程, 会调用getResouce 去读取”kylin-defaults.properties”(默认配置文件,用户不可修改) 的内容。

// 1. load default configurations from classpath.// we have a kylin-defaults.properties in kylin/core-common/src/main/resourcesURL resource = Thread.currentThread().getContextClassLoader().getResource("kylin-defaults.properties");Preconditions.checkNotNull(resource);logger.info("Loading kylin-defaults.properties from {}", resource.getPath());OrderedProperties orderedProperties = new OrderedProperties();loadPropertiesFromInputStream(resource.openStream(), orderedProperties);

2. 循环10次去读取”kylin-defaults” +(i)+ “.properties”, 线程阻塞就发生在这里。

for (int i = 0; i < 10; i++) {String fileName = "kylin-defaults" +  + ".properties"; URL additionalResource = Thread.currentThread().getContextClassLoader().getResource(fileName); if (additionalResource != null) {        logger.info("Loading {} from {} ", fileName, additionalResource.getPath()); loadPropertiesFromInputStream(additionalResource.openStream(), orderedProperties); }

通过版本追溯,这段逻辑是在2017/6/7 引入的,对应的JIRA ID 是KYLIN-2659。



04 问题解决

针对第一段逻辑,因为kylin-defaults.properties是打包在kylin-core-common-xxxx.jar中,在Kylin启动后是不会改变的,因此不需要每次查询时都从文件读取。可以将这段逻辑挪至 getInstanceFromEnv(),这个静态方法只会在服务加载时调用一次。

在修改这块逻辑时遇到一个坑。在Coprocessor中的类CubeVisitService,它会调用KylinConfig作为工具类去生成KylinConfig 对象,引入读取properties文件的逻辑是危险的,因为 Coprocessor中没有打包kylin.properties文件。

buildDefaultOrderedProperties();

对于第二块逻辑,设计的最初应该是为了未来扩展,允许用户定于多达10个default properties文件(彼此覆盖),但是经历了一年半的版本迭代,这段逻辑似乎没有被使用。但是为了降低风险,在这次修复中暂时保留这段逻辑,因为前面的改动后,这段逻辑只会在服务加载时执行一次,因此它的时间损耗基本可以忽略。

05 修复后性能测试

基于修复后的版本在同样的数据量和环境中进行测试,结果如下:

同样地绘制出趋势图:

当并发数达到150时,Kylin每秒能处理的查询请求数可以达到467,与此bug修复前并发处理能力提高了5倍左右,趋势图也是呈线性增长的,可以看到瓶颈基本消除了。我们没有再进一步提高并发数测试的原因是Kylin Query engine都是做集群负载均衡配置,一味地增加单节点的并发连接数反而会增加Tomcat服务器的压力(Tomcat 默认最大线程数为150)。

重新收集分析jstack日志,再没有发现线程阻塞的问题。

根据现在的测试结果,单个Kylin节点每秒可以处理 467/15= 31个客户查询,是满足当前业务需求的。此外,如果开启Kylin的查询缓存,单节点 QPS 还可以提升若干倍,足以满足我们的需要。

06 总结

Kylin的一大亮点就是提供亚秒级的海量数据集的查询,而实现这个目标既得益于Cube预计算的设计,以及Query时Apache Calcite算子的优化,同时在2.5.0版本中也引入了PreStatement Cache来减少Calcite语义解析的消耗。每一点的查询性能优化都是来之不易的,在引入新功能、bug fix等代码改动的时候,大家要额外注意这些改动对 Kylin query engine的影响,有时可能会牵一发而动全身。这些在高并发下的问题,往往是比较难重现和分析的。

另外,查询性能测试不能仅局限在单次或少量查询,可以结合实际业务需求估算并发请求数做相应的高并发性能测试。对于企业级的报表系统,客户的新页面载入忍耐度在3秒钟,这包括了页面渲染和网络消耗,所以后台数据服务的查询响应最好控制在1秒以内。这在基于大数据集的业务场景下确实是不小的挑战,Kylin则很好的满足了这一需求。

目前这个问题已经作为KYLIN-3672在JIRA上提交,并在Kylin 2.5.2版本发布,感谢这一过程中来自Kyligence团队史少锋同学的帮助。

参考文献:

【1】https://issues.apache.org/jira/browse/KYLIN-3672

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容