数据倾斜问题

数据倾斜:主要就是在处理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;
    }
}
相关推荐
Data跳动2 小时前
Spark内存都消耗在哪里了?
大数据·分布式·spark
woshiabc1113 小时前
windows安装Elasticsearch及增删改查操作
大数据·elasticsearch·搜索引擎
lucky_syq4 小时前
Saprk和Flink的区别
大数据·flink
lucky_syq4 小时前
流式处理,为什么Flink比Spark Streaming好?
大数据·flink·spark
袋鼠云数栈4 小时前
深入浅出Flink CEP丨如何通过Flink SQL作业动态更新Flink CEP作业
大数据
Java程序之猿4 小时前
微服务分布式(一、项目初始化)
分布式·微服务·架构
清平乐的技术专栏4 小时前
Hive SQL 查询所有函数
hive·hadoop·sql
来一杯龙舌兰5 小时前
【RabbitMQ】RabbitMQ保证消息不丢失的N种策略的思想总结
分布式·rabbitmq·ruby·持久化·ack·消息确认
小白学大数据5 小时前
如何使用Selenium处理JavaScript动态加载的内容?
大数据·javascript·爬虫·selenium·测试工具
15年网络推广青哥5 小时前
国际抖音TikTok矩阵运营的关键要素有哪些?
大数据·人工智能·矩阵