Hadoop

概述

代码地址:https://github.com/liubin95/hadoop

组成部分

HDFS

Hadoop Distributed File System :Hadoop分布式文件存储系统

Map Reduce

批处理数据

Yarn

资源调度组件

优缺点

  • 高拓展:可以通过增加廉价的机器拓展集群
  • 高可靠:数据多节点,多副本,不会丢失
  • 高容错:任务失败可以重启
  • 高效:Map Reduce并行执行,效率高

  • 高延迟:Map Reduce 是离线计算,而且是磁盘计算,延迟高
  • 不适合存储大量小文件
    • NameNode都会消耗内存来存储文件目录和块信息。所以,过多的小文件会消耗大量的内存
    • Mapper阶段默认情况下:每一个小文件会被切成一片,每一片文件会创建一个MapperTask,会消耗大量资源
  • 不支持文件的修改:仅支持文件的追加操作,不支持随机修改
  • 不擅长有向图计算:前一个Map Reduce任务的输出作为后一个Map Recuce的输入。这样会产生大量的IO操作

配置

  • 优先级从高到低。前面覆盖后面的
  • 代码指定
package com.liubin.hadoop.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.LocatedFileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.RemoteIterator;
import org.apache.hadoop.io.IOUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * HDFSClient.
 *
 * @author huawei.
 * @version 0.0.1.
 * @serial 2021-03-26 : base version.
 */
public class HDFSClient {

  private static final Logger LOGGER = LoggerFactory.getLogger(HDFSClient.class);

  private static FileSystem fileSystem;
  @BeforeAll
  static void beforeAll() throws URISyntaxException, IOException, InterruptedException {
    final Configuration configuration = new Configuration();
    // 配置副本数
    configuration.set("dfs.replication", "1");
    fileSystem = FileSystem.get(new URI("hdfs://localhost:9000"), configuration, "liubin");
    LOGGER.info("开启链接");
  }

  @AfterAll
  static void afterAll() throws IOException {
    fileSystem.close();
    LOGGER.info("关闭链接");
  }
}

  • 客户端配置D:\hadoop-3.2.1\etc\hadoop\core-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
	<property>
		<name>dfs.replication</name>
		<value>1</value>
	</property>
</configuration>
  • 服务端配置/opt/hadoop-3.2.1/etc/hadoop/core-site.xml,同客户端配置方式
  • 默认值\hadoop-hdfs\3.2.1\hadoop-hdfs-3.2.1.jar/hdfs-default.xml查询地址
    默认值查询

HDFS

组成部分

NameNode 一主

  • 处理客户端请求
  • 管理block数据的映射信息(类似索引)
  • 管理HDFS命名空间
  • 配置副本策略
  • 可以借助Zookeeper 实现HA(高可用),NameNode HA 官网方案

DataNode 多从

  • 存储和管理block数据
  • 执行具体的文件读写操作
  • 定期向NameNode汇报存储的block数据信息(心跳)

Secondary NameNode

  • 不是NameNode热备,不能直接替换NameNode工作
  • 辅助NameNode工作,比如定期合并Fsimage 和 Edits ,并推送给NameNode

概述

  • 文件是分块存储的(block),块大小可以配置,dfs.blocksize,默认128M。大小的设置取决于磁盘传输速率
  • 多副本,dfs.replication,副本数最大就是dataNode数量,单节点多副本无意义

命令

hadoop fs <args>
# 例,列出/users下文件和目录
hadoop fs -ls /users

操作和Linux文件系统命令类似,具体参考官网


Map Reduce

在这里插入图片描述

概述

组成部分

Map阶段

  • 代码靠近数据:通常,计算节点和存储节点是相同的。也就是说,MapReduce框架和Hadoop分布式文件系统在同一组节点上运行
InputFormat
  • 对文件进行split(切片),默认的切片规则会将每一个文件切一片,对应一个MapTask,如果小文件过多,会极度消耗资源
  • 对切片的每一键值对,调用Mapper中的map(WritableComparable,Writable,Context)方法
