最近写 Hadoop 相关的代码写了很多,其实无非也就是 MapReduce 和 HQL 这两种东西比较多。但是,写着写着就发现效率有点低了,停下来看看其实是数据量已经有点上来了,所以一些低效率的 HQL 就暴露出来了。下来就分享一下其中一个我的修改过程。
首先,先说下场景吧,为了方面描述情景,我简化一下模型,可以将模型简化为:
图 1:简化模型 |
这里有两张表,一个可以看做是放了一些数据的用户表,但是这些用户是通过筛选的,数量大概是 100W(106),然后是 Tag 表,这张表放置了用户的标签和用户的 id,每个用户有多个标签,然后数量级的话大概是 10E(109),然后我们需要统计 100W 个用户中其中 100 个左右的标签的用户分布。
举个例子,例如这 100 个标签里面有两个标签是 男/女,这样的话,我们需要统计出 男 这个标签有多少,女这个标签有多少,然后算个比例。还有其他标签分组,就不细说了,对于这个需求,我们一开始的最初版本 HQL 是这样写的:
图 2:最初版本 HQL |
对于这段代码,我简要得解析了一下:
- Tag: 数量是 1010
- User: 数量是 106
那么这段 HQL 的复杂度可以简要得概括为:
- 1010 x 106 = 1016 的复杂度
为了方便,我们可以简要得认为为了计算出结果,我们需要遍历 1016 记录。
这个复杂度我肯定是接受不了的,而且后续还有筛选 100 个 Tag 的操作需要完成,所以我需要寻求其他方法。
换个思路
既然求全局有点麻烦,不如我们以个例来完成,例如,我想求出 10E 个数据中男的 uid,然后和 User 里面的 uid 求交集不就是男的个数了么?同样的操作我执行 100 遍,算一下复杂度是多少:
图 3:这个是图片说明 |
我们可以分别算一下每个步骤的复杂度:
- 106 * count(Tagman) ≈ 1015
- < 1015
- < 1015
所以最后可以算出中的复杂度是:
- 1015 * 100(tags) = 1016
反而比之前多了,于是乎我们会觉得还不如之前的好。但是,转念一想,这里的算法真的对么?
首先,这个 Tagman 其实不等于 109,认真想一想,其实这个值很可能是介于: 106 到 107 之间的,因为标签虽然有 10E,但是分属于很多标签,所以属于男的这一标签的占比是很小的一部分。因此这个方法还是可以的,有进步的。
再进一步
虽然这种方法有所改进,但是,依旧很慢,原因也是很简单,就是因为当我们只关注男的这个标签的时候,我们还会遍历其他的标签,也就是说每次我们都会遍历所有的标签。为了解决这个问题,我们引入了 Partition,在 Hive 中,Partition 的作用就是将分区存储于不同的目录,然后你当你只对某一个目录中的数据感兴趣的时候,Hive 就只读对应目录的数据,而不会读取其他目录的数据,例如我们修改 Tag 的结构为:
图 4:修改 Tag 结构 |
如此这般,我们就可以加快步骤一中的速度,从而获得跨越性得提速。
这就到尽头了?
好像到这里我是无能为力了,似乎性能也就这样了。但是,优化总是没有穷尽的,压榨一下总是有得提高的,我们可以看一下下面这段 HQL:
图 5:并行 HQL |
在原来的 HQL 中,stage1 和 stage2 是顺序的关系,想运行完 stage1 之后才会运行 stage2,那么它们就不能同时执行么?如果同时执行的话,效率岂不是能提高一倍?似乎有点可能,那么怎么做才能并行执行, Hive 帮我们了,使用这句配置即可:
set hive.exec.parallel = true;
总结
这是我在 Hive 中简单优化的一个样例,过程虽然很简单,但对于初级入门的我还是稍微有点吃力,而且效果很不错,你要问我优化效果有多少?我可以告诉你,优化之前跑一段 HQL 的时间是 180 分钟左右,优化之后 6 分钟即可全部跑完。