Hadoop 个人学习记录

本文详细介绍了Hadoop的组成结构,包括MapReduce、Yarn和HDFS的架构及其工作流程。MapReduce负责分布式计算,Yarn作为资源调度框架管理计算资源,而HDFS则提供分布式存储。HDFS的读写流程、NN和SNN的工作机制也被详尽阐述。此外,文章还讨论了Hadoop的动态扩容和缩容、数据倾斜处理策略以及Shuffle过程。通过对Hadoop的理解,读者可以更好地掌握大数据处理的基础知识。
摘要由CSDN通过智能技术生成

1、Hadoop组成结构

 

1.1、MapReduce架构:分布式计算框架

  1. Map阶段:各个任务并行处理数据

  2. Reduce阶段:对map结果进行汇总

1.2、Yarn架构:资源调度框架

  1. NodeManager(NM):单个节点的leader,管理YARN集群中的每个节点

    • 管理单个节点的资源;

    • 处理来自ResourceManager的命令;

    • 处理来自ApplicationMaster的命令;

  2. ApplicationMaster(AM): 管理在YARN内运行的每个应用程序实例

    • 负责数据的切分,任务的监控与容错;

    • 为应用程序申请资源分配给内部任务;

  3. ResourceManager(RM):全局资源调度器,负责整个系统资源的管理和分配。

    • 处理客户端的请求,进行资源的分配与调度;

    • 监控NodeManager;

    • 启动或者监控ApplicationMaster;

  4. Container:任务资源管理器

    • 任务运行环境的资源管理;任务运行时,Yarn分配给AM的资源(内存、CPU、磁盘、网络等)时,RM为AM返回的资源便是用Container表示的,YARN会为每个任务分配一个Container,且该任务只能使用该Container中描述的资源。

1.3、HDFS架构:分布式存储框架

  1. NameNode(NN):master,管理资源文件元数据

    • 处理客户端的读写请求;

    • 管理HDFS的命名空间;

    • 配置副本策略;

    • 管理数据块(Block)的映射信息;

  2. DataNode (DN): slave,数据具体存储

    • 存储实际的数据块;

    • 执行数据块的读写操作;

  3. Secondary NameNode(SNN):分担NN的压力

    • 辅助NameNode ,分担工作量,定期合并Fsimage 和 Edits,合并成新的Fsimage,推送给NameNode;

    • 辅助NameNode恢复;

2、HDFS读写流程:

2.1、数据写入:

 

  1. client向nameNode 请求文件上传,nameNode检查目标文件是否存在,父目录是否存在;

  2. nameNode返回是否可以上传;

  3. client对文件切分,请求第一个block传输到哪些DataNode服务器上;

  4. NameNode 返回3个DataNode 服务器DataNode1,DataNode2,DataNode3;

  5. client请求3台中的一台DataNode1(网络拓扑最近的一台)上传数据(RPC,建立pipeline),DataNode1 收到请求继续调用DataNode2 ,然后DataNode2 调用DataNode3 ,将整个pipeline建立完成,然后逐级返回客户端;

  6. dn1、dn2、dn3逐级应答客户端。

  7. Client开始往DataNode 1上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位。写入的时候DataNode会进行数据校验。DataNode 1收到一个packet就会传给DataNode 2,DataNode 2传给DataNode 3,DataNode 1每传一个packet会放入一个应答队列等待应答;

  8. 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block到服务器。(重复执行3-7步);

2.2、数据读取:

  1. 客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址;

  2. 挑选一台DataNode(网络拓扑就近原则,然后随机)服务器,请求建立socket流读取数据;

  3. DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验);

  4. 客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。

3、NN和2NN工作机制

Fsimage:NameNode内存中元数据序列化后形成的文件,在 /data/tmp/dfs/name/current目录下生成

fsimage_0000000000000000

fsimage_0000000000000000.md5

seen_txid

VERSION

  1. Fsimage:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统所有的目录和文件inode的序列化信息。

  2. Edits:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作,会被优先记录到Edits文件中。

  3. seen_txid:文件保存的是一个数字,就是最后一个edits_的数字;

  4. 每次NameNode启动的时候会将Fsimage文件读入内存,加载Edits里的更新操作,保证内存的元数据信息是最新的、同步的,可以看成NameNode启动的时候,将Fsimage和Edits文件进行了合并。

