在map与reduce阶段有时候可能会出现各种非理想化的情景,导致数据计算和处理时会遇到一些瓶颈或问题,这里就列出来一些可参考的调优方案:
Map阶段调优:
- 自定义分区,减少数据倾斜;可以自定义一个类,继承Partitioner类,重写getPartition方法。这么做是因为有些业务场景中可能某个相同的key值对应的数据量太大,造成某个ReduceTask承担较大压力,这时我们可以通过自定义分区的方法,将key值在原来的基础上加上一些随机数,比如1,2,3,这样就可以将原来相同的key分放在不同的分区中,减轻某个单一ReduceTask的压力,以防止数据倾斜。
- 减少溢写次数;可以通过增大环形缓冲区的大小(mapreduce.task.io.sort.mb,默认是100MB)和增大环形缓冲区溢写的阈值(mapreduce.map.sort.spill.percent,默认是80%)来实现。因为这两个配置增大之后,就会减少溢写的次数,这样就会减少溢写文件,从而也就会减少后面合并溢写文件的次数等等后续操作,效率就会提高。
- 提高每次Merge的个数;通过提高mapreduce.task.io.sort.factor参数的值,可以使每次merge的spill个数增多,提高效率。
- 使用Snappy或LZO对mapTask写磁盘的数据进行压缩;由于map阶段产生的数据会写入磁盘,等待Reduce端来拉取数据,而如果这个数据量太大,在网络传输过程中就会消耗较多的资源,那么此时我们可以先将map阶段写出的数据进行压缩,以提高网络传输的效率。
- 提高MapTask内存上限(mapreduce.map.memory.mb,默认是1024MB);该参数表示一个MapTask可使用的资源上限,如果超过该上限,那么就会被杀死。所以在我们内存资源可用的情况下,可以调高该值,在调整该值时,原则上参考的是1024MB的内存可以处理128MB的数据。
- 增大mapreduce.map.java.opts的值;该参数是用来控制java中MapTask堆内存的大小,通常是和mapreduce.map.memory.mb的值相同。
- 增大CPU核数(mapreduce.map.cpu.vcores);该参数表示每个MapTask最多可使用的cpu核数,默认是1;对于计算密集型任务,我们可以适当增加该配置来提高计算效率。
Reduce阶段调优:
- 增大ReduceTask拉取map输出结果的并行度(mapreduce.reduce.shuffle.parallelcopies,默认是5),该参数表示每个ReduceTask去拉取map端输出结果时,可以并行拉取多少个map的输出。增大该参数,会使Reduce同时处理数据的效率提高。
- 提高内存缓冲区的占比(mapreduce.reduce.shuffle.input.buffer.percent,默认是0.7,表示内存缓冲区占内存的百分比);在reduce端,将map输出数据拉取过来时,会先放在内存缓冲区中,调大该参数,可以使内存缓冲区中同时容纳的数据变多,以提高效率。
- 调大缓冲区数据溢写的占比(mapreduce.reduce.shuffle.merge.percent,默认是0.66);该参数表示当reduce端的shuffle阶段中内存缓冲区的数据达到了缓冲区的多少占比时,开始溢写,提高该参数使,就可以减少合并溢写的次数,提高效率。
- 调大每个ReduceTask的内存上限(mapreduce.reduce.memory.mb,默认是1024MB);通过调大该参数,可以使ReduceTask占用更多的内存,处理更多的数据。
- 调大ReduceTask堆内存的上限(mapreduce.reduce.java.opts),该值要与mapreduce.reduce.memory.mb保持一致。
- 提高每个ReduceTask分配的cpu核数上限(mapreduce.reduce.cpu.vcores,默认是1);由于ReduceTask要处理map输出的所有结果,所以通常情况下在Reduce端的计算较大,需要将该配置调整到2-4个cpu。
- 非必要,尽可能地不使用Reduce;因为一旦需要reduce,那么就会进入shuffle阶段,引起一些列的归并排序、合并等等操作,消耗资源。
以上参数的配置都应结合自己实际的硬件资源以及业务需求,按需调整。