数据倾斜问题

数据倾斜:主要就是在处理MR任务的时候,某个reduce的数据处理量比另外一些的reduce的数据量要大得多,其他reduce几乎不处理,这样的现象就是数据倾斜。

官方解释:数据倾斜指的是在数据处理过程中,由于某些键的分布极度不均匀,导致某些节点处理的数据量显著多于其他节点。‌这种情况会引发性能瓶颈,阻碍任务的并行执行,增加作业的整体执行时间。在Hadoop的MapReduce作业中,数据倾斜尤为明显,因为它会导致某些Reduce任务处理的数据量远大于其他任务,从而造成集群整体处理效率低下的问题。

这里比如有一个文本数据,里面内容全是:hadoop, hadoop, hadoop,hadoop ....,假设有800万条数据,这样更容易显示数据倾斜的效果,里面都是同样的单词,默认的hash取余分区的方法,明显不太适合,所以我们要自定义分区,重写分区方法。以及设置多个reduce,这里我设置为3, 主要就是对数据倾斜的key进行一个增加后缀的方法,以及在Map阶段就增加后缀,实现过程是将每个hadoop都进行增加后缀,刚开始会全部默认存放到第一个分区里(0分区),然后写到分区后,自定义分区方法SkewPartitioner就会对里面的数据进行分析,如果后缀是1就分到1区里面,一共就0、1、2三个分区,以此来解决数据倾斜的问题。

注意:在Job端进行自定义分区器的设置:job,setPartitionerClass(SkewPartitioner.class)

具体代码如下:

java 复制代码
package com.shujia.mr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class Demo05SkewDataMR {
    public static class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
        @Override
        protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
            String line = value.toString();
            // 将每一行数据按照逗号/空格进行切分
            for (String word : line.split("[,\\s]")) {
                // 使用context.write将数据发送到下游
                // 将每个单词变成 单词,1 形式
                // 对数据倾斜的Key加上随机后缀
                if ("hadoop".equals(word)) {
                    // 随机生成 0 1 2
                    int prefix = (int) (Math.random() * 3);
                    context.write(new Text(word + "_" + prefix), new IntWritable(1));
                } else {
                    context.write(new Text(word), new IntWritable(1));
                }
            }
        }
    }


    public static class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
            // 统计每个单词的数量
            int cnt = 0;
            for (IntWritable value : values) {
                cnt = cnt + value.get();
            }
            context.write(key, new IntWritable(cnt));
        }
    }

    // Driver端:组装(调度)及配置任务
    // 可以通过args接收参数
    // 本任务接收两个参数:输入路径、输出路径
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        Configuration conf = new Configuration();
        // 创建Job
        Job job = Job.getInstance(conf);

        // 配置任务
        job.setJobName("Demo05SkewDataMR");
        job.setJarByClass(Demo05SkewDataMR.class);

        // 设置自定义分区器
        job.setPartitionerClass(SkewPartitioner.class);

        // 手动设置Reduce的数量
        // 最终输出到HDFS的文件数量等于Reduce的数量
        job.setNumReduceTasks(3);

        // 配置Map端
        job.setMapperClass(MyMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 配置Reduce端
        job.setReducerClass(MyReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 验证args的长度
        if (args.length != 2) {
            System.out.println("请传入输入输出目录!");
            return;
        }

        String input = args[0];
        String output = args[1];

        // 配置输入输出的路径
        FileInputFormat.addInputPath(job, new Path(input));

        Path ouputPath = new Path(output);
        // 通过FileSystem来实现覆盖写入
        FileSystem fs = FileSystem.get(conf);
        if (fs.exists(ouputPath)) {
            fs.delete(ouputPath, true);
        }
        // 该目录不能存在,会自动创建,如果已存在则会直接报错
        FileOutputFormat.setOutputPath(job, ouputPath);

        // 启动任务
        // 等待任务的完成
        job.waitForCompletion(true);


    }
}

// 自定义分区:在Map阶段给key加上随机后缀,基于后缀返回不同的分区编号
class SkewPartitioner extends Partitioner<Text, IntWritable> {

    @Override
    public int getPartition(Text text, IntWritable intWritable, int numPartitions) {
        String key = text.toString();
        int partitions = 0;
        // 只对数据倾斜的key做特殊处理
        if ("hadoop".equals(key.split("_")[0])) {
            switch (key) {
//                case "hadoop_0":
//                    partitions = 0;
//                    break;
                case "hadoop_1":
                    partitions = 1;
                    break;
                case "hadoop_2":
                    partitions = 2;
                    break;
            }
        } else {
            // 正常的key还是按照默认的Hash取余进行分区
            partitions = (key.hashCode() & Integer.MAX_VALUE) % numPartitions;
        }
        return partitions;
    }
}
相关推荐
小马爱打代码13 分钟前
Elasticsearch简介与实操
大数据·elasticsearch·搜索引擎
只因在人海中多看了你一眼3 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
zhixingheyi_tian6 小时前
Spark 之 Aggregate
大数据·分布式·spark
PersistJiao6 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
求积分不加C7 小时前
-bash: ./kafka-topics.sh: No such file or directory--解决方案
分布式·kafka
nathan05297 小时前
javaer快速上手kafka
分布式·kafka
宅小海9 小时前
scala String
大数据·开发语言·scala
小白的白是白痴的白9 小时前
11.17 Scala练习:梦想清单管理
大数据
java1234_小锋9 小时前
Elasticsearch是如何实现Master选举的?
大数据·elasticsearch·搜索引擎
谭震鸿11 小时前
Zookeeper集群搭建Centos环境下
分布式·zookeeper·centos