工作机制:

  • Edits:记录客户端更新元数据信息的每一步操作(可通过Edits运算出元数据)。

  • NameNode启动时,先滚动Edits并生成一个空的edits.inprogress,然后加载Edits和Fsimage到内存中,此时NameNode内存就持有最新的元数据信息。Client开始对NameNode发送元数据的增删改的请求,这些请求的操作首先会被记录到edits.inprogress中(查询元数据的操作不会被记录在Edits中,因为查询操作不会更改元数据信息),如果此时NameNode挂掉,重启后会从Edits中读取元数据的信息。然后,NameNode会在内存中执行元数据的增删改的操作。

  • 由于Edits中记录的操作会越来越多,Edits文件会越来越大,导致NameNode在启动加载Edits时会很慢,所以需要对Edits和Fsimage进行合并(所谓合并,就是将Edits和Fsimage加载到内存中,照着Edits中的操作一步步执行,最终形成新的Fsimage)。SecondaryNameNode的作用就是帮助NameNode进行Edits和Fsimage的合并工作。

  • SecondaryNameNode首先会询问NameNode是否需要CheckPoint(触发CheckPoint需要满足两个条件中的任意一个,定时时间到和Edits中数据写满了)。直接带回NameNode是否检查结果。SecondaryNameNode执行CheckPoint操作,首先会让NameNode滚动Edits并生成一个空的edits.inprogress,滚动Edits的目的是给Edits打个标记,以后所有新的操作都写入edits.inprogress,其他未合并的Edits和Fsimage会拷贝到SecondaryNameNode的本地,然后将拷贝的Edits和Fsimage加载到内存中进行合并,生成fsimage.chkpoint,然后将fsimage.chkpoint拷贝给NameNode,重命名为Fsimage后替换掉原来的Fsimage。NameNode在启动时就只需要加载之前未合并的Edits和Fsimage即可,因为合并过的Edits中的元数据信息已经被记录在Fsimage中。

4、mapReduce工作流程

(1)、客户端执行submit()方法之前,会先获取待读取文件的信息;

(2)、将文件切片信息,jar包,job.xml 提交到yarn;

(3)、yarn根据job.xml ,启动切片数量相应的MapTask;

(4)、MapTask 调用inputFormat()方法读取HDFS文件,InputFormat()方法调用RecordRead()方法,默认TextInputFormat将数据以行首字母的偏移量为key,一行数据为value,传到map()方法;

(5)、map()方法做一些业务处理之后,将数据传输到分区方法中,将数据进行分区标注后,发送到环形缓冲区中。

(6)、环形缓冲区默认大小为100MB,达到80%后进行溢写;

(7)、溢写之前排序,按照key的字典序(快排);

(8)、溢写会产生大量溢写文件,会调用merge()方法,并用归并排序,默认10个溢写文件合并成一个大文件。

(9)、在不影响最终结果的前提下,可以先做一次combiner的操作;

(10)、等待所有的mapTask结束之后,会启动一定数量的reducerTask(根据分区数);

(11)、reduceTask会拉去map端数据到内存,内存不够时,写磁盘,待全部数据拉取完毕之后,会进行一次归并排序;

(12)、对并排序后的文件会在进行一次分组操作,将数据以组为单位发送到reduce()方法。

(13)、reduce()方法之后,做一些逻辑判断,调用outputFormat()方法,outputFormat调用RecordWirte()将数据以kv形式写到HDFS上。

5、Job提交流程:

5.1、Job提交源码:

Driver:
log.info("-----------job提交-------");
boolean b = job.waitForCompletion(true);
  job.submit();
   //1.建立连接
     this.connect();
       //(1)、创建job代理
        new Cluster(Job.this.getConfiguration());
           //(1.1)根据conf判断是本地还是yarn
             initialize(jobTrackAddr, conf);
   //2.提交job
    submitter.submitJobInternal(Job.this, Job.this.cluster);
       //(1)、创建给集群提交数据的Stag路径
          Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
       //(2)、获取jobid
          JobID jobId = this.submitClient.getNewJobID();
       //(3)、拷贝jar包到集群
         this.copyAndConfigureFiles(job, submitJobDir);
            //(3.1)、文件上传 
               rUploader.uploadFiles(job, jobSubmitDir);
       //(4)、计算切片,生产切片文件
         int maps = this.writeSplits(job, submitJobDir);
              //(4.1)、获取切片数据 
             List<InputSplit> splits = input.getSplits(job);
                //(4.1.1)、生成切片大小
                   long splitSize = this.computeSplitSize(blockSize, minSize, maxSize);
                      //(4.1.1.1)
                          Math.max(minSize, Math.min(maxSize, blockSize));
                //(4.1.2)、切片个数判断
                    (double)bytesRemaining / (double)splitSize > 1.1D;
       //(5)、向Stag路径写xml配置文件
          writeConf(conf, submitJobFile);
             //(5.1)、写xml
              conf.writeXml(out);
       //(6)、提交Job,返回提交状态
         submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());

5.2、FileInputFormat切片源码解析:

