上海墨智科技笔试题
一、hive抽样,分区和分桶区别?
1.分区
Hive分区是指按照数据表的某列或某些列分为多个区,区从形式上可以理解为文件夹,比如我们要收集某个大型网站的日志数据,一个网站每天的日志数据存在同一张表上,由于每天会生成大量的日志,导致数据表的内容巨大,在查询时进行全表扫描耗费的资源非常多。那其实这个情况下,我们可以按照日期对数据表进行分区,不同日期的数据存放在不同的分区,在查询时只要指定分区字段的值就可以直接从该分区查找。
2.分桶
对于每一个表或者是分区,Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分。Hive是针对某一列进行分桶。Hive采用对列值哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶中。分桶的好处是可以获得更高的查询处理效率。使取样更高效。
3.抽样
在大规模数据量的数据分析及建模任务中,往往针对全量数据进行挖掘分析时会十分耗时和占用集群资源,因此一般情况下只需要抽取一小部分数据进行分析及建模操作。Hive提供了数据取样(SAMPLING)的功能,能够根据一定的规则进行数据抽样,目前支持数据块抽样,分桶抽样和随机抽样,具体如下所示:
-
数据块抽样(tablesample()函数)
1) tablesample(n percent) 根据hive表数据的大小按比例抽取数据,并保存到新的hive表中。如:抽取原hive表中10%的数据
(注意:测试过程中发现,select语句不能带where条件且不支持子查询,可通过新建中间表或使用随机抽样解决)
create table xxx_new as select * from xxx tablesample(10 percent)
2)tablesample(n M) 指定抽样数据的大小,单位为M。
3)tablesample(n rows) 指定抽样数据的行数,其中n代表每个map任务均取n行数据,map数量可通过hive表的简单查询语句确认(关键词:number of mappers: x) -
分桶抽样
关于Hive中的分桶表(Bucket Table),在以后的文章中将会介绍,其实就是根据某一个字段Hash取模,放入指定数据的桶中,比如将表lxw1234按照ID分成100个桶,其算法是hash(id) % 100,这样,hash(id) % 100 = 0的数据被放到第一个桶中,hash(id) % 100 = 1的记录被放到第二个桶中。分桶表在创建时候使用CLUSTER BY语句创建。
Hive中分桶表取样的语法是:
table_sample: TABLESAMPLE(BUCKET x OUT OF y [ON colname])
其中x是要抽样的桶编号,桶编号从1开始,colname表示抽样的列,y表示桶的数量。
例子1:
SELECT COUNT(1)
FROM lxw1 TABLESAMPLE(BUCKET 1 OUT OF 10 ON rand());
该语句表示将表lxw1随机分成10个桶,抽样第一个桶的数据;
前面介绍过,表lxw1总大小约为64816816,总记录数为:2750714
出来的结果基本上是原表的十分之一,注意:这个结果每次运行是不一样的,因为是按照随机数进行分桶取样的。
例子2:
如果基于一个已经分桶表进行取样,将会更有效率。
执行下面的语句,创建一个分桶表,并插入数据:
CREATE TABLE lxw1_bucketed(pcid STRING)
CLUSTERED BY(pcid) INTO 10BUCKETS;
INSERT overwrite TABLElxw1_bucketed
SELECT pcid FROM lxw1;
表lxw1_bucketed按照pcid字段分成10个桶,下面的语句表示从10个桶中抽样第一个桶的数据:
SELECT COUNT(1) FROMlxw1_bucketed TABLESAMPLE(BUCKET 1 OUT OF 10 ON pcid);
很好理解。
再看这个:
SELECT COUNT(1) FROMlxw1_bucketed TABLESAMPLE(BUCKET 1 OUT OF 20 ON pcid)
表只有10个桶,如果指定20,看结果:
结果差不多是源表记录的1/20,Hive在运行时候,会在第一个桶中抽样一半的数据。
还有一点:
如果从源表中直接分桶抽样,也能达到一样的效果,比如:
SELECT COUNT(1) FROM lxw1TABLESAMPLE(BUCKET 1 OUT OF 20 ON pcid);
区别在于基于已经分桶的表抽样,查询只会扫描相应桶中的数据,而基于未分桶表的抽样,查询时候需要扫描整表数据,先分桶,再抽样。
3.随机抽样(rand()函数)
1)使用rand()函数进行随机抽样,limit关键字限制抽样返回的数据,其中rand函数前的distribute和sort关键字可以保证数据在mapper和reducer阶段是随机分布的,案例如下:
select * from table_name where col=xxx distribute by rand() sort by rand()limit num;
2)使用order关键词
案例如下:
select * from table_name where col=xxx order by rand() limit num;
经测试对比,千万级数据中进行随机抽样 order by方式耗时更长,大约多30秒左右。
二、groupbykey和reducebykey的区别?
reduceByKey用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义。
groupByKey也是对每个key进行操作,但只生成一个sequence。需要特别注意“Note”中的话,它告诉我们:如果需要对sequence进行aggregation操作(注意,groupByKey本身不能自定义操作函数),那么,选择reduceByKey/aggregateByKey更好。这是因为groupByKey不能自定义函数,我们需要先用groupByKey生成RDD,然后才能对此RDD通过map进行自定义函数操作。
上面得到的wordCountsWithReduce和wordCountsWithGroup是完全一样的,但是,它们的内部运算过程是不同的。
三、map和flatmap的区别?
1.map(func)
将原数据的每个元素传给函数func进行格式化,返回一个新的分布式数据集。(原文:Return a newdistributed dataset formed by passing each element of the source through afunction func.)
2.flatMap(func)
跟map(func)类似,但是每个输入项和成为0个或多个输出项(所以func函数应该返回的是一个序列化的数据而不是单个数据项)。(原文:Similar tomap, but each input item can be mapped to 0 or more output items (so funcshould return a Seq rather than a single item).)
区别对比:
map(func)函数会对每一条输入进行指定的func操作,然后为每一条输入返回一个对象;而flatMap(func)也会对每一条输入进行执行的func操作,然后每一条输入返回一个相对,但是最后会将所有的对象再合成为一个对象;从返回的结果的数量上来讲,map返回的数据对象的个数和原来的输入数据是相同的,而flatMap返回的个数则是不同的。
通过上图可以看出,flatMap其实比map多的就是flatten操作。
示例比较:
四、map和foreach的区别?
Map和foreach都是对rdd中的每一个元素进行操作的。
foreach 对 RDD 中的每个元素都应用 f 函数操作,不返回 RDD 和 Array, 而是返回Uint;foreach 算子通过用户自定义函数对每个数据项进行操作。
map将原数据的每个元素传给函数func进行格式化,返回一个新的分布式数据集。
五、如果一个数据文件有100亿行,怎样做时间优化?
使用mapPartition 算子,增加并行度。
六、使用Linux命令读取文本文件中的某一列数据?
使用awk命令获取文本的某一行,某一列
1、打印文件的第一列(域) : awk '{print $1}' filename
2、打印文件的前两列(域) : awk '{print $1,$2}' filename
3、打印完第一列,然后打印第二列 : awk '{print $1 $2}' filename
4、打印文本文件的总行数: awk 'END{print NR}' filename
5、打印文本第一行:awk 'NR==1{print}' filename
6、打印文本第二行第一列:sed -n "2, 1p" filename | awk '{print $1}'
shell里面的赋值方法有两种,格式为
1) arg=`(命令)`
2) arg=$(命令)
因此,如果想要把某一文件的总行数赋值给变量nlines,可以表达为:
1) nlines=`(awk 'END{print NR}' filename)`
或者
2) nlines=$(awk 'END{print NR}' filename)
七、使用Linux命令修改文本文件中的指定数据?
sed 可以直接修改文件的内容,不必使用管道命令或数据流重导向!不过,由於这个动作会直接修改到原始的文件,所以请你千万不要随便拿系统配置来测试!
替换文本中的字符串:(将book替换为books)
s 替换指定字符
sed 's/book/books/' filename
/g 标记会替换每一行中的所有匹配:
sed 's/book/books/g' filename
八、如果一个rdd有300个分区,如何将它变成3000个?
思路:重分区
1.coalesce
defcoalesce(numPartitions: Int, shuffle: Boolean = false)(implicit ord:Ordering[T] = null): RDD[T]
该函数用于将RDD进行重分区,使用HashPartitioner。
第一个参数为重分区的数目,第二个为是否进行shuffle,默认为false;
以下面的例子来看:
1. scala>var data = sc.textFile("/tmp/lxw1234/1.txt")
2. data: org.apache.spark.rdd.RDD[String]=MapPartitionsRDD[53] at textFile at :21
3.
4. scala> data.collect
5. res37:Array[String]=Array(hello world, hello spark, hello hive, hi spark)
6.
7. scala> data.partitions.size
8. res38:Int=2 //RDD data默认有两个分区
9.
10. scala>var rdd1 = data.coalesce(1)
11. rdd1: org.apache.spark.rdd.RDD[String]=CoalescedRDD[2] at coalesce at :23
12.
13. scala> rdd1.partitions.size
14. res1:Int=1 //rdd1的分区数为1
15.
16. scala>var rdd1 = data.coalesce(4)
17. rdd1: org.apache.spark.rdd.RDD[String]=CoalescedRDD[3] at coalesce at :23
18.
19. scala> rdd1.partitions.size
20. res2:Int=2 //如果重分区的数目大于原来的分区数,那么必须指定shuffle参数为true,否则,分区数不变。
21.
22. scala>var rdd1 = data.coalesce(4,true)
23. rdd1: org.apache.spark.rdd.RDD[String]=MapPartitionsRDD[7] at coalesce at :23
24.
25. scala> rdd1.partitions.size
26. res3:Int=4
2.repartition
defrepartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
该函数其实就是coalesce函数第二个参数为true的实现
1. scala>var rdd2 = data.repartition(1)
2. rdd2: org.apache.spark.rdd.RDD[String]=MapPartitionsRDD[11] at repartition at :23
3.
4. scala> rdd2.partitions.size
5. res4:Int=1
6.
7. scala>var rdd2 = data.repartition(4)
8. rdd2: org.apache.spark.rdd.RDD[String]=MapPartitionsRDD[15] at repartition at :23
9.
10. scala> rdd2.partitions.size
11. res5:Int=4