最近写 Hadoop 相关的代码写了很多,其实无非也就是 MapReduce 和 HQL 这两种东西比较多。但是,写着写着就发现效率有点低了,停下来看看其实是数据量已经有点上来了,所以一些低效率的 HQL 就暴露出来了。下来就分享一下其中一个我的修改过程。

首先,先说下场景吧,为了方面描述情景,我简化一下模型,可以将模型简化为:

图 1:简化模型

这里有两张表,一个可以看做是放了一些数据的用户表,但是这些用户是通过筛选的,数量大概是 100W(106),然后是 Tag 表,这张表放置了用户的标签和用户的 id,每个用户有多个标签,然后数量级的话大概是 10E(109),然后我们需要统计 100W 个用户中其中 100 个左右的标签的用户分布。

举个例子,例如这 100 个标签里面有两个标签是 男/女,这样的话,我们需要统计出 男 这个标签有多少,女这个标签有多少,然后算个比例。还有其他标签分组,就不细说了,对于这个需求,我们一开始的最初版本 HQL 是这样写的:

图 2:最初版本 HQL

对于这段代码,我简要得解析了一下:

那么这段 HQL 的复杂度可以简要得概括为:

为了方便,我们可以简要得认为为了计算出结果,我们需要遍历 1016 记录。

这个复杂度我肯定是接受不了的,而且后续还有筛选 100 个 Tag 的操作需要完成,所以我需要寻求其他方法。

换个思路

既然求全局有点麻烦,不如我们以个例来完成,例如,我想求出 10E 个数据中男的 uid,然后和 User 里面的 uid 求交集不就是男的个数了么?同样的操作我执行 100 遍,算一下复杂度是多少:

图 3:这个是图片说明

我们可以分别算一下每个步骤的复杂度:

  1. 106 * count(Tagman) ≈ 1015
  2. < 1015
  3. < 1015

所以最后可以算出中的复杂度是:

反而比之前多了,于是乎我们会觉得还不如之前的好。但是,转念一想,这里的算法真的对么?

首先,这个 Tagman 其实不等于 109,认真想一想,其实这个值很可能是介于: 106 到 107 之间的,因为标签虽然有 10E,但是分属于很多标签,所以属于男的这一标签的占比是很小的一部分。因此这个方法还是可以的,有进步的。

再进一步

虽然这种方法有所改进,但是,依旧很慢,原因也是很简单,就是因为当我们只关注男的这个标签的时候,我们还会遍历其他的标签,也就是说每次我们都会遍历所有的标签。为了解决这个问题,我们引入了 Partition,在 Hive 中,Partition 的作用就是将分区存储于不同的目录,然后你当你只对某一个目录中的数据感兴趣的时候,Hive 就只读对应目录的数据,而不会读取其他目录的数据,例如我们修改 Tag 的结构为:

图 4:修改 Tag 结构

如此这般,我们就可以加快步骤一中的速度,从而获得跨越性得提速。

这就到尽头了?

好像到这里我是无能为力了,似乎性能也就这样了。但是,优化总是没有穷尽的,压榨一下总是有得提高的,我们可以看一下下面这段 HQL:

图 5:并行 HQL

在原来的 HQL 中,stage1 和 stage2 是顺序的关系,想运行完 stage1 之后才会运行 stage2,那么它们就不能同时执行么?如果同时执行的话,效率岂不是能提高一倍?似乎有点可能,那么怎么做才能并行执行, Hive 帮我们了,使用这句配置即可:

  1. set hive.exec.parallel = true;

总结

这是我在 Hive 中简单优化的一个样例,过程虽然很简单,但对于初级入门的我还是稍微有点吃力,而且效果很不错,你要问我优化效果有多少?我可以告诉你,优化之前跑一段 HQL 的时间是 180 分钟左右,优化之后 6 分钟即可全部跑完。