1. OLAP vs OLTP
OLAP 翻译成中文叫联机分析处理,OLTP 叫联机事务处理。OLTP 它的核心是事务,实际上就是我们常见的数据库。我们业务数据库就是面向于事务。它的并发量会比较高,但是操作的数据量会比较小。它是实时更新的。数据库的设计会按照 3NF 范式,更高的话可能会按照 BC 范式之类的来做。而 OLAP 的核心是分析,面向应用是分析决策,需要分析的数据级会非常大,可能 TB,甚至 PB 都会有。它的数据更新会稍微慢一些,它的设计一般是反范式的,因为面向分析。常见的是雪花模型和星型模型。
实际上 OLAP 是什么呢?
非常简单,就是一个 SQL,这里按照两个维度,一个 returnflag,一个 orderstatus 来做 Group By,然后做一下 Sum,Group By 这段就叫维度,From 这段叫做指标,非常简单。
2. OLAP 引擎分类
OLAP 引擎的一些常见分类大概有这几种。
- 第一种叫 ROLAP,叫关系型 OLAP,它的特点就是它是基于关系性模型,计算的时候,根据原始数据去做聚合运算。常见的实现,小数据量可以利用 MySQL、Oracle 这种传统数据库,而大数据量可以利用 Spark SQL、Presto 这些项目。
- 第二种类型叫 MOLAP,叫多维 OLAP,它的特点就是它会基于一个预定义的模型,我需要知道,要根据什么维度,要去算哪些指标,我提前就把这些结果弄好,存储在引擎上。当查询的时候,根据结果简单地做一做汇总就可以得出来。今天主要会讲到 Kylin 引擎,蚂蜂窝的汪老师后续会讲到 Druid 引擎。大家也可以做一下对比。
3. HOLAP
最后一种叫 HOLAP,叫混合 OLAP,这个就非常简单了,就是两个杂交一下。根据业务场景,采用不同的引擎。
上图这是简单的 ROLAP 模型,具体操作流程刚才已经提到了,它的优势就是,它其实就是个数据库,所以任何的 SQL 都可以在里面执行。数据是没有冗余的。缺点就是数据量很大的时候,计算速度会下降很多,所以并发会比较差。它的场景就是不知道要查什么数据,灵活性非常高时,一般会选 ROLAP。
4. MOLAP
第二种 MOLAP,刚才提到 MOLAP 主要是要定义一个模型。比如说,下图样例里面定义了三个维度:Time,Web page,Action。从这三个维度把所有的组合提前预计算好,存在一个存储引擎中,需要的时候里面取出来做一下聚合就可以,所以它的原始数据支持非常大,查询速度,因为直接算好了,可以很快返回。它的缺点就是因为聚合了以后,就查不到明细数据。它的灵活性稍微差一点,因为需要预先去定义维度,还有指标。所以说是需要能够知道查询的模式,才能用这个 MOLAP。
1. 早期分析数据实现
我们刚开始做大数据的时候,Hadoop 组建基本上都是基于开源的。我们会从日志、Kafka 去导数据,MySQL 数据会用 Sqoop 同步到 Hadoop 中。当时是用一个开源的调度引擎 Ooize,我们根据业务需求去建表,然后把数据导到 MySQL 中,因为要做呈现,必须要在一个比较快的返回的数据库中。所以放在 MySQL。
它的缺点是什么?
当它的数据量越来越大,MySQL 做存储,它的扩展性是非常不好的。数据量大以后,第二个问题速度会比较慢。第三个,面临分析的维度非常多,每个维度的分析,都要一个数据开发做需求,平均做一个需求时间可能要两周。
能不能用一些 OLAP 引擎来简化操作?我们总结了对 OLAP 引擎的一些需求。
第一,响应要快。因为业务分析人员等不了太久。需要一定并发。最好有一个 SQL 接口。支持数据级要非常大。当然离线方面,我们目前是 T+1 的模式,所以说综合考虑选择了 Kylin。Kylin 就是基于 Hadoop 之上的提供 SQL 查询和多维分析的能力,它能支持超大规模数据级。它能在亚秒返回巨大的一个查询。实际上它就是标准的 MOLAP 方案,我们需要预先定义维度和指标,提前预计算它的 Cube,把结果存在 HBase,查询时解析 SQL,根据路由把它到 HBase 到对应的表中去拿取数据。
下图是 Kylin 的架构图,最下面是构建引擎,左边是 Hadoop 数据仓库,右边是 HBase。最下面会根据 Hadoop 不同的原始数据进行构建,然后把数据存在 HBase。查询的时候,在 Query Engine 做解析,解析完了以后,下面路由选择,去 HBase 中查对应的数据。
左边大家看到有根虚线,这个是什么意思呢?刚才提到 MOLAP 只能支持预聚合的数据,要查原始的明细数据应该怎么办,Kylin 是不支持的。这条虚线划出来,官方说会在后续某个版本支持,但实际上一直没做。后面会讲到我们是怎么解决这个问题的。
再介绍一下链家 Kylin 使用统计,定位是离线的 OLAP 引擎。线上有 100 多个 Cube,公司 8 个业务现在使用,总共存储容量应该也到 30T。总的数据量应该 800 亿行左右,单个 Cube 最大已经到 40 亿行。每天的查询量大概有 10 万多,查询性能还是非常好的,95% 能在 500ms 以内,99% 都在 1s 中返回。除开那种超大的,可能会在 10s 左右,还是非常满足我们需求的。
3. 链家 OLAP 平台架构上图是链家整个 OLAP 平台的架构。
首先核心还是基于 Kylin 这样一个 MOLAP 引擎,我们 Kylin 做了读写分离的部署,也做了负载均衡的高可用。Build 机器是构建任务的机器,下面对接的是主要的 Hadoop 集群,负责所有的数据仓库存储和所有的计算。
我们有自研的调度系统,每天会去调度 Kylin 的构建任务。关于调度系统,调度提供了很多功能,比如说分布式调度,会根据机器的负载情况去分发任务,会做任务的依赖,会做任务的监控等等。
为什么第一块是HBase集群,而且还是单独的集群呢?
如果 HBase 跟 Hadoop 在一起,Hadoop 一旦运行大的任务,内存压力大的时候,HBase 就会性能非常差,所以把查询服务独立出来。所有的查询 Query 机器都会去查 HBase。上面指标分析平台就是链家可视化的分析平台,它底层的引擎主要就是 Kylin,它所有的预建模的查询都会走 Kylin,当然会做缓存,把那些常用的 SQL,重复的 SQL 缓存住。
第二块就是刚才提到的,如果有查明细数据该怎么办?
链家这边集群组做了另一个引擎叫 Query Engine,这是我们自己的。它主要是给另外一个业务提供服务,提供即时查询服务。它底层是两个引擎,Presto 和 Spark SQL。在指标上会把明细的查询和灵活的查询,如果 Kylin 不支持,就会把它转到 Query Engine,Query Engine 会根据它的 Cost来做优化,来选择 Presto 或者 Spark SQL 来查询。这样就解决了明细查询的问题。
指标分析平台同时也对外提供 API,如果其他业务需要我们的数据,可以提供 API 的形式。然后 Cube 管理,Kylin 有原生页面来做 Cube 管理的,但是那个原生的页面会有很多问题。比如说涉及 LDAP 的权限控制,跟链家的权限是没有打通的。它本身的性能是非常差。刷一个页面可能要发几十个 Post 请求,非常卡。于是我们独立开发了一套 Cube 管理,集成了链家的权限管理,性能会比原来好很多。
最后一块就是预警监控,我们所有模块都有 Supervisior 存活监控,保证它挂了以后能启动,整个链路都会上报到 OP 部门 维护的基于 Falcon 的监控平台。
下图是报表查询页面,这里会显示我有权限的报表,在里面搜索一下,我想看的一些报表。
首先选择一个时间的维度,然后这里可以筛选很多条件,比如说根据分公司,根据店组去查。比如想看东北区的详情,点进去就可以显示到这个区里面所有店主。如果需要看这个店主再点进去,就可以到这个店主里面的每个人。这个在 OLAP 领域,这个过程叫做下钻的过程。如果往回这个过程叫上卷,相当于把那个结果再往回聚合。我们感觉 UI 设计的还可以。
然后下图这是我们自研的 Cube 管理。
如果大家有用过 Kylin 的话,应该觉得主要的功能都是差不多的,因为都是根据 Kylin 来做定制的。比如说选择维度,选择指标。然后可以设置一些参数,可以在这里面做查询。它的可能就是它对接了我们权限平台,能够自己对它做管理。
4. 链家 OLAP 特色
总结一下链家 OLAP 的一些特色。
- 这是自研的可视化平台,支持上卷下钻,维度对比,可视化报表创建,指标管理。
- 开源的时候,其实有些开源产品比如说叫 Saiku,有些公司有应用。相比于它的话,个人感觉 UI 还是美观一些,更切近业务,方便灵活定制。
- 我们的引擎能力,其实并不是简单的 MOLAP,而是一个混合的 OLAP 模型,既支持明细查询,又支持聚合查询。
- 针对需求实现了跨 Cube 查询的功能。就是 Kylin 中,一个 Cube 对应一张实时表。一个 Cube 里面只能查一张实时表的数据。假如说有多个实时表,但是我查的维度是类似的,我想把结果做聚合运算,Kylin 是不支持的,我们平台里面支持了跨 Cube 查询。
- 然后在这里面完整监控,有高可用的架构。
- 前端做一些了简化,对接了权限,简化配置,提升我们的管理效率。
5. OLAP 展望
下面是我们 OLAP 下半年的展望。
一是扩展能力。刚才提到已经有跨 Cube 查询,但是随着业务增长,可能有更多的需求。比如说想把 Kylin 的数据去跟 Hive 里的数据做计算,或者业务那边接入他们自己的 Oracle,里面放了一些他们不想给我们的数据,但是想跟我们的数据做一些混合运算,我们这边做一个多元数据查询。
第二块会做一些更多的路由优化,优化查询效率。第三就是 Kylin 目前它的源数据同步,Build 机器相当于 Master,Query的机器相当于 Slave,目前是 Push 的模式,Build 机器 build 好了,把一些元数据同步过去。但是缺点是有时候会出现失败的情况。我们目前做得非常 low,就是用 Crontab,定期让 Slave 去重新刷一下缓存。
后期我们正在做的就是让 Slave 用心跳机制,Master 那里用一个状态机模型,把每一个要下发的任务放在状态机里,Slave 心跳了之后来取这个状态。当我更新完缓存以后,我在下次心跳的时候,把这个状态再置为已经更新。其实这个非常类似于 Hadoop 里面的 YARN 架构,都是基于状态机的。这样的话,扩展性会非常好,减少 Master 的一些压力。我们正在做 Kylin 2.0 调研,比如它支持雪花模型,不用转化数据仓库。支持 Spark 构建,提升速度。
第三块,我们目前的 OLAP 还是一个离线服务,随着业务的发展,我们有更多实时需求。主要是 MOLAP 里面的一些实时的方向,一个是 Kylin 自身支持 Streaing Cubing,类似于 Spark Streaming,实际上是小批量近实时的,做到分钟级。另外我们也考虑 Druid 或者 Palo 这些架构,它们是 Lambda 的架构。就是内存中会维护最新的状态,这样可以做到纯实时的更新。
三、OLAP 平台链路优化实践1. Kylin 全链路架构
最后讲一些比较接地气的,我们在 OLAP 平台全链路的一些优化经验。
首先看一下 Kylin 的整个链路。最上头的 Kylin,它的存储引擎是 HBase,HBase 又基于 HDFS。所有程序都跑到了JVM 上。首先是 Kylin 本身有一些优化,我们知道假如我们定义了 N 个维度,我查询的时候,每一个维度可选择或者不选择,其实有 2 的 N 次方种选择。比如说 A、B、C、D 四个维度,这里头一共有 16 种维度选择。这个 Kylin 里面会提供很多方式来避免这种维度的膨胀。
我这儿简要介绍一下,见下图。
第一个是聚合组,用户查询的时候不会所有条件都查,而是同时只会出现某几个维度,我们就把这几个维度定义为一个聚合组。比如说这里就用了 A、B、C 和 B、C、D。于是只有 A、B、C 内部的这些维度和 B、C、D 内部维度会进行计算,而且如果两个有交叉,就会计算一份。
其他的各种衍生维度、强制维度、层次维度,大家如果做 Kylin 引擎,后续可以在这块着重关注一下,我这边就不详细解释了。反正原理都是尽量根据查询的一些条件或者业务的一些条件,尽量减少要计算的维度搭配。
2. Kylin 改造以及优化
我们在 Kylin 里面做了很多改造和优化,第一个是 Kylin 默认只能在 HBase 中,放在默认库里面,前缀是 Kylin 加下划线。我们如果要在上面部署多套环境就会有冲突,我们这边修改了一下,让它支持配置一个前缀,可以在一套 HBase 中部署多套 Kylin。
我们之前讲漏了一件事儿,刚才架构图里面有一个预上线的 Kylin,我们所有的,因为上线前都会在预上线的 Kylin 里面进行验证,然后只有它的数据 OK 了以后才会上到线上,预上线的环境。还有第二个功能就是双写。假如一些重要的业务,我会在两个集群里边同时构建。如果主集群先出现问题,立马切到备用集群。我们修复了它里面的一个死锁的问题,主要是更新缓存部分,这个引擎加了社区。
然后接下来是一些,比如说构建的时候,MR 引擎的一些优化,比如开启压缩等等。Kylin 也是内存型的查询服务,它是 Java 写的,所以说自然 GC 的问题,等一下会详细讲一下。如果你使用全局字典,你是要调大一些 MAP 内存。定期会做 Segment 的合并,清理一些过期数据,减少HBase Region 个数。因为 HBase 如果 Region Server 太多,压力非常大。
最后还有一个默认使用的授权的时候,有个 Byct 加密,这个非常影响性能。后续我会讲如何定位这个问题。
3. 系统性能调优
关于系统调优给大家推荐一个工具叫火焰图,见下图。
火焰图是什么呢?
它就是一个可视化的去展示 CPU 占用情况的图。每一个小方框里面都是一个函数,水平方向看它的宽度就是 CPU 的时间占用,垂直方向看就是一个函数的堆栈。下面的函数就会调用上面的函数。所以自然上面函数的时间会累加到下面函数中。
如何定位性能的问题呢?
从下往上看,可以理解成,如果一个山峰一直没有缩短,到最顶层的时候,调用堆栈最上面的一个函数,那是最终调用的函数,如果它占用非常多,一定是它的性能问题。
我们之前发现 Kylin 在高并发的时候,CPU 会满载,非常高,但是查询非常简单。利用火焰图去分析发现,它有 80% 的时间在做一个 Spring 的 Bcybt 加密的验证。然后右边那一小块占 4% 的,这个才是 Kylin 的查询。
我们查了一下,其实 Bcybt 加密是一个比较好,相当于是比较不容易破解的加密。它的方式就是让它每次计算非常耗性能,降低它的速度。我们 将 Bcybt 加密换成了其他加密的算法。大家可以看到,这段(Kylin 查询部分)就是之前 4% 的时间,因为我把其他时间消掉了,所以它的占比就大幅度提升,提升到 40%。提升了大概 1 倍 QPS。
第二块就是 Java GC。其实 Java 大家应该还是比较熟悉的。像传统的 CMS 非常经典,但是它的缺点就是对于大内存回收会有问题,而我们现在线上基本开到 80G,甚至 90G,CMS 在这种情况下基本上是 STW 非常长,可能要达到几分钟。
G1 是 Java 最新的垃圾回收算法。它的核心还是保留了 CMS 的分代概念,但是它把每一代分成了很多 Region,打散。G1 定位是暂停时间可控的 GC,它会根据你设定暂停时间,在暂停时间内尽可能多地回收内存。因为打散成了很多 Region,我可以不全部回收,我可以回收部分,保证我每次回收的时间可控。因为新生代的 GC 是发生非常频繁的,所以我们要控制新生代的大小,保证每次回收时间可控。
上图是 GC 调优后的对比图,上面是 CMS,下面是 Java。可以看到,GC 频率和时间都有大幅地缩短。
第二块就是我们 Java 的存储是放在 Hbase 中,HBase 又放在 HDFS 中,HDFS 的底层就是物理上的磁盘。我想提升 Java 的查询性能,从硬件上我就想,现在主流的磁盘 IOPS 可能就几百,但是现在的 SSD,普通的 SSD 能达到几万,像 PCI 组建的 SSD 现在已经达到 30、40 万 IOPS。
我们今年年初做了一件事情,我们把 Hadoop 集群从 2.4 升到了 2.7,2.7 里面引入了一个非常重要的特性,叫异构存储,或叫混合存储。就是 Hadoop 支持我把我的磁盘标记一个标签,支持四种标签。第一种叫内存磁盘,第二种叫 SSD,第三个是普通磁盘,最后一种是归档,归档是一种性能非常差的磁盘,可以认为。定义了存储策略。比如说 ALLSSD 是指我的所有副本全放在 SSD 中,ONE-SSD 就是放一份,放在 SSD 中。默认是 Hot 全部放磁盘。我们这边会把 HBase 的核心日志,还有核心业务都会放在 ALLSSD 中,对重要业务会使用 ONE-SSD,如果是普通业务放在磁盘上就行了。
刚才提到,我们其实为了节省成本,如果用 SSD 也是主要用 ONE-SSD。ONE-SSD 有一个问题,假如说这个客户端读取数据,我有三个备份。其中一个是 SSD,但是我本地的副本是一个机械磁盘,默认的社区版会优先读取本地,因为它的想法就是网络延迟是比较高的。但实际上我们链家大数据已经全部是万兆的架构,所以说网络瓶颈已经是不存在的。我们这里给它定制了一个 First 选择策略,叫 SSD-FIRST。就是如果我远程有 SSD,我还是优先使用远程的。后面会给一个对比数据,这个已经提交给社区。
下图是一个 HBase 就是读写分离。
HBase 默认所有队列和处理线程实际上是不区分的,我查询请求分 Scan、Get、Write。三种请求都会打到相同的队列里面。如果出现了 Scan,把所有队列和线程都堵满了以后,简单的小查询都会卡住。这个是 HBase 在 1.2,还是 1.3 的时候已经实现了,我们可以把队列和线程按照比例去分配给 Scan、Get、Write 三种请求,避免 Scan 请求阻塞小请求。
下图是我们 HBase 的测试数据。
大概是三台机器,使用 ONESSD 测试工具。我们就关注绿色这条线,这条线就是吞吐量,或者叫 QPS 。最右边是 HDD,当我们使用 ONE-SSD 以后,大概能提升 4 倍左右的吞吐。我们再作用,上线 SSD-FIRST 策略以后,又能提升一倍的吞吐。最后在上线读写分离以后又能提升一倍,整体提升的大概 10 多倍。而且只有第一步是需要硬件成本的,后面的两个策略都是纯策略上的调整。
下图是很多常见的一些优化项,我这边就不详细解释了。主要是针对 Hadoop 的一些读写优化。
一些操作系统常见的部分调优
首先给大家讲一个最常见的命令 Free,运行出来能显示当前内存的情况。我这里写了三种境界,第一种是刚接触 Linux 同学,Free 是 0,是不是内存不够了,是不是感觉要挂。经过一段时间学习,发现 Cache,Buffer 都是可以拿来用的。
看第二行, Free 是 4G,内存什么问题都没有。真正老司机是什么样的呢?我们要知道 Cache 是用来干什么的,使用的命中率如何,Cache 是干什么的,Cache 默认就是 Page Cache,就是页面缓存,主要是为了缓存文件系统,它在 VFS 里面会用到的。然后一般的 Cache 都是可以释放出来给程序使用的。但是有一种情况 Cache 不能释放。就是有一种文件系统叫 tempfs,相当于内存的一个文件系统,利用了 Page Cache 的空间来做存储,这种数据是没法被清掉的。所以 Cache 并不是所有的可以用。如果拿来做文件缓存,本身操作系统这么设计一定是有它的作用,我们想知道这个缓存命中率如何。
我看了一下网上各种工具,实际上没有一个对缓存命中率计算的方法。命中率我们现在算 100%-Miss 的概率,Miss 是什么情况呢?Miss 的分母就是 Page 的访问次数,分子是,如果发生了 Misses,我就会把它从磁盘上读出来,再放到缓存中。所以说分子就是添加到 Page 缓存的一个次数。
我们这边因为 ftrace 会在系统上有问题,我用 System Tap 把脚本改写了一下,着重是四个系统的调用,System Tap 就是一个动态跟踪技术,它可以在内核任何一个函数上动态去注入一段代码,然后可以规定在函数运行前或者是运行后,我们这里只是给了四个过滤器,就是在四个条件上面做了过滤。
下面大家看一下 Total,里面第一项叫 Mark Page Accessed,就是页面访问次数,Miss 的第一项就是添加到 LRU 缓存的一个次数。后面减掉的一部分是什么呢?大家知道,页面缓存除了去读的时候用到,写的时候也会用到。任何一次写入都会写入到 Page 中。有时候我们要从分子分母中把写入的那部分数据给减掉。我们会把 Cache 的命中率定期同步到我们里面做监控。我们线上现在应该能达到 70%-90% 的命中率。据我们的试验,一般在 60% 以上,HBase 会比较稳定,因为 HBase 对于 IO 性能要求非常高。如果 Cache 命中率非常高,实际上每次读写都会非常快。当然这个也跟业务有关系。如果你的查询是非常随机的话,这个缓存也很容易被弄脏。
下面讲一个比较大的坑:NUMA。现在大家的服务器基本上都是多个 CPU,常见的有两种架构,一种叫 SMP,对称多处理。内存其实是跟 CPU 之间连着一根总线。它的问题就是随着CPU 越来越多,内存越来越大,所有的瓶颈都压在总线上。接下来英特尔公司就想到另一个方案,就是我每个 CPU 给它自己专属的内存,这个 CPU 的专属内存访问速度非常快。现在内存非常大,每个地方的专属内存应该是足够的,如果我实在是需要远程内存,我再走主线。
默认是一个亲和模式,什么意思呢?
就是我这个 CPU 分配的内存和淘汰的内存都会优先从我的专属内存中来找,比如说我专属内存不够用了,我就会优先从我专属内存里去淘汰。这样会导致一个问题,我系统看系统非常充足,但是我发现有 SWAP 占用,我的 Java 在做 GC。这个就非常奇怪。其实就是因为默认的亲和模式造成的。
这个亲和模式实际上对小内存应用非常好的,因为我一看 CPU 内存其实也很大了,至少 64G、128 个 G 都有,因为总共可能 256、512。单个小内存应用内存是足够的,但是像 HBase 或者说数据库,我们现在可以到 80G,甚至 100G,实际上单个 CPU 的专属内存是不够用的。我们这儿主要做了一个调整策略,就是把它的分配参数调整了一下。如果专属内存不够的时候,我允许从其他地方获取内存,而不是去做本地的回收。
最后一个也是比较大的一个坑,就是透明大页。什么是透明大页呢?大家知道,现在操作系统都是分页的,每一个程序有一个页表。程序分配都是逻辑地址,它要真的访内存要通过一次逻辑地址到物理地址映射,然后 TLB 是用来做一个加速的,现在默认的大小是 4K,随着内存越来越大就会有问题,页表会越来越长,TLB 缓存命中率也会降低。
操作系统想到一个方式,我就把配置调大,就给了两个选项,一个是 2M,最大可以开到 1G。这样看起来非常完美,但是在 6.5 的 centOS 中,这个大页是默认开启的,但是我们会发现 HBase 在使用过程中,CPU 占用会非常异常,System Tap CPU 会非常高。大家可以看,JVM 的使用才 24%,但是我内核达到 32%,我们用 Perf 工具来做这个实时采样。
会发现 Kernel 会发生在一个自选锁上,自选锁里面是什么呢?就能看到,会做一个异名的一个大页内存分配。这个就会造成在系统非常繁忙的时候,内核的 CPU 占用非常高。然后我们做了一个测试,在开启透明大页和关闭透明大页,看蓝色和红色的线,在开启透明大页的功能以后,就是 Always 这个选项下,它的性能有很多毛刺,性能总体会下降 30%- 40%。所以说我们推荐把透明大页关闭。当然操作系统还有很多其他的一些选项,这边就不详细说了,比如说文件数限制,关闭 SWAP ,TCP 这一块。
原文发布时间为:2018-01-11
本文作者:邓钫元