数据倾斜问题

数据倾斜:主要就是在处理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;
    }
}
相关推荐
V搜xhliang02467 小时前
机器人建模(URDF)与仿真配置
大数据·人工智能·深度学习·机器学习·自然语言处理·机器人
房产中介行业研习社7 小时前
2026年3月哪些房源管理系统功能全
大数据·运维·人工智能
玄微云8 小时前
2026年通用软件难适配,垂直店务系统反而更省心
大数据·云计算·软件需求
Elastic 中国社区官方博客8 小时前
Elastic 为什么捐赠其 OpenTelemetry PHP 发行版
大数据·开发语言·elasticsearch·搜索引擎·信息可视化·全文检索·php
方向研究9 小时前
ABS生产
大数据
TDengine (老段)9 小时前
TDengine 视图功能使用
大数据·数据库·servlet·时序数据库·tdengine·涛思数据
TDengine (老段)9 小时前
TDengine IDMP 运维指南 —— 部署架构
大数据·运维·数据库·架构·时序数据库·tdengine·涛思数据
utmhikari10 小时前
【测试人生】变更规则校验Agent研发的一些思路
大数据·人工智能·llm·agent·变更风险·openclaw
AC赳赳老秦10 小时前
DeepSeek优化多智能体指令:避免协同冲突,提升自动化流程稳定性
android·大数据·运维·人工智能·自然语言处理·自动化·deepseek
成长之路51411 小时前
【数据集】A股上市公司数字投资数据集-含代码(2000-2024年)
大数据