常用类型
  • TextInputFormat 默认输入Format,<数字(文件中的位置),Text(一行数据)>
  • KeyValueTextInputFormat 默认\t分割一行,<Text(\t之前部分),Text(\t之后部分>
  • NLineInputFormatTextInputFormat 一样,但是可以控制多少行(setNumLinesPerSplit)切一片
  • CombineTextInputFormat 处理小文件, 具体见下文
  • 自定义,见小文件处理中SequenceFile 压缩例子
Mapper
  • 通过Job.setMapperClass(Class)方法传递给作业
  • 通过对context.write(WritableComparable,Writable)的调用来输出数据

Shuffle阶段

  • Map阶段和Reduce阶段中间的过程即Shuffle
Partitioner 分区

在这里插入图片描述

  • 将map阶段的数据,重新分区,相同分区的交给同一个ReduceTask处理
  • 通过setNumReduceTasks设置ReduceTask的数量。可以实现将同一类的数据保存到一个文件
    // 设置reduce的数量
    job.setNumReduceTasks(4);
  • NumReduceTasks等于分区数量才会生成对应数量的文件
  • NumReduceTasks大于分区数量时,会生成空文件
  • NumReduceTasks小于分区数量时,会报错
WritableComparable 排序
  • Hadoop 会默认对数据的Key进行排序
  • 实现WritableComparable接口
  • Map阶段结束后,数据会写到缓存区。达到阈值后进行一次排序(快排),然后溢写到磁盘。所有数据处理完,对所有有序的文件进行一次合并排序(归并排序)
  • Reduce阶段结束后,会对所有数据进行一次排序(归并排序)
  • 只能在单个文件中进行排序
  @Override
  public int compareTo(FlowComparatorBean o) {
    if (o.getTotalFlow() > this.totalFlow) {
      return -1;
    } else if (o.getTotalFlow() < this.totalFlow) {
      return 1;
    }
    return 0;
  }
Combiner 合并
  • 在Map阶段之后,对MapTask的结果进行一次局部汇总
  • 减少和ReduceTask的网络传输,算是一种本地的reduce
  • 不适合平均值操作
  • 继承Reducer即可,setCombinerClass 设置使用的combiner
    // 使用Combiner 优化,使得进入reduce的数据量更少
    job.setCombinerClass(WordCountReducer.class);
GroupingComparator 分组
  • 分区决定数据去哪一个Reduce,分组决定数据的Key
  • 将数据分组成reduce入参。按Key和规则,收集一组Iterable<Writeable>处理
  @Override
  protected void reduce(FlowComparatorBean key, Iterable<Text> values, Context context)
      throws IOException, InterruptedException {

    context.write(values.iterator().next(), key);
  }
  • 继承WritableComparator,在job.setGroupingComparatorClass(class);设置
package com.liubin.hadoop.mapreduce.order;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

import java.util.Objects;

/**
 * OrderComparator.
 *
 * @author 刘斌
 * @version 0.0.1
 * @serial 2021-04-02 : base version.
 */
public class OrderGroupingComparator extends WritableComparator {

  public OrderGroupingComparator() {
    super(OrderComparator.class, true);
  }

  @Override
  public int compare(WritableComparable a, WritableComparable b) {
    // id相同,是相同的Key
    final OrderComparator a1 = (OrderComparator) a;
    final OrderComparator b1 = (OrderComparator) b;
    if (Objects.equals(a1.getOrderId(), b1.getOrderId())) {
      return 0;
    } else {
      return 1;
    }
  }
}

Reduce阶段

OutputFormat
  • 自定义reduce结果的输出形式
  • 实现方式和InputFormat类似
Reduce
  • 通过Job.setReducerClass(Class)方法传递给作业
  • 注意入参是<key,Iterable<Writeable>> 形式,注意循环

Join

  • 将不同文件的数据聚合在一起
Reduce Join
  • 在Reduce阶段将数据拼装
  • 通过Mapper 的setup方法,获取数据来源文件。注意:即使没有的字段也要设置默认值,否则序列化会报错
  @Override
  protected void setup(Context context) throws IOException, InterruptedException {
    // 获取文件名
    final FileSplit fileSplit = (FileSplit) context.getInputSplit();
    // 不同文件设置不同的标识位
    this.name = fileSplit.getPath().getName();
  }
  • 将关联的字段作为Key,这样数据就会分到一个Reduce的一组键值对
  • 根据表示位获取到不同的对象,最后拼装数据。注意:对象的拷贝
问题
  • 数据倾斜:大量数据集中在少数的ReduceTask中
  • Reduce 负载太大
Map Join
  • 小表缓存到内存中,提前处理,减少Reduce的负载和数据倾斜
  • 设置缓存job.setCacheFiles(new URI[] {new URI("file:///D:/tmp/input/join/pd.txt")});
  • 在Map阶段 setup方法中读取缓存文件
  @Override
  protected void setup(Context context) throws IOException, InterruptedException {
    // 获取缓存的文件
    final URI cacheFile = context.getCacheFiles()[0];
    final BufferedReader bufferedReader =
        new BufferedReader(
            new InputStreamReader(
                new FileInputStream(cacheFile.getPath()), StandardCharsets.UTF_8));
    String line;
    // 读取文件
    while (StringUtils.isNoneEmpty(line = bufferedReader.readLine())) {
      // 03	苹果
      final String[] split = line.split("\t");
      // 缓存到内存
      this.idAndName.put(split[0], split[1]);
    }
    IOUtils.closeStream(bufferedReader);
  }
  • map方法中使用缓存

计数器

  • Hadoop默认维护若干计数器,以描述多项指标
使用
	// 计数器(组名,计数器名)
    final Counter counterTrue = context.getCounter("map", "true");
    //+1
    counterTrue.increment(1L);

命令

# jar包路径,输入路径,输出路径
hadoop jar ./share/hadoop/mapreduce/map-reduce-1.0-SNAPSHOT.jar /users/liubin/input /users/liubin/output/0401

序列化

  • Java 自带的Serializable 是重量级序列化,不适合大量io和网络传输操作。所以Hadoop自定义了writable序列化接口
  • writereadFields字段顺序要保持一致

Yarn

组成部分

在这里插入图片描述

ResourceManager 集群一个

  • 管理集群中的所有资源

NodeManager 一个机器一个

  • 负责每一台机器的资源(cpu,内存,磁盘,网络)监视

ApplicationMaster 一个Job一个

  • 向ResourceManager 申请Job需要的资源
  • Job的监控和容错

Container 资源的抽象

  • 资源的封装(类似于yarn资源的计量单位)

提交任务流程

  1. client(客户端)向ResourceManager 提交Job(即MR程序)
  2. ResourceManager 为Job分配一个Container ,并要求所在的NodeManager 在Container 中启动ApplicationMaster
  3. ApplicationMaster 启动后注册到ResourceManager ,然后重复步骤4-7
  4. ApplicationMaster 通过RPC协议向ResourceManager 申请资源
  5. 与资源所在的NodeManager 通信,要求它启动Task(经过ApplicationMaster 切分的Job,即MapTask、ReduceTask)
  6. NodeManager 为Task设置运行时环境(包括环境变量、JAR包、二进制程序等),然后启动Task
  7. Task通过RPC协议向ApplicationMaster 汇报自己的状态和进度,以便ApplicationMaster 监控和容错
  8. Job行完成后,ApplicationMaster向ResourceManager注销并关闭自己

小文件处理

Har 压缩

将一组文件压缩为一个*.har格式的文件,减少NameNode内存消耗

压缩

hadoop archive -archiveName name -p <parent> [-r <replication factor>] <src>* <dest>
# 例,将/users/liubin/input下文件压缩到/users/liubin/output/input.har
hadoop archive -archiveName input.har /users/liubin/input /users/liubin/output/
  • name:指定存档文件名,其名应该以 .har 为后缀。
  • -p :表示父目录,指定该参数后,后面的src可使用相对路径。dest使用相对路径时,其相对基础目录为计算用户目录而非-p指定的目录。
  • -r 指示所需的复制因子;如果未指定此可选参数,则使用10的复制因子。所谓复制因子即为map任务的个数。
  • src:源路径:可以指定指定一个,也可以指定多个,空格分隔
  • dest:目标路径:最后一个路径为存放路径。

使用压缩文件

通过har 协议使用har压缩文件

hadoop fs -ls har:///users/liubin/output/input.har

SequenceFile 压缩

  • 将小文件合并成<文件全路径,文件内容(二进制)>的形式储存

自定义MR程序实现

  • 自定义FileInputFormat,设置不分区,使用自定义的RecordReader
package com.liubin.hadoop.mapreduce.wholefile;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import java.io.IOException;

/**
 * WholeFileInputFormat.
 *
 * @author 刘斌
 * @version 0.0.1
 * @serial 2021-04-01 : base version.
 */
public class WholeFileInputFormat extends FileInputFormat<Text, BytesWritable> {
  @Override
  protected boolean isSplitable(JobContext context, Path filename) {
	 //不分区,一次读取一个文件
    return false;
  }

  @Override
  public RecordReader<Text, BytesWritable> createRecordReader(
      InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
    return new WholeFileRecordReader();
  }
}
  • 自定义RecordReader,将每一个文件读取成<文件名,文件流>的形式
package com.liubin.hadoop.mapreduce.wholefile;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;

import java.io.IOException;

/**
 * WholeFileRecordReader. 每一个文件执行一次,新对象
 *
 * @author 刘斌
 * @version 0.0.1
 * @serial 2021-04-01 : base version.
 */
public class WholeFileRecordReader extends RecordReader<Text, BytesWritable> {
  private FileSplit fileSplit;
  private Configuration configuration;
  private final Text text = new Text();
  private final BytesWritable bytesWritable = new BytesWritable();

  private boolean isProgress = true;

  @Override
  public void initialize(InputSplit split, TaskAttemptContext context)
      throws IOException, InterruptedException {
    this.fileSplit = (FileSplit) split;
    this.configuration = context.getConfiguration();
  }

  @Override
  public boolean nextKeyValue() throws IOException, InterruptedException {
    if (isProgress) {
      // 缓存区
      byte[] bytes = new byte[(int) fileSplit.getLength()];
      // 读取的文件path
      final Path path = fileSplit.getPath();
      // Hadoop 文件系统实例
      final FileSystem fileSystem = path.getFileSystem(configuration);
      // 获取文件的流
      final FSDataInputStream inputStream = fileSystem.open(path);
      // 读取到缓存区
      IOUtils.readFully(inputStream, bytes, 0, (int) fileSplit.getLength());
      // 设置到value
      this.bytesWritable.set(bytes, 0, bytes.length);
      // 设置key
      this.text.set(path.toString());
      // 关闭输入流
      IOUtils.closeStream(inputStream);
      this.isProgress = false;
      return true;
    }
    return false;
  }

  @Override
  public Text getCurrentKey() throws IOException, InterruptedException {
    return this.text;
  }

  @Override
  public BytesWritable getCurrentValue() throws IOException, InterruptedException {
    return this.bytesWritable;
  }

  @Override
  public float getProgress() throws IOException, InterruptedException {
    return 0;
  }

  @Override
  public void close() throws IOException {}
}
  • Driver 类设置参数
    // 设置使用自定义的输入类
    job.setInputFormatClass(WholeFileInputFormat.class);
    // 输出使用SequenceFileOutputFormat
    job.setOutputFormatClass(SequenceFileOutputFormat.class);
  • 使用时,需要自定义InputFormat读取文件

CombineTextInputFormat

  • 通过InputFormat减少小文件的mapper阶段的切片,从而减少MapperTask的数量,减少资源的浪费
  • 虚拟化划分:根据setMaxInputSplitSize将所有输入文件,重新虚拟分区
  • 切片 :根据虚拟分区,重新切片,分区小于setMaxInputSplitSize的会合并

使用

    final Configuration configuration = new Configuration();
    final Job job = Job.getInstance(configuration);
    // 使用CombineTextInputFormat处理小文件
	job.setInputFormatClass(CombineTextInputFormat.class);
    // 设置分区的最大值
    CombineTextInputFormat.setMaxInputSplitSize(job, 10 * 1024 * 1024);

JVM 重用

  • Map阶段结束后,JVM会继续执行其他Map。节约时间
  • 配置mapred-site.xmlmapreduce.job.jvm.numtasks

压缩

  • 可以在MapReduce任何阶段使用压缩
  • 计算密集型,少用压缩;IO密集型,多用压缩

类型

gzip

  • 适用于压缩后小于文件块大小的场景

Bzip2

  • 速度慢,压缩率高。适用于追求极致压缩率的场景

LZO

  • 适合于文件压缩后还大于块大小的场景

Snappy

  • 压缩速度极致快

位置

Map之前

  • 自动匹配拓展名,自动解压缩文件
  • 配置方式:core-site.xmlio.compression.codecs可以多种方式配置

Map 和Reduce之间

  • 选择速度快的压缩类型,比如:LZO
  • 配置方式:mapred-site.xml
    • mapreduce.map.output.compress:开启
    • mapreduce.map.output.compress.codec:指定压缩的类型

Reduce之后

  • 减少磁盘存储量
  • 也会减少下一个Job的输入量
  • 配置方式:mapred-site.xml
    • mapreduce.output.fileoutputformat.compress:开启
    • mapreduce.output.fileoutputformat.compress.codec:指定压缩类型
    • mapreduce.output.fileoutputformat.compress.type:如果是SequenceFiles,可以指定类型(NONE、RECORD、BLOCK)

相关链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值