即便没有数据倾斜,千亿级的数据查询对于系统也是一种巨大负担,对于数据开发来讲,如何来优化它,既是挑战,也是机遇!git
在上一篇文章 《漫谈千亿级数据优化实践:数据倾斜(纯干货)》中,咱们分享了一些在千亿级数据优化实践中和数据倾斜相关的内容。本文将分享千亿级数据优化的另个一点:如何使用使用数据!github
注意:sql
本文会限定一些业务场景和技术架构,所以解决方法会局限于此。不少问题能够经过换架构或者引入新的组件来解决,可是成本可能会很高,所以暂不考虑。apache
本文不是一篇Hive使用和优化文档,更侧重于梳理笔者的思路,让你们少走些坑。数组
在流行的大数据领域中,Hive绝对占据了很大的一片天地,不论是数据仓库和数据分析,仍是数据挖掘和机器学习,凡是须要和大数据量打交道的童鞋们,基本上都要接触Hive。所以,本文将侧重于千亿级数据在Hive中的使用,并经过一个典型的数据使用难题来总结一些在大规模数据场景下的优化方式。数据结构
本文主要以一个具体的使用场景为切入点,为了解决该场景下的使用难题,笔者经理了一次次的尝试+失败,最终找到了一种相对比较合适的方式。架构
本文能够看过是一种记录和思考,彻底还原笔者在遇到问题时的解决方式。所以全文会以事情的发展为主线,每次尝试一种解决方法,失败后继续查找新的方法,中间会穿插一些技术细节。机器学习
文章主线以下:函数
明确使用场景和困难。学习
如何解决,这是一个不断推翻重来的过程。
回顾总结
本章做用主要有二:
明确业务背景和使用场景
明确困难所在
按照惯例,我又来到了一家电商网站来工做,咱们有一张十分重要的表:用户和购买过的商品表。
以下图,该表只有三个字段,分别是用户ID、商品ID。咱们能够简单地理解这是一张事实表。对事实表没印象的能够参考这篇《漫谈数据仓库之维度建模》。
咱们暂且无论当初为什么这样设计的,如今的状况就是
| 列名 | 字段类型 |
| ------------- |:-------------:|
| user_id | string |
| product_id | string |
这张表有哪些使用场景呢:
输入一批用户,找到和他们有类似购买行为的用户
统计用户购买商品的数据区间
......
总之这张表能用到的地方是极多的,相信数据分析和数据挖掘的童鞋们确定能想到不少场景,这里就不展开讲了。
问题来了: 数据量太大,随便一个查询就是五六个小时,有没有办法优化?
先说明一下问题在哪。
数据量大
这张表里面保存了我站来自全球的50亿用户和他们购买过的商品,粗略估计一下,人均会购买60件商品,也就是说这张表有 3000亿 的数据。
3千亿条记录是什么概念呢,若是存成没有压缩的txt文件的话,大体有30T以上。若是作一个压缩,咱们保守一点估计,要有接近10T的数据。
查询速度慢
这么大数据量,查询起来的确比较慢。可能随便跑一个数据,就要3到5个小时。
咱们能够大体地分析一下慢的缘由:
扫描数据量大
join的时候时间长
由于咱们的reduce数量右限制,每一个reduce须要处理的数据量太多
shuffle的时候效率过低。
咱们在解决这个难题的时候是围绕一些出发点的:
减小扫描的数据量
加快关联查询的速度
第一个思路就是分区,咱们能够根据用户的帐号分布来进行分区,而后在扫描的时候,只扫描部分分区就行。 好,咱们作一个设计。
咱们美好的愿望是:假设有一个需求须要查询必定的用户的购买记录,咱们不用扫描全量的数据,只扫描其中一部分便可。
下面咱们基于几个设定来设计咱们的分区规则:
假设咱们的用户id都是数字类型的,以下图。
咱们按照帐号的id来设计分区函数,好比说前四位相同的放在一个分区中。
写入数据,和查询数据使用相同的分区函数
这样咱们就有了1万个分区,每一个分区中有30万用户的购买记录,也就是说每一个分区中会有1800万的记录数,总计约1G的文件大小。
下面就是咱们设计出来的分区。
咱们的想法是好的,下面举几个场景:
好比如今须要查100个用户的数据,不分区的话,咱们须要扫描全量的数据,如今咱们可能只要扫描10个分区,最多100个分区,也就是咱们的速度回提高100倍以上。
须要查1万个用户的数据,咱们假设会命中1000个分区。
须要查10万个用户的数据,咱们假设会命中5000个分区。
例子我都举不下去了,实际状况是,若是用户分布比较分散的话,超过20万个用户的话,基本上就命中了全部了分区了。 这个感兴趣的能够测一下。
增长分区数?
这个方案是能够的,好比咱们变成10万个分区,这样固然能够,可是让须要查询的用户多的话,效果照样变弱,并且更多的分区意味着每一个分区的数据会变少,这样小文件就会多不少。
结论
分区的方式不靠谱!
注意: Hive的索引也是个坑,怪不得没人用,可是咱们仍是要设计一下。
基于“减小扫描的数据量”这点来说,索引是一种极妙的方式,有了索引,咱们就没必要全量扫描全部的数据,速度确定就快了呀。 可是, Hive的索引是个坑。
下面讲一下Hive索引的机制就明白了。
Hive索引机制
在指定列上创建索引,会产生一张索引表(Hive的一张物理表),里面的字段包括,索引列的值、该值对应的HDFS文件路径、该值在文件中的偏移量。
以下,是Hive的索引表。其中,索引表中key字段,就是原表中key字段的值,_bucketname 字段,表明数据文件对应的HDFS文件路径,_offsets 表明该key值在文件中的偏移量,有可能有多个偏移量,所以,该字段类型为数组。
| 列名 | 字段类型 |
| ------------- |:-------------:|
| user_id | string |
| _bucketname | string |
| _offset | array < bigint > |
在执行索引字段查询时候,首先额外生成一个MR job,根据对索引列的过滤条件,从索引表中过滤出索引列的值对应的hdfs文件路径及偏移量,输出到hdfs上的一个文件中,而后根据这些文件中的hdfs路径和偏移量,筛选原始input文件,生成新的split,做为整个job的split,这样就达到不用全表扫描的目的。
注意: 按照上面的说明,咱们的索引其实就是另外一张Hive的表,并且数据量仍是很大。 下面从两个点说明Hive的索引方案不能用。
通过测试,索引表就有四、5T,咱们在查询的时候,要先和这张索引表作关联,而后再和原表作关联,损失太大了。
HDFS文件系统的设计问题。会致使最终咱们扫描的仍是全表。为何?下面讲解。
HDFS的设计
咱们默认你们对HDFS原理有所认知。这里只说一下此次咱们优化的内容。
假设咱们10T的数据,按照128M一个文件块,那就是咱们有七八万个文件块。和前面的分区的状况相似,当须要查询的用户数量到必定程度,基本上仍是要扫描全部的文件块。
结论
索引的方式不靠谱,至少Hive中不可用。
索引的使用方式,就再也不描述了,看官网仍是挺简单的:Hive官网:Index。
分桶就再也不说了,和前面说的问题相似,也不可用。
到这里我就绝望了,有打算不解决了。准备用最初的一招:按活跃度区分。
这也是一种很值得考虑的方式,由于咱们大部分对数据的使用都会考虑活跃用户,这里咱们把30亿用户中活跃的10亿的用户抽出来放一个分区,这样的话,咱们的查询效率能提升3倍左右。
问题
活跃用户很差定义,每一个业务方的定义不同。
运行成本太大,跑这个数据挺耗时间。
结论
这是一种方法,若是没有更好的方法就用这个了。
受大神的指点,咱们更换了一种在Hive中的存储方式,如今更新表以下
| 列名 | 字段类型 |
| ------------- |:-------------:|
| user_id | string |
| product_list | array< string > |
这是一个很简单的转表,咱们使用了Hive中的数据结构Array,把一个用户的全部购买过的商品放入到一个字段中,这样的话,咱们的总数据量就只有30亿了,在作关联查询的时候速度必然很快。
实践
通过实践,这样的存储,只需占用以前存储量的1/2左右,也就是只须要不到5T的大小,查询速度从平均一个任务4个小时缩减到半个小时。
问题
这里有两个问题:
数据更新,数据结构的改变致使在更新数据的时候有一些障碍,这点再也不展开,方法老是有的,笔者是保留了两份数据,这一份专门供查询用。
使用成本增长,由于数据结构变了,相应的查询sql也要调整。 这里就须要用到lateral view explode
,详情可看官网。
总结
总的来接,这种方式仍是可行的,目前的反馈还都是正向的,额外的sql复杂度不是很大,大部分童鞋都能接收。
本文主要是描述了一次千亿级数据量查询优化的过程,回头来看,其实也听简单的,可是身在其中未必能想清楚,这也和经验有关,所以走了不少的弯路。
总的来说,笔者尝试了5种方式:分区、索引、分桶、活跃度区分、新的数据结构。最后一种方式基本解决了遇到的问题。
本文对一些技术细节没有过多描述,好比建索引,建表,这些在官网很容易找到,所以再也不过多涉及。思想到位了,其它的问题都不大。
做者:dantezhao |简书 | CSDN | GITHUB
我的主页:http://dantezhao.com
文章能够转载, 但必须以超连接形式标明文章原始出处和做者信息