Hadoop
简介
一、概述
- Hadoop是大数据生态圈的生态框架 - Hadoop是大数据框架中的基石
- Hadoop是Yahoo!(雅虎)开发的后来贡献给了Apache的一套开源的、可靠的、可伸缩的分布式框架
- Hadoop之父:Doug Cutting(道格.卡丁/汀)
- Hadoop提供了简单的编程模型,能够在集群中对大量数据进行分布式处理
- Hadoop能够从一台服务器扩展到上千台服务器,并且每一台服务器都可以用于进行存储和计算
- Hadoop提供了探测和处理异常的机制
- Hadoop的版本非常混乱,现在官网在同时更新Hadoop2.X和Hadoop3.X两个大版本
- 市面上Hadoop的发行版非常多,常见的:
- Apache Hadoop:最原始、最基础的版本。其他的发行版都是基于这个版本进行的变化
- CDH:是Cloudera提供的商用版Hadoop。这个版本的Hadoop的稳定性相对较好,同时提供了可视化UI来对Hadoop进行部署和监控
- HDP:是Hortonworks提供的商用版Hadoop。这个版本侧重于分布式计算,同时也提供了可视化UI来对Hadoop进行部署和监控
二、版本
- Hadoop1.0:包含Common、HDFS和MapReduce模块
- Hadoop2.0:包含Common、HDFS、MapReduce和YARN模块。Hadoop1.0和Hadoop2.0不兼容
- Hadoop3.0:包含Hadoop的所有模块。但是注意,Hadoop2.0和Hadoop3.0部分版本兼容
三、模块
- Hadoop Common:公共模块
- Hadoop Distributed File System(HDFS):分布式文件存储系统
- NameNode:主节点。管理DataNode,记录元数据
- DataNode:从节点。存储数据
- Hadoop YARN:任务调度和资源管理
- ResourceManager:主节点。负责管理集群中的资源
- NodeManager:从节点。负责管理本节点上的资源
- Hadoop MapReduce:分布式计算系统
- Hadoop Ozone:对象存储
四、安装
- 单机模式:在一台服务器上安装Hadoop,只能启动Hadoop的MapReduce模块
- 伪分布式:在一台服务器上安装Hadoop,利用多个进程来模拟集群环境,能够启动Hadoop的大部分功能
- 完全分布式:在集群中安装,能够启动Hadoop所有功能
HDFS
简介
一、概述
- HDFS(Hadoop Distributed File System)是Hadoop提供的分布式文件存储系统
- HDFS是根据Google的论文<The Goolge File System>来实现的
二、特点
- 能够存储超大文件 - 切块
- 能够快速的应对和检测故障 - 心跳
- 能够流式访问模型
- 能够在相对廉价的机器上进行横向扩展
- 不建议存储小文件
- 不支持低延迟的响应
- HDFS的响应级别在秒甚至分钟
- HFS牺牲了响应速度来提高吞吐量
- 简化的一致性模型
- 允许一次写入多次读取
- 不允许修改但是允许追加
- HDFS本身是弱事务或者不支持事务
技术细节
一、基本结构
- HDFS本身是一个典型的主从结构的框架:主节点是NameNode,从节点是DataNode
- HDFS会对上传的文件进行切分,切分出来的每一个数据块称之为Block
- HDFS会对每一个Block进行备份,这个备份称之为副本(replication)。在HDFS中,如果不指定,则默认的副本数量为3
- HDFS仿照Linux设计了一套文件系统,因此在HDFS中存在虚拟路径的说法。在HDFS中,根路径也是/ --一个文件能否被删除,由父目录的w权限的来决定
二、Block - 数据块
- Block是HDFS存储数据的基本形式,即上传到HDFS上的每一个文件都会切分成Block,数据在HDFS上一定是以Block形式来存储
- Block大小默认是134217728B(128MB),可以通过dfs.blocksize属性来调节,单位默认是字节,放在hdfs-site.xml文件中。默认情况下,一个1GB的文件上传到HDFS上,会产生8个Block
- 如果一个文件不足一个Block的默认大小,则这个文件是多大,则对应的Block就是多大。例如,一个文件是10M,那么这个文件对应的Block就是10M
- HDFS会给每一个Block分配一个递增的编号
- 切块的意义
a. 能够存储超大文件
b. 能够进行快速备份
三、NameNode - 名称节点
- NameNode作为主节点,如果不指定,在Hadoop集群中只有1个 在Hadoop2.0中,允许最多存在2个NameNode,在Hadoop3.0中,NameNode的个数不再限制
- 作用:管理DataNode,记录元数据
- 元数据(metadata)是描述数据的数据
- HDFS中的元数据中包含了三十多个属性,主要包含
- 上传的用户及用户组
- 上传的文件名以及存储路径
- 文件的大小
- 文件的权限策略
- 文件的上传时间
- Block的大小
- 文件和BlockID的映射关系
- BlockID和DataNode的映射关系
- 副本数量
- 一条元数据的大小在150B左右,一般在130B~200B
- 元数据维系在内存以及磁盘中
- 维系在内存中的目的是查询快
- 维系在磁盘中的目的是持久化
- 元数据在磁盘中的存储位置由hadoop.tmp.dir属性来决定。如果不指定,则默认放在/tmp下
- 和元数据相关的文件
- edits:写操作文件。用于记录HDFS所产生的写操作
- fsimage:元映像文件。用于记录元数据的。注意,fsimage文件中元数据和内存中的元数据不同步
- 当NameNode接收到写操作的时候,会先将这个操作记录到edits_inprogress文件中。如果记录成功,则修改内存中的元数据,修改完成之后,会给客户端返回一个ACK信号表示成功。此时,注意,fsimage文件中的元数据并没有产生变化
- 在HDFS中,当达到指定的条件之后,edits_inprogress会进行滚动,滚动产生一个edits文件,同时生成一个新的edis_inprogress。新来的写操作会记录到新的edits_inpogress文件中。NameNode会滚动生成的edits文件中的命令一一取出,然后更新fsimage文件中的元数据
- edits_inprogress文件的滚动条件:
- 空间:当edits_inprogress文件达到指定大小(默认是64M,可以通过fs.checkpoint.size属性来调节,单位默认为是字节,放在core-site.xml文件中)的时候,会滚动生成一个edits文件
- 时间:当距离上次的滚动达到指定的时间间隔(默认是1H,可以通过fs.checkpoint.period属性来调节,单位默认是秒,放在core-site.xml文件中)之后,edits_inprogress文件也会自动滚动生成一个edits文件
- 重启:当NameNode被重启的时候,自动触发edits_inprogress文件的滚动
- 强制:利用命令hadoop dfsadmin -rollEdits来强制滚动edits_inprogress
- 在HDFS第一次启动的时候,启动之后1min会自动触发一次edits_inprogress文件的滚动
- 在HDFS中,会将每一次的写操作看作是一个事务,分配要给全局递增的事务id,称之为txid
- 在HDFS中,会将OP_START_LOG_SEGMENT和OP_END_LOG_SEGMENT看作是一个写操作,也会分配一个事务id
- 查看edits文件:hdfs oev -i edits_0000000000000000006-0000000000000000013 -o edits.xml
- 查看fsimage文件:hdfs oiv -i fsimage_0000000000000000002 -o f.xml -p XML
- 每一个fsimage文件对应了一个md5文件,这个md5不是用于加密的,而是用于校验的
- NameNode通过心跳机制(定时/周期)来管理DataNode
- DataNode会定时(默认是3s,通过属性dfs.heartbeat.interval来调节,单位默认是秒,放在hdfs-site.xml文件中)的给NameNode发送心跳信号。如果NameNode默认超过10min没有收到DataNode的心跳,则会认为这个DataNode已经lost(丢失),此时NameNode会试图将这个DataNode上的数据备份到其他节点上保证整个集群中的副本数量
- 心跳信号
- clusterid - 集群编号
- clusterid是在NameNode被格式化(hadoop namenode -format)的时候自动计算产生的
- NameNode每次被格式化,都会自动计算产生一个新的clusterid
- 当DataNode第一次给NameNode发送心跳的时候,NameNode就会将clusterid在心跳响应中返回给这个DataNode
- 之后每次DataNode给NameNode发送信号,都会在请求中携带clusterid
- NameNode收到DataNode的信号之后,会校验clusterid是否一致。如果一致,则处理DataNode的请求,如果不一致,则直接拒绝
- 如果NameNode被多次格式化,那么NameNode和DataNode的clusterid就不一致,那么在启动的时候,就会发现缺少NameNode或者DataNode
- 当前DataNode的节点状态(预服役、服役、预退役)
- 当前DataNode上的Block信息
- clusterid - 集群编号
- safe mode - 安全模式
- 当NameNode被重启的时候,自动进入安全模式中
- NameNode会自动将edits_inprogress文件来进行滚动,生成edits之后,更新fsimage文件中的元数据
- 更新完fsimage文件中的元数据之后,NameNode会将这个fsimage文件中的元数据加载到内存中
- 加载完成之后,NameNode就会等待DataNode的心跳。如果没有DataNode的心跳,那么NameNode会试图将这个DataNode上的数据重新备份
- 如果NameNode收到DataNode上的心跳,会校验DataNode上的Block信息(例如Block数量,Block大小等);如果校验失败,NameNode会试图恢复数据(重新备份),恢复完成之后会重新校验;如果校验成功,那么NameNode会自动退出安全模式
- 在安全模式中,HDFS只提供读服务不提供写服务
- 在实际生产过程中,如果NameNode在合理的时间内没有退出安全模式,那么说明数据已经产生了不可挽回的丢失(即这份数据的所有副本全部损坏或者丢失),此时需要强制退出安全模式:hadoop dfsadmin -safemode leave
- 正因为安全模式的存在,所以一般要求副本数量不能超过节点数量
四、DataNode - 数据节点
- DataNode用于存储数据的,数据是以Block形式来存储
- DataNode将Block维系在磁盘上
- Block在磁盘上的存储位置由hadoop.tmp.dir来决定
- 每一个Block都会伴随着产生一个meta文件用于进行校验
- DataNode的状态:预服役、服役、预退役、退役(DEAD)、丢失(LOST)
五、HDFS的启动结束命令
命令 | 解释 |
---|---|
start-all.sh | 启动整个Hadoop:HDFS和YARN |
start-dfs.sh | 启动HDFS,此时不启动YARN |
hadoop-daemon.sh start namenode | 启动NameNode |
hadoop-daemon.sh start datanode | 启动DataNode |
hadoop-daemon.sh start secondarynamenode | 启动SecondaryNameNode |
六、SecondaryNameNode
- SecondaryNameNode不是NameNode的备份,而是辅助NameNode完成edits_inprogress文件的滚动和fsimage文件的更新
- 在HDFS集群中,如果存在SecondaryNameNode,则edits_inprogress文件的滚动和fsimage文件的更新由SecondaryNameNode来完成;如果不存在SecondaryNameNode,则上述过程由NameNode自己完成
- 在Hadoop2.0中,支持2 种HDFS结构
- 1个NameNode + 1个SecondaryNameNode + n个DataNode
- 2个NameNode(Active + Standby) + n个DataNode
- 考虑到NameNode的核心地位,必须要对NameNode来进行备份,那么在实际生产过程中,就需要搭建上述的第二种结构
七、副本放置策略
- HDFS采用多副本机制来保证数据的可用性。如果不指定,默认情况下,副本数量为3,可以通过dfs.replication属性来调节,放在hdfs-site.xml文件中
- 默认情况下,如果存在多个副本,同一个Block的副本不能放在相同的节点上行;多个副本在不指定的情况下,放在相对空闲的节点上
- 如果开启了机架感知策略,那么多个副本的存放就需要遵循副本放置策略
- 第一个副本
- 集群内部上传:谁上传就放在谁身上
- 集群外部上传:谁空闲就放在谁身上
- 第二个副本
- Hadoop2.7之前:第二个副本是放在和第一个副本不同机架的节点上
- Hadoop2.7开始:第二个副本是放在和第一个副本相同机架的节点上
- 第三个副本
- Hadoop2.7之前:第三个副本是放在和第二个副本相同机架的节点上
- Hadoop2.7开始:第三个副本是放在和第二个副本不同机架的节点上
- 更多副本:谁空闲就放在谁身上
- 第一个副本
八、机架感知策略
- 在HDFS中,机架感知策略默认是不开启的
- 如果需要开启机架感知策略,需要在hadoop-site.xml文件中添加一个属性topology.script.file.name,这个属性的值需要指定一个脚本文件的路径,在这个脚本文件中来配置机架感知策略
- 这个脚本文件可以使用Python/Shell来完成。需要在这个脚本文件中来指定一个map,map的键是服务器的主机名或者IP,map的值是机架名。在配置的时候,只要保证键对应的值相同,那么就表示将这几台服务器配置在了同一个机架上
- 将脚本中map定义的机架称之为逻辑机架。理论上,可以将不同物理机架上的节点配置在同一个逻辑机架上。在实际生产过程中,为了保证数据的可用性以及集群的维护问题,所以一般将同一个物理机架上的节点配置在同一个逻辑机架上
九、基本命令
命令 | 解释 |
---|---|
hadoop fs -put /home/a.txt / | 将/home/a.txt传到HDFS的根目录 |
hadoop fs -get /a.txt /home | 将HDFS上/a.txt下载到/home目录下 |
hadoop fs -mkdir /log | 创建目录 |
hadoop fs -mv /a.txt /b.txt | 重命名 |
hadoop fs -mv /a.tar /home | 剪切 |
hadoop fs -cp /b.txt /home | 复制 |
hadoop fs -chmod 777 /b.txt | 改变文件的权限 r-4, w-2, x-1 |
hadoop fs -chown tom /b.txt | 改变文件的拥有者 |
hadoop fs -chgrp test /b.txt | 改变文件对应的用户组 |
hadoop fs -rm /b.txt | 删除文件 |
hadoop fs -rmdir /user | 删除目录 |
hadoop fs -rm -r /home 或者 hadoop fs -rmr /home | 递归删除 |
hadoop fs -ls / | 查看 |
hadoop fs -ls -R /home 或者 hadoop fs -lsr /home | 递归查看 |
hadoop fs -getmerge /demo /home/m.txt | 合并下载 |
十、回收站机制
- 在HDFS中,回收站机制默认是不开启的,即文件会被立即删除且删除操作不可逆
- 开启回收站机制
- 如果需要将文件从回收站中挪出来,那么使用hadoop fs -mv命令即可
流程
一、读/下载流程
- 客户端发起RPC请求到NameNode
- NameNode收到请求之后,会进行校验
- 校验是否有读取权限
- 校验是否存在指定文件
- 如果校验失败,则直接报错;如果校验成功,那么NameNode会给客户端返回一个信号表示允许读取
- 当客户端收到信号之后,客户端会再次给NameNode来发送请求,请求获取第一个Block的存储地址
- NameNode收到请求之后,会查询元数据,然后这个Block的存储地址放到队列中返回给客户端
- 当客户端收到队列之后,会将地址从队列中全部取出,然后客户端会从地址中选举较近的节点来读取这个Block
- 读取完这个Block之后,客户端会对这个Block进行checksum校验
- 如果校验失败,那么客户端会给NameNode发送一个信号,同时客户端会从剩余的地址中重新选取一个地址重新读取重新校验;如果校验成功,那么客户端会给NameNode发送请求,请求获取下一个Block的地址,重复5.6.7.8四个步骤,直到读取完所有的Block
- 当客户端读取完最后一个Block之后,客户端会给NameNode发送一个结束信号。NameNode收到信号之后就会关闭文件
二、写/上传流程
- 客户端发起RPC请求到NameNode
- NameNode收到请求之后,会先进行校验
- 校验是否有写入权限
- 校验是否有同名文件
- 如果校验失败,则直接报错;如果校验成功,那么NameNode会给客户端返回一个信号表示允许上传
- 当客户端收到信号之后,客户端会再次给NameNode来发送请求,请求获取第一个Block的存储地址
- 当NameNode收到请求之后,会将Block的存储地址(实际上就是DataNode的主机名或者IP)放到队列中返回给客户端。默认情况下,NameNode会给客户端返回3个地址 - 地址个数由副本数量决定
- 客户端收到队列之后,会将地址从队列中全部取出,然后客户端会从地址中选取一个**较近(拓扑距离的远近)**的节点将这个Block的第一个副本写入
- 这个Block的第一个副本写完之后,这个副本所在的节点通过pipeline(管道,本质上就是NIO中Channel的封装)将第二个副本写入到指定的节点上。第二个副本写完之后,第二个副本所在的节点会通过pipeline将第三个副本写入到对应的节点上
- 等最后一个副本写完之后,最后一个副本所在的节点会给上一个副本所在的节点返回一个ACK信号,依次往前返回。直到第一个副本所在的节点收到ACK,会给客户端返回一个ACK,表示这个Block的所有副本全部写完
- 当客户端收到ACK之后,会给NameNode发送请求,请求获取下一个Block的存储位置,重复5.6.7.8四个步骤
- 直到所有的Block全部写完之后,客户端会给NameNode发送一个结束信号,NameNode收到接收信号之后会关闭文件(关流)。文件关闭之后,就不能更改
三、删除流程
- 客户端发起RPC请求到NameNode
- NameNode收到请求之后,会先进行校验
- 校验文件是否存在
- 校验是否有删除权限 - Permission Denied
- 如果校验成功,NameNode会将这个操作记录到edits_inprogress文件,然后修改内存中的元数据,改完之后会给客户端返回一个成功信号表示文件被删除。注意,此时这个文件并没有被真正删除,只是元数据发生了改变!!!
- NameNode会等待DataNode的心跳,会在心跳响应中命令DataNode删除对应的Block
- DataNode收到心跳响应之后,才会将Block移除。此时,文件才真正被删除
MapReduce
简介
2021年1月7日 15:34
一、概述
- MapReduce是Hadoop提供的一套分布式计算模型
- MapReduce是Doug根据Google的论文<The Google MapReduce>来实现的
- MapReduce将整个计算过程拆分成2个阶段:Map(映射)阶段和Reduce(规约)阶段
- 如果双击winutils.exe报错,那么将msvcr120.dll文件拷贝到C:\Windows\System32目录下
- 配置环境变量
- 配置HADOOP_HOME
- 修改Path变量
- 新建HADOOP_USER_NAME
- 错误解决
- 出现null/bin/winutils.exe错误,解决方案
- 先检查环境变量是否配置正确
- 如果环境变量配置正确IDEA重启之后,依然报错,在Driver类中添加代码指定home
- 出现NativeIO$Windows错误,解决方案
- 先检查环境变量是否配置正确
- 如果环境变量配置正确且IDEA重启之后依然报错,那么将Hadoop解压目录下的bin目录中的hadoop.dll文件复制到C:/Windows/System32目录下
- 如果上述方案依然不能解决,就需要将NativIO.java文件复制到当前工程下,建好对应的package
- 出现null/bin/winutils.exe错误,解决方案
组件
一、Writable - 序列化
- 在MapReduce中,要求被传输的数据能够被序列化
- 如果不指定,则默认情况下,MapReduce的序列化机制采用的是AVRO
- 为了便于操作,Hadoop在AVRO的基础上进行了封装,提供了更简单的形式 - 只需要让被序列化的对象对应的类实现接口即可 - Writable
- 在MapReduce中,序列化的时候,也不允许属性值为null
- 案例:统计每一个人花费的流量/去过的地方(文件:flow.txt) - serialflow
- 练习:获取一个人的最好成绩以及平均成绩(文件:score.txt)
二、Partitioner - 分区
- 分区的作用是对数据来进行分类
- 在使用MapReduce的过程中,需要对数据分几类,就可以设置对应数量的分区
- 在实现MapReduce的过程中的步骤
- 如果遇到了多个字段,先考虑封装对象,封装对象的时候需要考虑序列化
- 封装完成之后,然后定义Mapper来拆分字段封装对象
- 考虑Reducer,完成需求
- 最后,再考虑添加分区条件
- 自定义分区类的时候,需要定义一个类继承Partitioner
- Partitioner的数据是从Mapper来的,所以Partitioner的泛型和Mapper的输出类型一致
- MapReduce默认会对分区来进行编号,编号从0开始
- 在MapReduce中,如果不指定,则默认情况下只有1个ReduceTask,也只产生一个结果文件。如果进行了分类,那么设置了几个分区,就需要设置对应数量的ReduceTask,那么这个时候才会产生对应数量的结果文件 - 分区的数量决定了ReduceTask的数量 - ReduceTask的数量≥分区数量
- 在分区的时候,如果数据大体连续,那么可以按照数据连续的方式来处理
- 在分区的时候,如果不指定,则默认使用的HashPartitioner
三、Comparable - 排序
- 在MapReduce中,默认会对键来进行排序,因此放在键的位置上的元素对应的类必须Comparable接口
- 考虑到键还需要被序列化,所以键对应的类必须实现Writale接口 - WritableComparable
- 案例:按照分数来进行降序排序(结果目录:total_score) - sortscore
- 练习:先按照月份升序;如果是同一个月,那么按照利润降序(文件:profit2.txt) - sortprofit
- 在排序的时候,如果两条数据的compareTo结果为0,那么在Reduce阶段开始的时候,会将这两条数据看作是同一个键,就会将这两条数据对应的值分到一组去
四、Combiner - 合并
- Combiner本质上减少数据的条目数但是不改变计算结果
- 如果需要使用Combiner,那么只需要在驱动类中添加job.setCombinerClass(Class<? extends Reducer> cls);
- 使用Combiner能够提高MapReduce的执行效率,但是不是所有场景都适合于使用Combiner。例如求和、求积、最值、去重等可以进行运算传递的场景可以使用Combiner;例如求平均等无法进行运算传递的场景就不能使用Combiner
基本理论
一、数据本地化策略
- 在实际生产过程中,Split=Block/n
- 数据本地化策略
- 在实际生产过程中,会考虑将DataNode和NodeManager(TaskTracker)部署在相同的节点上,那么这样就减少了数据跨集群的传输
- JobTracker在分配任务的时候,考虑将任务分配到有数据的节点上,那么这样就能保证任务需要的数据是从本地读取而不需要进行跨节点访问
- 切片需要注意的问题
- 切片数量决定MapTask的数量,每一个切片会分配给一个单独的MapTask
- Split最小是1B,最大允许是Long.MAX_VALUE,即(263-1)B
- 在MapReduce中,文件存在可切与不可切的区分。默认文件是可切的,但是如果是压缩文件,那么大部分压缩文件是不可切的
- 如果文件不可切,则整个文件作为一个切片来处理
- 默认情况下,SplitSize和BlockSize等大
- 如果需要将SplitSize调小,那么需要将maxSize调小,如果需要将SplitSize调大,那么需要将minSize调大
- 在切片的时候,需要注意切片阈值SPLIT_SLOP=1.1;只有当剩余的字节个数/SplitSize>1.1的时候才会继续切片。例如,一个文件大小为650M,那么整个文件对应了6个Block:5128M + 10M;对应了5个Split:4128 + 138M
二、Job的执行流程
- 准备阶段
- 检查输入输出路径
- 计算和产生切片
- 如果有需要,那么设置分布式的缓存存根账户信息
- 将任务的jar包和配置信息拷贝到HDFS上
- 提交任务并且选择是否监控任务的执行状态
- 执行阶段
- 拆分子任务:JobTracker收到任务之后,会将这个任务来进行划分,拆分多个子任务:MapTask和ReduceTask。其中,MapTask的数量由切片数量来决定;ReduceTask的数量由分区的数量来决定
- 分配子任务:JobTracker拆分子任务之后,会等待TaskTracker的心跳。JobTracker在收到TaskTracker的心跳之后,会将子任务分配给TaskTracker来执行。在分配的时候,MapTask要尽量满足数据本地化策略;ReduceTask无法满足数据本地化策略,所以ReduceTask只能分配到相对空闲的节点上
- 下载依赖包:TaskTracker通过心跳领取到子任务之后,会连接对应的节点来下载需要的jar包和配置信息(已经提交到HDFS上),这一步体现了"逻辑移动、数据固定"的思想
- 执行子任务:下载完jar包之后,TaskTracker就会在本节点内部开启JVM子进程来执行子任务(MapTask或者ReduceTask)。默认情况下,JVM子进程每次只执行一个任务就会结束。如果TaskTracker领取了多个子任务,那么JVM子进程就要开关多次,那么会造成节点资源的浪费
三、uber模式
- 如果不指定,uber模式默认情况下是不开启的,即每一个JVM子进程只执行一次任务就关闭,如果需要执行新的任务就需要重新开启,会造成资源的浪费
- 开启uber模式的目的是为了让JVM多执行几个任务之后再结束以节省服务器的资源
- 参数设置:(mapred-site.xml)
参数 | 默认值 | 解释 |
---|---|---|
mapreduce.job.ubertask.enable | false | 是否开启uber模式 |
mapreduce.job.ubertask.maxmaps | 9 | uber模式开启之后,JVM子进程需要最多执行多少个MapTask之后再结束 |
mapreduce.job.ubertask.maxreduces | 1 | uber模式开支之后,JVM子进程需要最多执行多少个ReduceTask之后再结束 注意:这个值往往设置的是无效的 |
Shuffle
一、Map端的Shuffle
- MapTask调用map方法处理数据,会将处理结果临时写到MapTask自带的缓冲区中
- 数据在缓冲区中会进行分区、排序。如果指定了Combiner,那么还会进行combine操作。这一次排序,是将数据从杂乱整理成有序状态,所以采用的是快速排序
- 每一个MapTask会自带一个缓冲区,维系在内存中,默认大小是100M
- 缓冲区本质上是一个环形的字节数组
- 当缓冲区使用达到指定阈值(默认是0.8)的时候,缓冲区中的数据会进行spill(溢写),会产生一个溢写文件 因为数据在缓冲区中是有序的,所以溢写文件中的数据也是有序的
- 溢写完成之后,MapTask产生的数据会继续写到缓冲区中。当缓冲区再次达到阈值的时候,会再次进行溢写。每一次溢写,都会产生一个新的溢写文件 单个溢写文件中的数据是有序的,但是所有的溢写文件之间的数据是无序的 局部有序整体无序
- 当MapTask将所有的数据处理完成之后,MapTask会进行merge(合并)操作:将所有的溢写文件合并成一个大文件,形成最后的结果文件(final out)。如果数据都处理完成,但是缓冲区中还有数据,那么缓冲区中的数据会直接冲刷到final out文件中
- 在merge过程中,数据会再次进行分区和排序。如果溢写文件个数≥3个,并且还指定了Combiner,那么还会进行combine操作。因此最后的结果文件final out中的数据是分好区且排好序的。这次排序,将数据从局部有序到整体有序,所以采用的是归并排序
- 注意问题
- 缓冲区设置为环形的目的是为了减少寻址的时间
- 阈值的作用是减少阻塞的几率
- 溢写过程不一定会产生
- 溢写文件的大小会收到序列化因素的影响
- 原始文件大小并不能直接决定溢写次数
二、Reduce端的Shuffle
- 当ReduceTask达到启动阈值(默认是0.05,即有5%的MapTask结束)的时候,ReduceTask就会启动抓取数据
- ReduceTask会启动fetch线程(默认情况下,ReduceTask会启动5个fetch线程)去MapTask端来抓取数据
- fetch线程通过HTTP请求中的GET请求来获取数据,在发送GET请求的时候,会携带分区号作为参数
- fetch线程会将抓取回来的数据先临时存放在本地磁盘上,形成一个个的小文件
- 当所有的fetch线程抓取完数据之后,ReduceTask再将这些小文件进行merge(merge/sort factor merge因子默认为10,即每10个小文件合并一次,最终合并成一个大文件),合并成一个大文件
- 在merge过程中,会再次对数据进行排序,这次排序采用的是归并排序
- 合并完成之后,ReduceTask会再将相同的键对应的值分到一组去,形成一个(伪)迭代器(迭代器本质上是一个基于迭代模式来实现的流,因此这个迭代器只能使用一次),这个过程称之为group(分组)。分组完成之后,每一个键会调用一次reduce方法
三、Shuffle的基本优化
- ※ 适当的增大缓冲区,实际生产过程中,缓冲区大小一般在250M~400M之间
- 调大溢写阈值,但是会增加阻塞的几率
- ※ 增加Combiner,但不是所有的场景都可以使用Combiner
- ※ 考虑将final out来进行压缩传输。这种方案是网络条件和压缩效率之间的取舍
- 适当的调节ReduceTask的启动阈值,这个阈值调节是经验值 需要根据不同的项目以及日常数据量的大小来调节
- ※ 适当的增加fetch线程的数量,这个值一般在5000~8000之间,一般不超过1W
- 适当的调节merge因子,但是会增加底层的运算复杂度
扩展
一、InputFormat - 输入格式
- InputFormat发生在Mapper之前,用于读取数据,然后将读取到的数据传输给Mapper,所以InputFormat读取出来的数据是什么格式,Map接收的数据就是什么格式
- 作用
- getSplits:对数据进行切片
- createRecordReader:提供输入流来读取切片
- 处理文件的时候,如果不指定,实际上使用的是TextInputFormat
- 处理文件的时候,利用父类FileInputFormat来进行切片,利用子类TextInputFormat来读取数据
- 默认情况下,TextInputFormat在切分和读取文件的时候,会先判断这个文件是否可切
- 先获取这个文件的压缩编码,然后判断是否是一个压缩文件
- 如果不是压缩文件,则认为这个文件是可切的;如果是压缩文件,则判断压缩文件是否是一个可切的压缩文件
- 在MapReduce中,默认可以切分的压缩编码只有Bzip2(文件后缀是.bz2)
- 在读取数据的时候,会先判断这个文件是否是一个压缩文件。如果不是压缩文件,则对文件进行按行读取;如果是压缩文件,则判断压缩文件是否可切。如果压缩文件可切,则对压缩文件进行按行读取;如果压缩文件不可切,则先将文件解压然后再读取
- 除了第一个切片和最后一个切片对应的MapTask以外,其他的MapTask都是从当前切片的第二行开始处理到下一个切片的第一行。第一个MapTask需要多处理一行,最后一个MapTask需要少处理一行,以此来保证数据的完整性
- 自定义输入格式:需要定义一个类继承InputFormat。但是考虑到切片过程比较繁琐,所以考虑定义一个类继承FileInputFormat,这个类中已经覆盖了getSplits方法,只需要考虑实现createRecordReader方法即可
- 多源输入:需要同时处理多个文件,且文件的路径/文件系统不一致,那么这个时候,可以考虑使用多源输入 输入的多个文件之间的InputFormat和Mapper类可以不一致
二、OutputFormat - 输出格式
- OutputFormat发生在Reducer之后。Reducer将计算结果传递给OutputFormat,由OutputFormat写出到指定的位置上。Reducer输出的数据是什么类型,OutputFormat接收的就是什么类型
- 作用
- 校验输出路径,例如检查输出路径不存在
- 提供输出流将文件写出
- 在写出数据的时候,默认情况下,如果不指定,实际上使用的是TextOutputFormat
- 在写出的数据的时候,使用父类FileOutputFormat来校验输出路径,使用子类TextOutputFormat来写出数据
- 自定义输出格式:定义一个类继承OutputFormat,但是考虑到需要校验输出路径,所以一般是继承子类FileOutputFormat,这个类中已经覆盖了对应的方法
- 在实际开发中,很少自定义输出格式
- MapReduce中存在多源输出,但是实际开发中很少使用多源输出,绝大部分情况下,多源输出可以被分区或者分组来取代
三、小文件问题
- 在大数据场景中,希望使用的是大文件,但是在实际生产过程中,实际上碰到的大量文件都是小文件
- 小文件的危害
- 存储:每一个小文件在HDFS上存储的时候,都会产生一条元数据;如果有大量小文件存储到HDFS上,就会产生大量的元数据,导致查询效率降低
- 计算:每一个小文件都会对应一个切片。如果存在大量小文件,会产生大量切片,从而就会产生大量的MapTask(线程),导致服务器资源被大量占用
- 到目前为止,在市面上,针对小文件的处理手段无非两种:打包和合并
- Hadoop针对小文件提供了一种原生的打包手段:Hadoop Archive,实际上是将小文件达成har包 hadoop archive -archiveName txt.har -p /txt /result
- 实际生产过程中,也可以自定义程序来完成合并逻辑 也可以考虑使用CombineTextInputFormat来合并
四、推测执行机制
- 推测执行机制实际上是Hadoop针对MapReduce的慢任务提供的一种"优化"方案
- 在MapReduce中,当出现慢任务的时候,MapReduce会将这个节点(服务器)上的慢任务拷贝一份到其他节点上,两个节点同时执行相同的任务。谁先执行完,那么这个任务的结果就作为最终结果来使用,没有执行完的任务就会被kill掉。这个过程称之为推测执行机制
- 慢任务的场景
- 任务分配不均匀
- 节点性能不一致
- 数据倾斜
- 在实际生产过程中,因为数据倾斜导致的慢任务出现的概率更高,此时推测执行机制并没有效果反而会占用更多的服务器资源,所以一般会关闭推测执行机制
五、数据倾斜
- 在实际生产中,任务之间处理的数据量并不均等,这种现象称之为数据倾斜
- 在实际生产过程中,超过90%的数据倾斜产生在了Reduce端,但是不意味着Map端不会产生数据倾斜
- Map端的倾斜条件:多源输入、文件不可切、文件大小不均等。这三个条件缺少任何一个条件,Map端都不会产生倾斜。这三个条件如果想要不成立,所耗费的资源更多,效率更低,所以一般不考虑解决Map端的倾斜。- 在实际生产过程中,一般会尽量避免Map端的倾斜 - 控制文件大小,例如规定每一个日志文件大小不能超过1G
- Reduce端的产生数据倾斜的直接原因是对数据进行了分类,本质原因是数据本身就具有倾斜特性,即数据本身就不均等。要想解决Reduce端的数据清晰额,可以考虑使用二/两阶段聚合
- 二阶段聚合实际上是将任务拆分成2个MapReduce程序来执行,在第一个阶段(第一个MapReduce中)来将数据打散然后分步聚合;在第二个阶段(第二个MapReduce种)再来进行最后的聚合
- 数据的倾斜度越大,二阶段聚合的效率越高
六、Join
- 现在开发中,有部分人认为Join看作是数据倾斜的解决手段之一,但是这个观点并没有得到公认
- 在MapReduce中,如果需要同时处理多个文件,且文件之间还相互关联,那么可以考虑将其中一个文件作为主处理文件,然后将其他文件放到缓存中,等到需要使用关联文件的内容的时候,再从缓存中将关联文件取出来处理
- 案例:统计每一天卖了多少钱(目录:union)
Yarn
简介
一、概述
- YARN(Yet Another Resource Negotiator 至今另一个资源调度器)是Hadoop2.0中提供的用于进行任务调度和资源管理的机制
- YARN是Hadoop2.0中提供的最重要的特性之一,也正是因为YARN的出现,导致Hadoop1.0和Hadoop2.0不兼容
- YARN 产生的原因
- 内部原因
- 在Hadoop1.0中没有YARN,MapReduce既需要接收和处理任务,又需要负责资源的管理和调度,MapReduce要处理的内容就比较多,效率就比较低
- 在Hadoop1.0中,MapReduce的主节点JobTracker只能存在1个,容易存在单点故障
- 在Hadoop1.0中,MapReduce的主节点JobTracker在接收到任务之后,还需要将任务拆分成子任务,然后分配给从节点TaskTracker来执行,并且JobTracker还会监控每一个TaskTracker的子任务的完成情况,导致JobTracker要处理的任务就格外多。根据官方文档给定,每一个JobTracker大概能够监控最多4000个TaskTracker
- 外部原因
- Hadoop刚诞生的时候,没有现在这么的大数据框架。所以Hadoop在刚开始设计和实现的时候,只考虑了MapReduce的资源调度问题,而没有考虑其他框架的资源调度
- 随着大数据的发展和Hadoop的发展,基于Hadoop衍生出来了很多的大数据框架,Hadoop也成为了大数据生态系统中的基石,也就意味着会有很多的计算框架(Pig/Spark/Flink等)来基于Hadoop来使用,那么这个时候这些计算框架在使用Hadoop的资源的时候就产生了冲突
- 内部原因