(1)、程序先找到源文件目录;

(2)、遍历该目录下的所有文件;

(3)、遍历文件:

a、获取文件大小 fs.sizeOf(xx.txt)

b、计算切片大小 ​ computeSplitSize( Math.max(minSize, Math.min(maxSize, blockSize)))=blockSize = 128M; ​ 默认情况下,切片大小 = blockSize ,2.x版本liunx =128M, windows = 32M;

c、文件开始切片,形成第一个切片:(double)bytesRemaining / (double)splitSize > 1.1D;

每次切片时,都需要判断切片完成剩下的部分是否大于块的1.1倍,不大于1.1倍就划分为1个切片。

d、将切片信息写到一个切片job.split中;

f、整个切片的核心过程是getSplits()方法;

g、InputSplit记录了切片的元数据信息,比如起始位置、长度以及所在的结点列表。

(4)、提交切片到Yarn上,Yarn上的MrAppMaster就可以根据切片规划文件计算开启MapTask数。

5.3、FileInputFormat参数

(1)、源码中计算切片公式

class FileInputFormat
  Math.max(minSize, Math.min(maxSize, blockSize));
mapreduce.input.fileinputformat.split.maxsize = 1L;
mapreduce.input.fileinputformat.split.maxsize =  9223372036854775807L; LongMaxValue
因此,默认情况下,切片大小=blockSize

(2)、切片大小设置

通过公式: Math.max(minSize, Math.min(maxSize, blockSize)); ​ 可以推出:调整 maxSize,使其比blockSize小,会让切片变小,切片大小 = maxSize ​ 调整 minSize,使其比blockSize大,会让切片变大,切片大小 = minSize

6、DataNode工作机制

  1. 一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。

  2. DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。

  3. 心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。

  4. 集群运行中可以安全加入和退出一些机器。

7、HDFS高可用

  1. Zookeeper为基础的集群上,NameNode 部署在2个节点上;两个NameNode 在ZK中谁先注册,谁就是Active,剩余的就是Standly状态;而同一时间只有一个NameNode 对外提供服务 ->Active NameNode。

  2. Zookeeper中有两个FailoverController,一个负责ANN的状态,一个监控SNN的状态,FailoverController通过心跳负责将监控信息保存在znode中。

  3. Standly NN负责同步Active NN中的元数据信息,也接收Activie NN的块报告,所有的DN上的Block 块的信息会同时发送给active NN 和 Standly NN,这样他们的信息能够同步。(JN进程进行主从复制)

  4. Edits日志只有Active状态的NameNode 节点可以进行写操作,但是两个NameNode 都可以读取Edits;

  5. 当Active NN节点发生故障,zookeeper 通过FailoverController 感知到,ZK会自动销毁Active znode(排它锁)。Standly NN的监控器检测到Active NN的znode被移除,通知NameNode 节点开始工作,并发送 kill -9 nameNode 进程号,杀掉进程,Standly NN切换到Active状态

8、HDFS 动态扩容、缩容

