数据倾斜问题

数据倾斜:主要就是在处理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;
    }
}
相关推荐
武子康1 天前
大数据-236 离线数仓 - 会员指标验证、DataX 导出与广告业务 ODS/DWD/ADS 全流程
大数据·后端·apache hive
初次攀爬者2 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
武子康2 天前
大数据-235 离线数仓 - 实战:Flume+HDFS+Hive 搭建 ODS/DWD/DWS/ADS 会员分析链路
大数据·后端·apache hive
DianSan_ERP3 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet
够快云库3 天前
能源行业非结构化数据治理实战:从数据沼泽到智能资产
大数据·人工智能·机器学习·企业文件安全
AI周红伟3 天前
周红伟:智能体全栈构建实操:OpenClaw部署+Agent Skills+Seedance+RAG从入门到实战
大数据·人工智能·大模型·智能体
B站计算机毕业设计超人3 天前
计算机毕业设计Django+Vue.js高考推荐系统 高考可视化 大数据毕业设计(源码+LW文档+PPT+详细讲解)
大数据·vue.js·hadoop·django·毕业设计·课程设计·推荐算法
计算机程序猿学长3 天前
大数据毕业设计-基于django的音乐网站数据分析管理系统的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)
大数据·django·课程设计
B站计算机毕业设计超人3 天前
计算机毕业设计Django+Vue.js音乐推荐系统 音乐可视化 大数据毕业设计 (源码+文档+PPT+讲解)
大数据·vue.js·hadoop·python·spark·django·课程设计
十月南城3 天前
数据湖技术对比——Iceberg、Hudi、Delta的表格格式与维护策略
大数据·数据库·数据仓库·hive·hadoop·spark