深入认识hive 以mapreduce 为计算引擎时Mapper 和Reducer的设置
运营团队反映,公司广告业务的日活用户数据量偶尔呈剧烈下降趋势,同时出现用户数低于日活用户数据的问题,后来查看离线解析任务,执行过程正常但是数据的加载却除了问题——部分文件并没有加载到操作型数据库(提供数据展示的库,infobright)
问题分析:
由于load 数据时只加载了000000_0 文件,所以导致数据库中数据不齐全,同时产生的结果文件数量和Reducer 的个数相关。
简单说就是用于描述数据的输入格式,
@Public @Stable public interface InputFormat{ InputSplit[] getSplits(JobConf var1, int var2) throws IOException; RecordReader getRecordReader(InputSplit var1, JobConf var2, Reporter var3) throws IOException; } 从源码可以看出,该接口下的两个方法的功能分别是将输入的文件分片(splits)和将输入的splits 转化成key-value 数据, hive> set hive.input.format; hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat 也可通过hive-site.xml 配置文件可确认使用的那种InputFormat,也可以设置为其他的输入格式 默认使用的是CombineHiveInputFormat,其作用是将多个小文件组合成一个split 使用一个Mapper 处理,避免因太多的小文件生成大量任务,耗用资源。 hive.input.format org.apache.hadoop.hive.ql.io.CombineHiveInputFormat The default input format. Set this to HiveInputFormat if you encounter problems with CombineHiveInputFormat.
如果 hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat,采用文件分割算法。 文件切分算法主要用于确定InputSplit的个数以及每个InputSplit对应的数据段,FileInputSplit以文件为单位切分生成InputSplit。有三个属性值来确定InputSplit的个数: goalSize:该值由 totalSize/numSplits 来确定 InputSplit 的长度,它是根据用户的期望的 InputSplit 个数计算出来的;numSplits 为用户设定的 Map Task 的个数,默认为1。 minSize:由配置参数 mapred.min.split.size(或者 mapreduce.input.fileinputformat.split.minsize)决定的 InputForma t的最小长度,默认为1。 blockSize:HDFS 中的文件存储块block的大小,不同版本的hadoop的默认不一样,2.0 之前的hadoop 的blocksize 默认是64MB,2.0 后的默认值是128M。 numSplits=mapred.map.tasks 或者 mapreduce.job.maps 最终: InputFormat 分片的长度:splitSize = max{minSize,min{goalSize,blockSize}} 如果hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat,采用host 选择算法。 long left = locations[i].getLength(); long myOffset = locations[i].getOffset(); long myLength = 0; do { if (maxSize == 0) { myLength = left; } else { if (left > maxSize && left < 2 * maxSize) { myLength = left / 2; } else { myLength = Math.min(maxSize, left); } } OneBlockInfo oneblock = new OneBlockInfo(path, myOffset, myLength, locations[i].getHosts(), locations[i] .getTopologyPaths()); left -= myLength; myOffset += myLength; blocksList.add(oneblock); } while (left > 0); 根据以下几个参数确定splitsize hive> set mapred.min.split.size; mapred.min.split.size=1 hive> set mapred.max.split.size; mapred.max.split.size=67108864 hive> set mapred.min.split.size.per.rack; mapred.min.split.size.per.rack=1 hive> set mapred.min.split.size.per.node; mapred.min.split.size.per.node=1 hive> set dfs.blocksize; dfs.blocksize=134217728
参考别人的测试用例:
如果 hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat,则这时候的参数如下:
hive> set mapred.min.split.size; mapred.min.split.size=1 hive> set mapred.map.tasks; mapred.map.tasks=2 hive> set dfs.blocksize; dfs.blocksize=134217728
上面参数中 mapred.map.tasks 为2,dfs.blocksize(使用的是 cdh-4.3.0 版本的 hadoop,这里 block 和 size 之间没有逗号)为128M。
假设有一个文件为200M,则按上面 HiveInputFormat 的 split 算法:
1、文件总大小为200M,goalSize=200M /2 =100M,minSize=1 ,splitSize = max{1,min{100M,128M}} =100M
2、200M / 100M >1.1,故第一块大小为100M
3、剩下文件大小为100M,小于128M,故第二块大小为100M。
如果 hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat,则这时候的参数如下:
hive> set mapred.min.split.size; mapred.min.split.size=1 hive> set mapred.max.split.size; mapred.max.split.size=67108864 hive> set mapred.min.split.size.per.rack; mapred.min.split.size.per.rack=1 hive> set mapred.min.split.size.per.node; mapred.min.split.size.per.node=1 hive> set dfs.blocksize; dfs.blocksize=134217728
上面参数中 mapred.max.split.size 为64M,dfs.blocksize 为128M。
假设有一个文件为200M,则按上面 CombineHiveInputFormat 的 split 算法:
1、128M < 200M <128M X 2,故第一个block大小为128M
2、剩下文件大小为200M-128M=72M,72M < 128M,故第二块大小为72M
设置为指定大小 set mapred.reduce.tasks=10
如果reducer 数量没有指定,默认为-1,执行任务是hive 则会计算reducer 的数量
hive.exec.reducers.bytes.per.reducer 每个reducer 的默认大小默认1GB。(hive 1.2 默认是256 M)
hive.exec.reducers.max 每个任务最大的reduce数,默认为999 (hive 1.2 默认是1009)
reducerNum=min(参数2,输入文件大小/参数1)
关于hive 执行sql 到处数据文件时,文件的个数与hive 执行到最后stage的reducer 的数量相关。reducer 的数量如果设置过大,怎会生成的小文件就会更多,如果reducer 的数量设置的太少,可能导致所有的一个reducer 处理更多的文件,易引起OOM。
因理解不到位,在此分享使用过程遇到的问题,共勉。