扩容:

  1. 新机器上修改hosts配置,设置免密登录(ssh-copy-id)

  2. 安装环境(jdk,hadoop)

  3. 在NameNode机器的hdfs-site.xml配置文件中增加dfs.hosts属性

  4. 新节点启动DataNode(hadoop-daemon.sh start datanode

  5. 配置负载均衡;(sbin/start-balancer.sh -threshold 5)【各个节点与集群总的存储使用率相差不超过5%,默认10%】,等待集群自均衡完成即可。

  6. 新的机器上单独启动NodeManager(yarn-daemon.sh start nodemanager

  7. yarn node -list查看集群情况

缩容:

  1. 退役节点服务器配置目录(etc/hadoop)下创建dfs.hosts.exclude文件,添加需要退役的主机名称(ip或者主机名),在hdfs-site.xml配置文件中添加dfs.hosts.exclude属性,值为新增文件的全路径。如下:

    <property>
    ​
    <name>dfs.hosts.exclude</name>
    ​
    <value>/etc/hadoop/dfs.hosts.exclude</value>
    ​
    </property>

     

  2. 刷新集群,在NameNode机器上执行命令(hdfs dfsadmin -refreshNodes),刷新NameNode,刷新ResourceManager。

    hdfs dfsadmin -refreshNodes 刷新NameNode
    yarn rmadmin -refreshNodes 刷新ResourceManager

     

  3. 等待退役节点状态为decommissioned(所有的块已经复制完成),停止该节点以及资源管理器。注:如果副本数为3,退役节点小于3,是不能退役成功的,需要修改副本数后才能退役。

    sbin/hadoop-daemon.sh stop datanode
    sbin/yarn-daemon.sh stop nodemanager
    sbin/start-balancer.sh 重新负载
    

     

9、用mapreduce怎么处理数据倾斜问题?

数据倾斜:map /reduce程序执行时,reduce节点大部分执行完毕,但是有一个或者几个reduce节点运行很慢,导致整个程序的处理时间很长,这是因为某一个key的条数比其他key多很多(有时是百倍或者千倍之多),这条key所在的reduce节点所处理的数据量比其他节点就大很多,从而导致某几个节点迟迟运行不完,此称之为数据倾斜。

(1)局部聚合加全局聚合。

第一次在 map 阶段对那些导致了数据倾斜的 key 加上 1 到 n 的随机前缀,这样本来相

同的 key 也会被分到多个 Reducer 中进行局部聚合,数量就会大大降低。

第二次 mapreduce,去掉 key 的随机前缀,进行全局聚合。

思想:二次 mr,第一次将 key 随机散列到不同 reducer 进行处理达到负载均衡目的。第

二次再根据去掉 key 的随机前缀,按原 key 进行 reduce 处理。

这个方法进行两次 mapreduce,性能稍差。

(2)增加 Reducer,提升并行度

JobConf.setNumReduceTasks(int)

(3)实现自定义分区

根据数据分布情况,自定义散列函数,将 key 均匀分配到不同 Reducer

10、Shuffle过程详解

具体来说:就是将maptask输出的处理结果数据,分发给reducetask,并在分发的过程中,对数据按key进行了分区和排序;

1)Map 方法之后 Reduce 方法之前这段处理过程叫 Shuffle

2)Map 方法之后,数据首先进入到分区方法,把数据标记好分区,然后把数据发送到 环形缓冲区;环形缓冲区默认大小 100m,环形缓冲区达到 80%时,进行溢写;溢写前对数据进行排序,排序按照对 key 的索引进行字典顺序排序,排序的手段快排;溢写产生大量溢写文件(在溢出过程及合并的过程中,都要调用Partitioner进行分区和针对key进行排序),需要对溢写文件进行归并排序;对溢写的文件也可以进行 Combiner 操作,前提是汇总操作,求平均值不行。最后将文件按照分区存储到磁盘,等待 Reduce 端拉取。

3)每个 Reduce 拉取 Map 端对应分区的数据。拉取数据后先存储到内存中,内存不够 了,再存储到磁盘。拉取完所有数据后,采用归并排序将内存和磁盘中的数据都进行排序。

在进入 Reduce 方法前,可以对数据进行分组操作,合并成大文件后,Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)

注意:

Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。

缓冲区的大小可以通过参数调整,参数:io.sort.mb默认100M。

11、排序

11.1、快排:

private static  int[] qsort(int arr[] ,int start, int end){
        int tem = arr[start]; //定义分界值
        int i = start;
        int j = end ;
​
        while (i < j){ //直到 i = j时,排序完成
            while ((i <j) && (arr[j] > tem)){ //将大于或等于分界值的数据集中到数组右边
               j--;
            }
            while ((i < j) && (arr[i] < tem)){ //小于分界值的数据集中到数组的左边
                i++ ;
            }
            if((arr[i] == arr[j]) && (i <j)){ //相同的数,但是位置不对
                i++;
            }else { //交换顺序
//                int temp = arr[i];
//                arr[i] = arr[j];
//                arr[j] = temp;
                swap(arr,i,j);
            }
        }
        if (i - 1 >start) arr = qsort(arr,start,i-1);
        if (j + 1 <end )  arr = qsort(arr,j+1,end);
        return arr;
    }
​
    private static void swap(int[] x, int a, int b) {
        int t = x[a];
        x[a] = x[b];
        x[b] = t;
    }

11.2、归并排序:

/**
     * 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用分治思想
     * 分段有序(2分法),再把排好序的合并
     * @param src 需要排序的数组
     * @param low 开始位置
     * @param high 结束位置
     */
​
    private static  int[] mergeSort(int[] src,  int low, int high) {
        if (low == high)
            return new int[] { src[low] };
​
        int mid = low + (high - low) / 2;
        int[] leftArr = mergeSort(src, low, mid); //左有序数组
        int[] rightArr = mergeSort(src, mid + 1, high); //右有序数组
        int[] newNum = new int[leftArr.length + rightArr.length]; //新有序数组
​
        int m = 0, i = 0, j = 0;
        while (i < leftArr.length && j < rightArr.length) {
            newNum[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
        }
        while (i < leftArr.length)
            newNum[m++] = leftArr[i++];
        while (j < rightArr.length)
            newNum[m++] = rightArr[j++];
        return newNum;
    }
    
​
​
​
​
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值