hadoop
Hadoop总结
1 Hadoop注意事项
reduce方法参数中的key和value,在执行过程中,变量指向的对象是同一个,每次迭代只是把新的值直接在内存中对对象的内容进行修改
package com.atguigu.mr.writableComparable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<FlowBean, NullWritable,FlowBean,NullWritable> {
FlowBean k = new FlowBean();
@Override
protected void reduce(FlowBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
Long upFlow = 0L;
Long downFlow=0L;
for (NullWritable value : values) {
upFlow += key.getUpFlow();
downFlow += key.getDownFlow();
}
k.setPhoneNumber(key.getPhoneNumber());
k.setUpFlow(upFlow);
k.setDownFlow(downFlow);
context.write(k, NullWritable.get());
}
}
这样也可以进行合并
2 MapReduce源码解读
MR源码解读:
一、 Job提交的流程
1. job.waitForCompletion(true); 在Driver中提交job
1) sumbit() 提交
(1) connect():
<1> return new Cluster(getConfiguration());
① initialize(jobTrackAddr, conf);
通过YarnClientProtocolProvider | LocalClientProtocolProvider 根据配置文件的参数信息
获取当前job需要执行到本地还是Yarn
最终:LocalClientProtocolProvider ==> LocalJobRunner
(2) return submitter.submitJobInternal(Job.this, cluster); 提交job
<1> . checkSpecs(job); 检查job的输出路径。
<2> . Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
生成Job提交的临时目录
D:\tmp\hadoop\mapred\staging\Administrator1777320722\.staging
<3> . JobID jobId = submitClient.getNewJobID(); 为当前Job生成Id
<4> . Path submitJobDir = new Path(jobStagingArea, jobId.toString()); Job的提交路径
d:/tmp/hadoop/mapred/staging/Administrator1777320722/.staging/job_local1777320722_0001
<5> . copyAndConfigureFiles(job, submitJobDir);
① rUploader.uploadResources(job, jobSubmitDir);
[1] uploadResourcesInternal(job, submitJobDir);
{1}.submitJobDir = jtFs.makeQualified(submitJobDir);
mkdirs(jtFs, submitJobDir, mapredSysPerms);
创建Job的提交路径
<6> . int maps = writeSplits(job, submitJobDir); //生成切片信息 ,并返回切片的个数
<7> . conf.setInt(MRJobConfig.NUM_MAPS, maps); //通过切片的个数设置MapTask的个数
<8> . writeConf(conf, submitJobFile); //将当前Job相关的配置信息写到job提交路径下
路径下: job.split job.splitmetainfo job.xml xxx.jar
<9> .status = submitClient.submitJob(
jobId, submitJobDir.toString(), job.getCredentials());
//真正提交Job
<10> . jtFs.delete(submitJobDir, true); //等job执行完成后,删除Job的临时工作目录的内容
二、 MapTask的工作机制
1. 从Job提交流程的(2)--><9> 进去
Job job = new Job(JobID.downgrade(jobid), jobSubmitDir); 构造真正执行的Job , LocalJobRunnber$Job
2. LocalJobRunnber$Job 的run()方法
1) TaskSplitMetaInfo[] taskSplitMetaInfos =
SplitMetaInfoReader.readSplitMetaInfo(jobId, localFs, conf, systemJobDir);
// 读取job.splitmetainfo
2) int numReduceTasks = job.getNumReduceTasks(); // 获取ReduceTask个数
3) List<RunnableWithThrowable> mapRunnables = getMapTaskRunnables(
taskSplitMetaInfos, jobId, mapOutputFiles);
// 根据切片的个数, 创建执行MapTask的 MapTaskRunnable
4) ExecutorService mapService = createMapExecutor(); // 创建线程池
5) runTasks(mapRunnables, mapService, "map"); //执行 MapTaskRunnable
6) 因为Runnable提交给线程池执行,接下来会执行MapTaskRunnable的run方法。
7) 执行 LocalJobRunner$Job$MapTaskRunnable 的run()方法.
(1) MapTask map = new MapTask(systemJobFile.toString(), mapId, taskId,
info.getSplitIndex(), 1); //创建MapTask对象
(2) map.run(localConf, Job.this); //执行MapTask中的run方法
<1> .runNewMapper(job, splitMetaInfo, umbilical, reporter);
① org.apache.hadoop.mapreduce.TaskAttemptContext taskContext = JobContextImpl
② org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE> mapper = WordConutMapper
③ org.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE> inputFormat = TextInputFormat
④ split = getSplitDetails(new Path(splitIndex.getSplitLocation()),
splitIndex.getStartOffset()); // 重构切片对象
切片对象的信息 : file:/D:/input/inputWord/JaneEyre.txt:0+36306679
⑤ org.apache.hadoop.mapreduce.RecordReader<INKEY,INVALUE> input = MapTask$NetTrackingRecordReader
⑥ output = new NewOutputCollector(taskContext, job, umbilical, reporter); //构造缓冲区对象
[1] collector = createSortingCollector(job, reporter); //获取缓冲区对象
MapTask$MapOutputBuffer
{1} . collector.init(context); //初始化缓冲区对象
1>>.final float spillper =
job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8);
// 溢写百分比 0.8
2>>.final int sortmb = job.getInt(MRJobConfig.IO_SORT_MB,
MRJobConfig.DEFAULT_IO_SORT_MB);
// 缓冲区大小 100M
3>>.sorter = ReflectionUtils.newInstance(job.getClass(
MRJobConfig.MAP_SORT_CLASS, QuickSort.class,
IndexedSorter.class), job);
// 排序对象
// 排序使用的是快排,并且基于索引排序。
4>> . // k/v serialization // kv序列化
5>> . // output counters // 计数器
6>> . // compression // 压缩
7>> . // combiner // combiner
⑦ mapper.run(mapperContext); // 执行WordCountMapper中的run方法。 实际执行的是
WordCountMapper继承的Mapper中的run方法。
[1] . 在Mapper中的run方法中
map(context.getCurrentKey(), context.getCurrentValue(), context);
执行到WordCountMapper中的map方法。
[2] . 在WordCountMapper中的map方法中将kv写出
context.write(outK,outV);
三、 Shuffle流程(溢写,归并)
1. map中的kv持续往 缓冲区写, 会达到溢写条件,发生溢写,最后发生归并。
2. map中的 context.write(k,v)
1) . mapContext.write(key, value);
(1). output.write(key, value);
<1> collector.collect(key, value,
partitioner.getPartition(key, value, partitions));
// 将map写出的kv 计算好分区后,收集到缓冲区中。
<2> . 当满足溢写条件后 ,开始发生溢写
startSpill();
① spillReady.signal(); //线程间通信,通知溢写线程开始溢写
② 溢写线程调用 sortAndSpill() 方法发生溢写操作
③ final SpillRecord spillRec = new SpillRecord(partitions);
final Path filename =
mapOutputFile.getSpillFileForWrite(numSpills, size);
out = rfs.create(filename)
//根据分区的个数,创建溢写文件,
/tmp/hadoop-Administrator/mapred/local/localRunner/Administrator/jobcache/job_local277309786_0001/attempt_local277309786_0001_m_000000_0/output/spill0.out
④ sorter.sort(MapOutputBuffer.this, mstart, mend, reporter); // 溢写前先排序
⑤ writer.close(); 通过writer进行溢写,溢写完成后,关闭流,可以查看磁盘中的溢写文件
⑥ if (totalIndexCacheMemory >= indexCacheMemoryLimit)
// create spill index file
Path indexFilename =
mapOutputFile.getSpillIndexFileForWrite(numSpills, partitions
// 判断索引使用的内存空间是否超过限制的大小,如果超过也需要溢写到磁盘
⑦ map持续往缓冲区写,达到溢写条件,就继续溢写 ........ 可能整个过程中发生N次溢写。
⑦ MapTask中的runNewMapper 中 output.close(mapperContext);
假如上一次溢写完后,剩余进入的到缓冲区的数据没有达到溢写条件,那么当map中的所有的数据
都已经处理完后,在关闭output时,会把缓冲区中的数据刷到磁盘中(其实就是没有达到溢写条件的数据也要写到磁盘)
[1] collector.flush(); //刷写
{1} . sortAndSpill(); 通过溢写的方法进行剩余数据的刷写
{2} . 最后一次刷写完后,磁盘中会有N个溢写文件
spill0.out spill1.out .... spillN.out
{3} . 归并 mergeParts();
>>1. for(int i = 0; i < numSpills; i++) {
filename[i] = mapOutputFile.getSpillFile(i);
finalOutFileSize += rfs.getFileStatus(filename[i]).getLen();
}
//根据溢写的次数,得到要归并多少个溢写文件
>>2. Path finalOutputFile =
mapOutputFile.getOutputFileForWrite(finalOutFileSize);
/tmp/hadoop-Administrator/mapred/local/localRunner/Administrator/jobcache/job_local1987086776_0001/attempt_local1987086776_0001_m_000000_0/output/file.out
Path finalIndexFile =
mapOutputFile.getOutputIndexFileForWrite(finalIndexFileSize);
/tmp/hadoop-Administrator/mapred/local/localRunner/Administrator/jobcache/job_local1987086776_0001/attempt_local1987086776_0001_m_000000_0/output/file.out.index
//生成最终存储数据的两个文件
>>3. for (int parts = 0; parts < partitions; parts++) {
// 按照分区的, 进行归并。
>>4 .awKeyValueIterator kvIter = Merger.merge(job, rfs,
keyClass, valClass, codec,
segmentList, mergeFactor,
new Path(mapId.toString()),
job.getOutputKeyComparator(), reporter, sortSegments,
null, spilledRecordsCounter, sortPhase.phase(),
TaskType.MAP);
//归并操作
>>5 Writer<K, V> writer =
new Writer<K, V>(job, finalPartitionOut, keyClass, valClass, codec,
spilledRecordsCounter);
//通过writer写归并后的数据到磁盘
>>6 . if (combinerRunner == null || numSpills < minSpillsForCombine) {
Merger.writeFile(kvIter, writer, reporter, job);
} else {
combineCollector.setWriter(writer);
combinerRunner.combine(kvIter, combineCollector);
}
在归并时,如果有combine,且溢写的次数大于等于minSpillsForCombine的值3,
会使用Combine
>>7. for(int i = 0; i < numSpills; i++) {
rfs.delete(filename[i],true);
}
归并完后,将溢写的文件删除
>> 8. 最后在磁盘中存储map处理完后的数据,等待reduce的拷贝。
file.out file.out.index
四、 ReduceTask工作机制
1. 在LocalJobRunner$Job中的run()方法中
try {
if (numReduceTasks > 0) {
//根据reduceTask的个数,创建对应个数的LocalJobRunner$Job$ReduceTaskRunnable
List<RunnableWithThrowable> reduceRunnables = getReduceTaskRunnables(
jobId, mapOutputFiles);
// 线程池
ExecutorService reduceService = createReduceExecutor();
//将 ReduceTaskRunnable提交给线程池执行
runTasks(reduceRunnables, reduceService, "reduce");
}
1) . 执行 LocalJobRunner$Job$ReduceTaskRunnable 中的run方法
(1) . ReduceTask reduce = new ReduceTask(systemJobFile.toString(),
reduceId, taskId, mapIds.size(), 1);
//创建ReduceTask对象
(2) . reduce.run(localConf, Job.this); // 执行ReduceTask的run方法
<1> . runNewReducer(job, umbilical, reporter, rIter, comparator,
keyClass, valueClass);
[1] . org.apache.hadoop.mapreduce.TaskAttemptContext taskContext = TaskAttemptContextImpl
[2] . org.apache.hadoop.mapreduce.Reducer<INKEY,INVALUE,OUTKEY,OUTVALUE> reducer = WordCountReducer
[3] . org.apache.hadoop.mapreduce.RecordWriter<OUTKEY,OUTVALUE> trackedRW = ReduceTask$NewTrackingRecordWriter
[4] . reducer.run(reducerContext);
//执行WordCountReducer的run方法 ,实际执行的是WordCountReducer继承的Reducer类中的run方法.
{1} .reduce(context.getCurrentKey(), context.getValues(), context);
//执行到WordCountReducer中的 reduce方法.
{2} . context.write(k,v) 将处理完的kv写出.
>>1 . reduceContext.write(key, value);
>>2 . output.write(key, value);
>>3 . real.write(key,value); // 通过RecordWriter将kv写出
>>4 . out.write(NEWLINE); //通过输出流将数据写到结果文件中
注意: 我们是在本地进行调试,所以N个MapTask 和 N个 ReduceTask没有并行的效果。
但是如果在集群上,N个 MapTask 和 N 个ReduceTask 是并行运行.
Hadoop 3.x 和2.x主要区别
端口号变化
Daemon | App | Hadoop2 | Hadoop3 |
---|---|---|---|
NameNode Port | Hadoop HDFS NameNode | 8020 / 9000 | 9820 |
Hadoop HDFS NameNode HTTP UI | 50070 | 9870 | |
Hadoop HDFS NameNode HTTPS UI | 50470 | 9871 | |
Secondary NameNode Port | Secondary NameNode HTTP | 50091 | 9869 |
Secondary NameNode HTTP UI | 50090 | 9868 | |
DataNode Port | Hadoop HDFS DataNode IPC | 50020 | 9867 |
Hadoop HDFS DataNode | 50010 | 9866 | |
Hadoop HDFS DataNode HTTP UI | 50075 | 9864 | |
Hadoop HDFS DataNode HTTPS UI | 50475 | 9865 |
1) 最低Java版本从7升级到8
2) 引入纠删码(Erasure Coding)
主要解决数据量大到一定程度磁盘空间存储能力不足的问题.
HDFS中的默认3副本方案在存储空间中具有200%的额外开销。但是,对于I/O活动相对较少冷数据集,在正常操作期间很少访问其他块副本,但仍然会消耗与第一个副本相同的资源量。
纠删码能勾在不到50%数据冗余的情况下提供和3副本相同的容错能力,因此,使用纠删码作为副本机制的改进是自然而然,也是未来的趋势.
3) 重写了Shell脚本
重写了Shell脚本,修改了之前版本长期存在的一些错误,并提供了一些新功能,在尽可能保证兼容性的前提下,一些新变化仍然可能导致之前的安装出现问题。
例如:
l 所有Hadoop Shell脚本子系统现在都会执行hadoop-env.sh这个脚本,它允许所有环节变量位于一个位置;
l 守护进程已通过*-daemon.sh选项从*-daemon.sh移动到了bin命令中,在Hadoop3中,我们可以简单的使用守护进程来启动、停止对应的Hadoop系统进程;
4) 引入了新的API依赖
之前Hadoop客户端操作的Maven依赖为hadoop-client,这个依赖直接暴露了Hadoop的下级依赖,当用户和Hadoop使用相同依赖的不同版本时,可能造成冲突。
Hadoop3.0引入了提供了hadoop-client-api 和hadoop-client-runtime依赖将下级依赖隐藏起来,一定程度上来解决依赖冲突的问题
5) MapReduce任务的本地化优化
MapReduce引入了一个NativeMapOutputCollector的本地化(C/C++)实现,对于shuffle密集的任务,可能提高30%或者更高的性能
6) 支持超过两个NN
HDFS NameNode高可用性的初始实现为单个Active NameNode 和 单个 Standby NameNode, 将edits复制到三个JournalNode。 该体系结构能够容忍系统中一个NN或者一个JN故障.但是,某些部署需要更高程序的容错能力,Hadoop3.x允许用户运行一个Active NameNode 和 多个 Standby NameNode。
7) 许多服务的默认端口改变了
Hadoop3.x之前,多个Hadoop服务的默认端口位于Linux临时端口范围(63768~61000). 这意味着在启动时,由于与另一个应用程序冲突,服务有时无法绑定到端口.
在Hadoop3.x中,这些可能冲突的端口已移出临时范围,受影响的有NameNode ,
SecondaryNamenode , DataNode 和 KMS
8) 添加对Microsoft Azure Data Lake 和 阿里云对象存储系统的支持
9) DataNode内部实现Balancer
一个DN管理多个磁盘,当正常写入时,多个磁盘是平均分配的。然而当添加新磁盘时,这种机制会造成DN内部严重的倾斜。
之前的DataNode Balancer只能实现DN之间的数据平衡,Hadoop3.x实现了内部的数据平衡。
10) 重做的后台和任务堆内存管理
已实现根据服务器自动配置堆内存,HADOOP_HEAPSIZE变量失效。简化MapTask 和ReduceTask的堆内存配置,现已不必同时在配置中和Java启动选项中指定堆内存大小,旧有配置不会受到影响。
11) HDFS实现服务器级别的Federation分流
对于HDFS Federation, 添加了一个对统一命名空间的RPC路由层 。 和原来的HDFS Federation没有变化,只是目前挂在管理不必在客户端完成,而是放在的服务器,从而简化了HDFS Federation访问。
12) Yarn的时间线服务升级到V2
Yarn的时间线服务是MRJobHistory的升级版,提供了在Yarn上运行第三方程序的历史支持,该服务在Hadoop3.0升级为第二版
13) 容量调度器实现API级别的配置
现在容量调度器可以实现通过REST API来改变配置,从而让管理员可以实现调度器自动配置。
14) Yarn实现更多种资源类型的管理
Yarn调度器现已可以通过配置实现用户自定义的资源管理。现在Yarn可以根据CPU和内存意外的资源管理其任务队列
参考:
https://blog.csdn.net/chj_xc/article/details/54907029
https://www.cnblogs.com/smartloli/p/8827623.html
https://www.cnblogs.com/smartloli/p/9028267.html
15)常见端口号:
NameNode 9820(内部通信) 9870(web端) (注意我配置集群的时候还在用8020)
2NN 9868(web端)
ResourceManager 8088(web端)
HistoryServer 19888(web端)
第一章 hadoop的入门
1.1 hadoop不同版本的区别
hadoop的初衷是采用大量的廉价机器,组成一个集群,完成大数据的存储和计算
1.x
HDFS:负责大数据的存储
Common:HDFS和MR共有的常用的工具包模块
MapReduce:负责计算,负责计算资源的申请的调度
完成大数据的计算
①写程序,程序需要复合计算框架的要求! java—->main—–>运行 MapReduce(编程模型)—–>Map–Reducer
2.x
HDFS(框架):负责大数据的存储 YARN(框架): 负责大数据的资源调度
MR(编程模型): 使用Hadoop制定的编程要求,编写程序,完成大数据的计算!
②运行程序,申请计算资源(cpu+内存,磁盘IO,网络IO) java—–>JVM——>OS—–>申请计算资源 1.0: MapReduce(编程模型)—->JobTracker—–>JVM—–>申请计算资源 2.0: MapReduce(编程模型)—->jar——>运行时,将jar包中的任务,提交给YARN,和YARN进行通信 由YARN中的组件—–JVM——>申请计算资源
1.x和2.x的区别是将资源调度和管理进行分离!由同一的资源调度平台YARN进行大数据计算资源的调度! 提升了Hadoop的通用性!Hadoop搭建的集群中的计算资源,不仅可以运行Hadoop中的MR程序! 也可以运行其他计算框架的程序!
1.2 hadoop的组成
1.3 HDFS
负责大数据的存储
核心进程:
必须进程:
Namenode(1个):负责文件,名称等元数据的存储!(相当于一本书的目录)
文件名,大小,文件切分了多少块(block),创建和修改时间等
职责:接受客户端的请求
接受DN的请求
向DN分配任务
Datanode(N个) :负责文件中数据的存储! (相当于书的目录所对应的内容)
职责:负责接受NM分配的任务 负责数据块(block)的管理(读,写)!
可选进程:
SecondaryNamenode(N个) :负责辅助NameNode工作!(2nn)
1.4 MapReduce
MapReduce(编程规范):程序中有Mapper(简单处理)和Reducer(合并)
遵循MapReduce的编程规范,编写的程序,打包后,称为一个Job(任务)
Job需要提交到YARN上,向YARN申请计算资源,运行Job中的Task进程
(Job会先创建一个进程MRAppMaster(mapreduce 应用管理者),由MRAppMaster向YARN申请资源!MRAppMaster负责监控Job中各个Task运行情况,进行容错管理)
map产生了一个并行运算,把一些大的数据拆分成一个个小的来进行运算,reduce来进行汇总
1.5 YARN
YARN负责集群中所有计算资源的管理和调度
常见进程:
ResourceManager(1个):负责整个集群中所有的资源的管理
职责:负责接受客户端的提交Job的请求
负责向NM分配任务
负责接受NM上报的信息
NodeManager(N个):负责单台计算机所有资源的管理
职责:负责和RM进行通信,上报本机中的可用资源
负责领取RM分配的任务
负责为Job中的每一个Task分配计算资源!
概念:
Container(容器):NodeManager为Job的某个Task分配了2个CPU和2G内存的计算资源
为了防止当前Task在使用这些资源期间,被其他的task抢占资源!
被计算资源,封装到一个Container中,在Container中的资源,会被暂时隔离无法被其他进程所抢占!
当前Task运行结束后,当前Container中的资源会被释放,运行其他task来使用
1.5 大数据技术生态体系
图中涉及的技术名词解释如下:
1)Sqoop:Sqoop是一款开源的工具,主要用于在Hadoop、Hive与传统的数据库(MySql)间进行数据的传递,可以将一个关系型数据库(例如 :MySQL,Oracle 等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。
2)Flume:Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
3)Kafka:Kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性:
(1)通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。
(2)高吞吐量:即使是非常普通的硬件Kafka也可以支持每秒数百万的消息。
(3)支持通过Kafka服务器和消费机集群来分区消息。
(4)支持Hadoop并行数据加载。
4)Storm:Storm用于“连续计算”,对数据流做连续查询,在计算时就将结果以流的形式输出给用户。
5)Spark:Spark是当前最流行的开源大数据内存计算框架。可以基于Hadoop上存储的大数据进行计算。
6)Oozie:Oozie是一个管理Hdoop作业(job)的工作流程调度管理系统。
7)Hbase:HBase是一个分布式的、面向列的开源数据库。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。
8)Hive:Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的SQL查询功能,可以将SQL语句转换为MapReduce任务进行运行。 其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。
10)R语言:R是用于统计分析、绘图的语言和操作环境。R是属于GNU系统的一个自由、免费、源代码开放的软件,它是一个用于统计计算和统计制图的优秀工具。
11)Mahout:Apache Mahout是个可扩展的机器学习和数据挖掘库。
12)ZooKeeper:Zookeeper是Google的Chubby一个开源的实现。它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、 分布式同步、组服务等。ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
1.6 大数据与java进行关联
1.7 Hadoop运行环境搭建
1)准备三台虚拟机,虚拟机配置要求如下:
(1)单台虚拟机:内存4G,硬盘50G,安装必要环境
sudo yum install -y epel-release
sudo yum install -y psmisc nc net-tools rsync vim lrzsz ntp libzstd openssl-static tree iotop git
(2) 修改克隆虚拟机的静态IP
sudo vim /etc/sysconfig/network-scripts/ifcfg-ens33
改成
DEVICE=ens33 (注意你的虚拟机网卡里面的ip地址第三位是多少就改多少,把 1 改成 241)
TYPE=Ethernet
ONBOOT=yes
BOOTPROTO=static
NAME="ens33"
IPADDR=192.168.1.101
PREFIX=24
GATEWAY=192.168.1.2
DNS1=192.168.1.2
(3)保证Linux文件中IP地址、Linux虚拟网络编辑器地址和Windows系统VM8网络IP地址相同
2)修改主机名
(1)修改主机名称
sudo hostnamectl --static set-hostname hadoop101
(2)配置主机名称映射,打开/etc/hosts
sudo vim /etc/hosts
添加如下内容
192.168.1.100 hadoop100
192.168.1.101 hadoop101
192.168.1.102 hadoop102
192.168.1.103 hadoop103
192.168.1.104 hadoop104
192.168.1.105 hadoop105
192.168.1.106 hadoop106
192.168.1.107 hadoop107
192.168.1.108 hadoop108
(3)修改window7的主机映射文件(hosts文件)
(a)进入C:\Windows\System32\drivers\etc路径
(b)打开hosts文件并添加如下内容
192.168.1.100 hadoop100
192.168.1.101 hadoop101
192.168.1.102 hadoop102
192.168.1.103 hadoop103
192.168.1.104 hadoop104
192.168.1.105 hadoop105
192.168.1.106 hadoop106
192.168.1.107 hadoop107
192.168.1.108 hadoop108
(4)修改window10的主机映射文件(hosts文件)
(a)进入C:\Windows\System32\drivers\etc路径
(b)拷贝hosts文件到桌面
(c)打开桌面hosts文件并添加如下内容
192.168.1.100 hadoop100
192.168.1.101 hadoop101
192.168.1.102 hadoop102
192.168.1.103 hadoop103
192.168.1.104 hadoop104
192.168.1.105 hadoop105
192.168.1.106 hadoop106
192.168.1.107 hadoop107
192.168.1.108 hadoop108
(d)将桌面hosts文件覆盖C:\Windows\System32\drivers\etc路径hosts文件
3)关闭防火墙
sudo systemctl stop firewalld
sudo systemctl disable firewalld
4) 创建atguigu用户
sudo useradd atguigu
sudo passwd atguigu
5)重启虚拟机
reboot
6)配置atguigu用户具有root权限
修改/etc/sudoers文件,找到下面一行(91行),在root下面添加一行,如下所示:
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
atguigu ALL=(ALL) ALL
7)在/opt目录下创建文件夹
(1)在/opt目录下创建module、software文件夹
sudo mkdir /opt/module /opt/software
2)修改module、software文件夹的所有者
sudo chown atguigu:atguigu /opt/module /opt/software
8)利用快照克隆3台虚拟机
第一步 修改主机名
sudo hostnamectl --static set-hostname hadoop101
第二步,修改网络ip地址
vim /etc/sysconfig/network-scripts/ifcfg-ens33
改 IPADDR=192.168.1.101 为你需要的
9)快照怎么用
关闭你需要快照的虚拟机
1.8 在102安装JDK
(1)卸载现有JDK
rpm -qa | grep -i java | xargs -n1 sudo rpm -e --nodeps
(2)xftp5将JDK和hadoop安装包加入到/opt/softwate文件夹下面
(3)解压JDK到/opt/module目录下
tar -zxvf jdk-8u212-linux-x64.tar.gz -C /opt/module/
(4)配置JDK环境 变量
新建/etc/profile.d/my_env.sh文件
sudo vim /etc/profile.d/my_env.sh
添加如下内容
#JAVA_HOME
export JAVA_HOME=/opt/module/jdk1.8.0_212
export PATH=$PATH:$JAVA_HOME/bin
保存退出
重启xshell窗口,让环境变量生效
(5)测试JDK是否安装成功
java -version
如果能看到以下结果,则Java正常安装
java -version “1.8.0_212”
注意:重启(如果java -version可以用就不用重启)
1.9 在102安装Hadoop
(1)进入到Hadoop安装包路径下
[atguigu@hadoop101 ~]$ cd /opt/software/
(2)解压安装文件到/opt/module下面
[atguigu@hadoop101 software]$ tar -zxvf hadoop-3.1.3.tar.gz -C /opt/module/
(3)将Hadoop添加到环境变量
获取Hadoop安装路径
[atguigu@hadoop101 hadoop-3.1.3]$ pwd
/opt/module/hadoop-3.1.3
打开/etc/profile.d/my_env.sh文件
sudo vim /etc/profile.d/my_env.sh
在profile文件末尾添加JDK路径
##HADOOP_HOME
export HADOOP_HOME=/opt/module/hadoop-3.1.3
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin
让修改后的文件生效
[atguigu@ hadoop101 hadoop-3.1.3]$ source /etc/profile
或者重新打开xshell
(4)测试是否安装成功
hadoop version
和
[root@hadoop102 ~]# hadoop checknative
2020-11-21 13:55:46,264 INFO bzip2.Bzip2Factory: Successfully loaded & initialized native-bzip2 library system-native
2020-11-21 13:55:46,266 INFO zlib.ZlibFactory: Successfully loaded & initialized native-zlib library
2020-11-21 13:55:46,359 WARN erasurecode.ErasureCodeNative: ISA-L support is not available in your platform... using builtin-java codec where applicable
Native library checking:
hadoop: true /opt/module/hadoop-3.1.3/lib/native/libhadoop.so.1.0.0
zlib: true /lib64/libz.so.1
zstd : true /lib64/libzstd.so.1
snappy: true /lib64/libsnappy.so.1
lz4: true revision:10301
bzip2: true /lib64/libbz2.so.1
openssl: true /lib64/libcrypto.so
ISA-L: false libhadoop was built without ISA-L support
1.10 hadoop 目录结构
1、查看Hadoop目录结构
[atguigu@hadoop101 hadoop-2.7.2]$ ll
总用量 52
drwxr-xr-x. 2 atguigu atguigu 4096 5月 22 2017 bin
drwxr-xr-x. 3 atguigu atguigu 4096 5月 22 2017 etc
drwxr-xr-x. 2 atguigu atguigu 4096 5月 22 2017 include
drwxr-xr-x. 3 atguigu atguigu 4096 5月 22 2017 lib
drwxr-xr-x. 2 atguigu atguigu 4096 5月 22 2017 libexec
-rw-r–r–. 1 atguigu atguigu 15429 5月 22 2017 LICENSE.txt
-rw-r–r–. 1 atguigu atguigu 101 5月 22 2017 NOTICE.txt
-rw-r–r–. 1 atguigu atguigu 1366 5月 22 2017 README.txt
drwxr-xr-x. 2 atguigu atguigu 4096 5月 22 2017 sbin
drwxr-xr-x. 4 atguigu atguigu 4096 5月 22 2017 share
2、重要目录
(1)bin目录:存放对Hadoop相关服务(HDFS,YARN)进行操作的脚本(使用Hdfs和运算MR时,常用的目录)常用的hadoop命令!
(2)etc目录:Hadoop的配置文件目录,存放Hadoop的配置文件
(3)lib目录:存放Hadoop的本地库(对数据进行压缩解压缩功能)
(4)sbin目录:存放启动或停止Hadoop相关服务的脚本(管理员启动和停止集群使用的命令)
(5)share目录:存放Hadoop的依赖jar包、文档、和官方案例
第二章 hadoop运行模式
Hadoop运行模式包括:本地模式、伪分布式模式以及完全分布式模式。
Hadoop官方网站:http://hadoop.apache.org/
取决于参数: fs.defaultFS=file:///(默认) fs.defaultFS在core-default.xml中! ①本地模式(在本机上使用HDFS,使用的就是本机的文件系统) fs.defaultFS=file:/// ②分布式模式 要使用的文件系统是一个分布式的文件系统! 一个分布式的文件系统,必须由NN,DN等若干进程共同运行完成文件系统的读写操作! fs.defaultFS=hdfs://
启动NN: hadoop-daemon.sh start namenode
停止NN: hadoop-daemon.sh stop namenode
启动DN: hadoop-daemon.sh start datanode
停止DN: hadoop-daemon.sh stop datanode
使用: hadoop fs 命令 文件路径
2.1 本地运行模式(官方Grep案例)
\1. 创建在hadoop-3.1.3文件下面创建一个input文件夹
cd /opt/module/hadoop-3.1.3
mkdir input
\2. 将Hadoop的xml配置文件复制到input
cp etc/hadoop/*.xml input
\3. 执行share目录下的MapReduce程序
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar grep input output 'dfs[a-z.]+'
\4. 查看输出结果
cat output/*
2.1 本地运行模式(官方wordcount)
1)创建在hadoop-3.1.3文件下面创建一个wcinput文件夹
mkdir wcinput
2)在wcinput文件下创建一个wc.input文件
cd wcinput
3)编辑wc.input文件
vi wc.input
在文件中输入如下内容
hadoop yarn
hadoop mapreduce
atguigu
atguigu
保存退出::wq
4)回到Hadoop目录/opt/module/hadoop-3.1.3
5)执行程序
hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount wcinput wcoutput
6)查看结果
[atguigu@hadoop101 hadoop-3.1.3]$ cat wcoutput/part-r-00000
看到如下结果:
atguigu 2
hadoop 2
mapreduce 1
yarn 1
2.2 完全分布式运行模式
分析:
1)准备3台客户机(关闭防火墙、静态ip、主机名称)
2)安装JDK
3)配置环境变量
4)安装Hadoop
5)配置环境变量
6)配置集群
7)单点启动
8)配置ssh
9)群起并测试集群
2.2.1 虚拟机准备
详见上面
2.2.2 scp(secure copy)安全拷贝
(1)scp定义:
scp可以实现服务器与服务器之间的数据拷贝。(from server1 to server2)
(2)基本语法
scp -r $pdir/$fname $user@hadoop$host:$pdir
命令 递归 要拷贝的文件路径/名称 目的用户@主机:目的路径
(3)案例实操
(a)在hadoop102上,将hadoop101中/opt/module目录下的软件拷贝到hadoop103上。
scp -r /opt/module/* root@hadoop103:/opt/module
(b)在hadoop103上,将hadoop101服务器上的/opt/module目录下的软件拷贝到hadoop103上。
scp -r root@hadoop2:/opt/module/* /opt/module
(c)在hadoop103上操作将hadoop101中/opt/module目录下的软件拷贝到hadoop104上。
[atguigu@hadoop103 opt]$ scp -r atguigu@hadoop101:/opt/module/* root@hadoop104:/opt/module
注意:拷贝过来的/opt/module目录,别忘了在hadoop102、hadoop103、hadoop104上修改所有文件的,所有者和所有者组。sudo chown atguigu:atguigu -R /opt/module
(d)将hadoop101中/etc/profile.d/my_env.sh文件拷贝到hadoop102的/etc/profile.d/my_env.sh上。
注意:拷贝过来的配置文件别忘了source一下/etc/profile,。
[root@hadoop102 opt]# scp /etc/profile.d/my_env.sh root@hadoop103:/etc/profile.d/my_env.sh
2.2.3 rsync远程同步工具
rsync主要用于备份和镜像。具有速度快、避免复制相同内容和支持符号链接的优点。
rsync和scp区别:用rsync做文件的复制要比scp的速度快,rsync只对差异文件做更新。scp是把所有文件都复制过去。
(1)基本语法
rsync -av $pdir/$fname $user@hadoop$host:$pdir/$fname
命令 选项参数 要拷贝的文件路径/名称 目的用户@主机:目的路径/名称
选项参数说明
选项 | 功能 |
---|---|
-a | 归档拷贝 |
-v | 显示复制过程 |
(2)案例实操
把hadoop102机器上的/opt/software目录同步到hadoop101服务器的root用户下的/opt/目录
[root@hadoop102 software]# rsync -av /opt/software/ root@hadoop103:/opt/software
2.2.4 xsync 集群分发脚本
(1)需求:循环复制文件到所有节点的相同目录下
(2)需求分析:
(a)rsync命令原始拷贝:
rsync -av /opt/module root@hadoop103:/opt/
(b)期望脚本:
xsync要同步的文件名称
(c)说明:在/home/atguigu/bin这个目录下存放的脚本,atguigu用户可以在系统任何地方直接执行。
(3)脚本实现
(a)在/home/atguigu目录下创建xsync文件
cd /home/atguigu
vim xsync
在该文件中编写如下代码
#!/bin/bash
\#1. 判断参数个数
if [ $# -lt 1 ]
then
echo Not Enough Arguement!
exit;
fi
\#2. 遍历集群所有机器
for host in hadoop102 hadoop103 hadoop104
do
echo ==================== $host ====================
\#3. 遍历所有目录,挨个发送
for file in $@
do
\#4 判断文件是否存在
if [ -e $file ]
then
\#5. 获取父目录
pdir=$(cd -P $(dirname $file); pwd)
\#6. 获取当前文件的名称
fname=$(basename $file)
ssh $host "mkdir -p $pdir"
rsync -av $pdir/$fname $host:$pdir
else
echo $file does not exists!
fi
done
done
(b)修改脚本 xsync 具有执行权限
chmod +x xsync
(c)将脚本移动到/bin中,以便全局调用
sudo mv xsync /bin/
(d)测试脚本
sudo xsync /bin/xsync
2.2.5 免密登入
免密登入的原理
当一台虚拟机和另一台虚拟机进行交流的时候,第一台虚拟机有p,另一台虚拟机有q,进行传输的时候,如果有人入侵,这个入侵者既没有又没有q,因此这个入侵者无法入侵
首先让102这台机器生成p,q,然后把q发给102
生成公钥和私钥
ssh-keygen -t rsa
然后敲三个回车,就会生成两个文件id_rsa(私钥)、id_rsa.pub(公钥)
将公钥拷贝到自己的机器上
ssh-copy-id hadoop102
将/root/.ssh文件夹分发给别的机器
[root@hadoop102 etc]# xsync /root/.ssh
这样每台机器都有公钥和私钥就可以互相传递文件了
[atguigu@hadoop102 ~]$ xsync /home/atguigu/.ssh
用什么账户进行免密配置,就生成什么
因此需要在atguigu和root账户都进行这个操作
.ssh文件夹下(~/.ssh)的文件功能解释
known_hosts | 记录ssh访问过计算机的公钥(public key) |
---|---|
id_rsa | 生成的私钥 |
id_rsa.pub | 生成的公钥 |
authorized_keys | 存放授权过的无密登录服务器公钥 |
2.2.6 集群配置
1)集群部署规划
注意:NameNode和SecondaryNameNode 不要安装在同一台服务器上
注意:ResourceManager也很消耗内存,不要和NameNode、SecondaryNameNode配置在同一台机器上
hadoop102 | hadoop103 | hadoop104 | |
---|---|---|---|
HDFS | NameNode DataNode | DataNode | SecondaryNameNode DataNode |
YARN | NodeManager | ResourceManager NodeManager | NodeManager |
2)配置集群
(1)核心配置文件(在hadoop102上面进行配置) 配置core-site.xml
cd $HADOOP_HOME/etc/hadoop
vim core-site.xml
文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!--指定NameNode的地址-->
<property>
<name>fs.defaultFS</name>
<value>hdfs://hadoop102:8020</value>
</property>
<property>
<!--hadoop数据的存储目录-->
<name>hadoop.data.dir</name>
<value>/opt/module/hadoop-3.1.3/data</value>
</property>
<property>
<name>hadoop.proxyuser.atguigu.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.atguigu.groups</name>
<value>*</value>
</property>
</configuration>
(2)HDFS配置文件
配置hdfs-site.xml
vim hdfs-site.xml
文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!--NameNode数据的存储目录 -->
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.data.dir}/name</value>
</property>
<property>
<!--Datanode数据的存储目录-->
<name>dfs.datanode.data.dir</name>
<value>file://${hadoop.data.dir}/data</value>
</property>
<property>
<name>dfs.namenode.checkpoint.dir</name>
<value>file://${hadoop.data.dir}/namesecondary</value>
</property>
<property>
<!--兼容性配置-->
<name>dfs.client.datanode-restart.timeout</name>
<value>30s</value>
</property>
<property>
<!--2nn web端访问的地址-->
<name>dfs.namenode.secondary.http-address</name>
<value>hadoop104:9868</value>
</property>
</configuration>
(3)YARN配置文件
配置yarn-site.xml
vim yarn-site.xml
文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>hadoop103</value>
</property>
<property>
<name>yarn.nodemanager.env-whitelist</name>
<value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
</property>
</configuration>
(4)MapReduce配置文件
配置mapred-site.xml
vim mapred-site.xml
文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configuration>
(5)配置workers
vim workers
hadoop102
hadoop103
hadoop104
(6)配置到所有机器
[root@hadoop102 hadoop]# cd ..
[root@hadoop102 etc]# xsync hadoop
2.2.7 集群单点启动
(1)如果集群是第一次启动,需要格式化NameNode
在配置namenode的节点上面格式化
hdfs namenode -format
(2)在hadoop102上启动NameNode
hdfs --daemon start namenode
完成后执行jps命令,看到如下结果(进程号可能不同):
3461 NameNode
(3)在hadoop102、hadoop103以及hadoop104上执行如下命令(三台都要执行)
hdfs --daemon start datanode
(4)思考:每次都一个一个节点启动,如果节点数增加到1000个怎么办?
早上来了开始一个一个节点启动,到晚上下班刚好完成,下班?
2.2.8 群起集群
\1. 配置workers
vim /opt/module/hadoop-3.1.3/etc/hadoop/workers
在该文件中增加如下内容:
hadoop102
hadoop103
hadoop104
注意:该文件中添加的内容结尾不允许有空格,文件中不允许有空行。
同步所有节点配置文件
xsync /opt/module/hadoop-3.1.3/etc
\2. 启动集群
(1)如果集群是第一次启动,需要在hadoop102节点格式化NameNode(注意格式化之前,一定要先停止上次启动的所有namenode和datanode进程,然后再删除data和log数据)(因为namenode会根据信息去寻找datanode)
hdfs namenode -format
(2)启动HDFS(在$HADOOP目录下)
sbin/start-dfs.sh
(3)**在配置了ResourceManager的节点(hadoop103)**启动YARN
sbin/start-yarn.sh
输入http://hadoop104:9868/status.html看是否正常
查看http://hadoop102:9870/explorer.html#/看是否正常
3 .集群基本测试
(1)上传文件到集群
上传小文件
hadoop fs -mkdir -p /user/atguigu/input
hadoop fs -put $HADOOP_HOME/wcinput/wc.input /user/atguigu/input
上传大文件
hadoop fs -put /opt/software/hadoop-3.1.3.tar.gz /
(2)上传文件后查看文件存放在什么位置
(a)查看HDFS文件存储路径
[atguigu@hadoop102 subdir0]$ pwd
/opt/module/hadoop-3.1.3/data/tmp/dfs/data/current/BP-938951106-192.168.10.107-1495462844069/current/finalized/subdir0/subdir0
(b)查看HDFS在磁盘存储文件内容
[atguigu@hadoop102 subdir0]$ cat blk_1073741825
hadoop yarn
hadoop mapreduce
atguigu
atguigu
(3)拼接
-rw-rw-r--. 1 atguigu atguigu 134217728 5月 23 16:01 **blk_1073741836**
-rw-rw-r--. 1 atguigu atguigu 1048583 5月 23 16:01 blk_1073741836_1012.meta
-rw-rw-r--. 1 atguigu atguigu 63439959 5月 23 16:01 **blk_1073741837**
-rw-rw-r--. 1 atguigu atguigu 495635 5月 23 16:01 blk_1073741837_1013.meta
[atguigu@hadoop102 subdir0]$ cat blk_1073741836>>tmp.jar
[atguigu@hadoop102 subdir0]$ cat blk_1073741837>>tmp.jar
[atguigu@hadoop102 subdir0]$ tar -zxvf tmp.jar
(4)下载
[atguigu@hadoop102 hadoop-3.1.3]$ bin/hadoop fs -get
/hadoop-3.1.3.tar.gz ./
(5)执行wordcount程序
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount /user/atguigu/input /user/atguigu/output
2.2.9 集群启动/停止方式总结
1 各个服务组件逐一启动/停止
(1)分别启动/停止HDFS组件
hdfs –daemon start/stop namenode/datanode/secondarynamenode
(2)启动/停止YARN
yarn --daemon start/stop resourcemanager/nodemanager
2 .各个模块分开启动/停止(配置ssh是前提)常用
(1)整体启动/停止HDFS
start-dfs.sh/stop-dfs.sh
(2)整体启动/停止YARN
start-yarn.sh/stop-yarn.sh
2.2.10 配置历史服务器和日志聚集服务器
为了查看程序的历史运行情况,需要配置一下历史服务器。具体配置步骤如下:
第一步
在hadoop102上面
stop-dfs.sh
再hadoop103上面
stop-yarn.sh
第二步(配置历史服务器)
(再hadoop102上面)
vi mapred-site.xml
加入
<!-- 历史服务器端地址 -->
<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop102:10020</value>
</property>
<!-- 历史服务器web端地址 -->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>hadoop102:19888</value>
</property>
第三步配置日志的聚集
(再hadoop102上面
日志聚集概念:应用运行完成以后,将程序运行日志信息上传到HDFS系统上。
日志聚集功能好处:可以方便的查看到程序运行详情,方便开发调试。
注意:开启日志聚集功能,需要重新启动NodeManager 、ResourceManager和HistoryManager。
开启日志聚集功能具体步骤如下:
1 配置yarn-site.xml(再$HADOOP/etc/hadoop里面)
vim yarn-site.xml
在该文件里面增加如下配置
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<property>
<name>yarn.log.server.url</name>
<value>http://hadoop102:19888/jobhistory/logs</value>
</property>
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
第四步分发配置
xsync $HADOOP_HOME/etc/hadoop/
第五步开启服务
增加一个开启日志(在102上面执行)
[atguigu@hadoop102 etc]$ mapred --daemon start historyserver
第六步查看日志
http://hadoop102:19888/jobhistory
2.2.11 建立群体执行脚本xcall
在/home/atguigu下面新建xcall文件
#!/bin/bash
#在三个虚拟机上面同时执行一条语句
if [ $# -eq 0 ]
then
echo "请输入需要执行的命令"
exit;
fi
for host in hadoop102 hadoop103 hadoop104
do
echo ----------------------$host---------------------
ssh $host $1
done
改进
#!/bin/bash
#在三个虚拟机上面同时执行一条语句
if [ $# -eq 0 ]
then
echo "请输入需要执行的命令"
exit;
fi
path=$(cd ./;pwd)
for host in hadoop102 hadoop103 hadoop104
do
echo ----------------------$host---------------------
ssh $host "cd $path;"$*""
done
~
~
2.2.12 集群时间同步
时间同步的方式:找一个机器,作为时间服务器,所有的机器与这台集群时间进行定时的同步,比如,每隔十分钟,同步时间一次
配置时间同步具体实操:
\1. 时间服务器配置(必须root用户)
(1)在所有节点关闭ntp服务和自启动
systemctl stop ntpd
systemctl disable ntpd
(2)修改ntp配置文件
vim /etc/ntp.conf
修改内容如下
a)修改1(授权192.168.1.0-192.168.1.255网段上的所有机器可以从这台机器上查询和同步时间)
#restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap
为
restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap
b)修改2(集群在局域网中,不使用其他互联网上的时间)
server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst
为
#server 0.centos.pool.ntp.org iburst
#server 1.centos.pool.ntp.org iburst
#server 2.centos.pool.ntp.org iburst
#server 3.centos.pool.ntp.org iburst
c)添加3(当该节点丢失网络连接,依然可以采用本地时间作为时间服务器为集群中的其他节点提供时间同步)
server 127.127.1.0
fudge 127.127.1.0 stratum 10
(3)修改/etc/sysconfig/ntpd 文件
vim /etc/sysconfig/ntpd
增加内容如下(让硬件时间与系统时间一起同步)
SYNC_HWCLOCK=yes
(4)重新启动ntpd服务
systemctl start ntpd
(5)设置ntpd服务开机启动
systemctl enable ntpd
\2. 其他机器配置(必须root用户)
(1)在其他机器配置10分钟与时间服务器同步一次
crontab -e
编写定时任务如下:
*/10 * * * * /usr/sbin/ntpdate hadoop102
(2)修改任意机器时间
date -s "2017-9-11 11:11:11"
(3)十分钟后查看机器是否与时间服务器同步
date
说明:测试的时候可以将10分钟调整为1分钟,节省时间。
2.2.13 hadoop编译源码
5.1 前期准备工作
\1. CentOS联网
配置CentOS能连接外网。Linux虚拟机ping www.baidu.com 是畅通的
注意:采用root角色编译,减少文件夹权限出现问题
\2. jar包准备(hadoop源码、JDK8、maven、ant 、protobuf)
(1)hadoop-3.1.3-src.tar.gz
(2)jdk-8u212-linux-x64.tar.gz
(3)apache-ant-1.9.9-bin.tar.gz(build工具,打包用的)
(4)apache-maven-3.0.5-bin.tar.gz
(5)protobuf-2.5.0.tar.gz(序列化的框架)
5.2 jar包安装
注意:所有操作必须在root用户下完成
\1. JDK解压、配置环境变量 JAVA_HOME和PATH,验证java-version(如下都需要验证是否配置成功)
[root@hadoop101 software] # tar -zxf jdk-8u212-linux-x64.tar.gz -C /opt/module/
[root@hadoop101 software]# vi /etc/profile
#JAVA_HOME:
export JAVA_HOME=/opt/module/jdk1.8.0_212
export PATH=$PATH:$JAVA_HOME/bin
[root@hadoop101 software]#source /etc/profile
验证命令:java -version
\2. Maven解压、配置 MAVEN_HOME和PATH
[root@hadoop101 software]# tar -zxvf apache-maven-3.0.5-bin.tar.gz -C /opt/module/
[root@hadoop101 apache-maven-3.0.5]# vi conf/settings.xml
nexus-aliyun
central
Nexus aliyun
http://maven.aliyun.com/nexus/content/groups/public
[root@hadoop101 apache-maven-3.0.5]# vi /etc/profile
#MAVEN_HOME
export MAVEN_HOME=/opt/module/apache-maven-3.0.5
export PATH=$PATH:$MAVEN_HOME/bin
[root@hadoop101 software]#source /etc/profile
验证命令:mvn -version
\3. ant解压、配置 ANT _HOME和PATH
[root@hadoop101 software]# tar -zxvf apache-ant-1.9.9-bin.tar.gz -C /opt/module/
[root@hadoop101 apache-ant-1.9.9]# vi /etc/profile
#ANT_HOME
export ANT_HOME=/opt/module/apache-ant-1.9.9
export PATH=$PATH:$ANT_HOME/bin
[root@hadoop101 software]#source /etc/profile
验证命令:ant -version
\4. 安装 glibc-headers 和 g++ 命令如下
[root@hadoop101 apache-ant-1.9.9]# yum install glibc-headers
[root@hadoop101 apache-ant-1.9.9]# yum install gcc-c++
\5. 安装make和cmake
[root@hadoop101 apache-ant-1.9.9]# yum install make
[root@hadoop101 apache-ant-1.9.9]# yum install cmake
\6. 解压protobuf ,进入到解压后protobuf主目录,/opt/module/protobuf-2.5.0,然后相继执行命令
[root@hadoop101 software]# tar -zxvf protobuf-2.5.0.tar.gz -C /opt/module/
[root@hadoop101 opt]# cd /opt/module/protobuf-2.5.0/
[root@hadoop101 protobuf-2.5.0]#./configure
[root@hadoop101 protobuf-2.5.0]# make
[root@hadoop101 protobuf-2.5.0]# make check
[root@hadoop101 protobuf-2.5.0]# make install
[root@hadoop101 protobuf-2.5.0]# ldconfig
[root@hadoop101 hadoop-dist]# vi /etc/profile
#LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/opt/module/protobuf-2.5.0
export PATH=$PATH:$LD_LIBRARY_PATH
[root@hadoop101 software]#source /etc/profile
**验证命令:**protoc –version
\7. 安装openssl库
[root@hadoop101 software]#yum install openssl-devel
\8. 安装 ncurses-devel库
[root@hadoop101 software]#yum install ncurses-devel
到此,编译工具安装基本完成。
5.3 编译源码
\1. 解压源码到/opt/目录
[root@hadoop101 software]# tar -zxvf hadoop-3.1.3-src.tar.gz -C /opt/
\2. 进入到hadoop源码主目录
[root@hadoop101 hadoop-3.1.3-src]# pwd
/opt/hadoop-3.1.3-src
\3. 通过maven执行编译命令
[root@hadoop101 hadoop-3.1.3-src]#mvn package -Pdist,native -DskipTests -Dtar
等待时间30分钟左右,最终成功是全部SUCCESS,如图2-42所示。
图2-42 编译源码
\4. 成功的64位hadoop包在/opt/hadoop-3.1.3-src/hadoop-dist/target下
[root@hadoop101 target]# pwd
/opt/hadoop-3.1.3-src/hadoop-dist/target
\5. 编译源码过程中常见的问题及解决方案
(1)MAVEN install时候JVM内存溢出
处理方式:在环境配置文件和maven的执行文件均可调整MAVEN_OPT的heap大小。(详情查阅MAVEN 编译 JVM调优问题,如:http://outofmemory.cn/code-snippet/12652/maven-outofmemoryerror-method)
(2)编译期间maven报错。可能网络阻塞问题导致依赖库下载不完整导致,多次执行命令(一次通过比较难):
[root@hadoop101 hadoop-3.1.3-src]#mvn package -Pdist,nativeN -DskipTests -Dtar
(3)报ant、protobuf等错误,插件下载未完整或者插件版本问题,最开始链接有较多特殊情况,同时推荐
2.7.0版本的问题汇总帖子 http://www.tuicool.com/articles/IBn63qf
2.2.14 jiqun一键启脚本
#!/bin/bash
if [ $# == 0 ]
then
echo "参数不能为空"
exit;
fi
case $1 in
"start")
echo "======================start hdfs ======================"
ssh hadoop102 "/opt/module/hadoop-3.1.3/sbin/start-dfs.sh"
echo "======================start yarn ======================"
ssh hadoop103 "/opt/module/hadoop-3.1.3/sbin/start-yarn.sh"
;;
"stop")
echo "=======================stop yarn ========================="
ssh hadoop103 "/opt/module/hadoop-3.1.3/sbin/stop-yarn.sh"
echo "=======================stop hdfs ========================="
ssh hadoop102 "/opt/module/hadoop-3.1.3/sbin/stop-dfs.sh"
;;
*)
echo "参数错误,参数为start/stop"
;;
esac
第三章 HDFS
3.1 HDFS概述
3.1.1 HDFS产生背景
随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统,HDFS只是分布式文件管理系统中的一种
3.1.2 HDFS定义
HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色
HDFS的使用场景:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用
3.2 HDFS优缺点
3.2.1 优点
1)高容错性
(1)数据自动保存多个副本。它通过增加副本的形式,提高容错性
(2)某一个副本丢失之后,它可以自动恢复
2)适合处理大数据
(1)数据规模:能够处理数据规模达到GB、TB、甚至PB级别的数据;
(2)文件规模:能够处理百万规模以上的文件数量,数量相当之大
3)可构建在廉价机器上,通过多副本机制,提高可靠性
3.2.2 缺点
- 不适合低延迟数据访问,比如毫秒级的存储数据,是做不到的
2)无法高效的对大量小文件进行存储
(1)存储大量小文件的话,它会占用大量的NameNode大量的内存来存储文件目录,和块信息,这样是不合理的,因为NameNode的内存总是有限的
(2)小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标 3)不支持并发写入,文件随机修改
(1)一个文件只能有一个写,不允许多个线程同时写;
(2)仅支持数据append(追加),不支持文件的随机修改
3.3 HDFS组成架构
3.4 HDFS文件块大小(面试重点)
HDFS中的文件在物理上是分块存储(Block),块的大小可以通过配置参数(dfs.blocksize)来规定,默认大小在Hadoop2.x版本中是128M,老版本中是64M
思考:为什么块的大小不能设置太小,也不能设置太大?
(1)HDFS的块设置太小,会增加寻址时间,程序一直在找块的开始位置;
(2)如果块设置的太大,从磁盘传输数据的时间会明显大于定位这个块开始所需的时间,导致程序在处理这块数据时,会非常慢
总结:HDFS块的大小设置主要取决于磁盘传输速率
3.5 HDFS的Shell操作(开发重点)
1.基本语法
bin/hadoop fs 具体命令 OR bin/hdfs dfs 具体命令(两者没有任何区别)
dfs时fs的实现类
2.常用命令实操
(0)启动Hadoop集群(方便后续的测试)
[atguigu@hadoop102 hadoop-3.1.3]$ sbin/start-dfs.sh
[atguigu@hadoop103 hadoop-3.1.3]$ sbin/start-yarn.sh
(1)-help:输出这个命令参数
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -help rm
(2)-ls:显示目录信息
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -ls /
(3)-mkdir:在HDFS上创建目录
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir -p /sanguo/shuguo
(4)-moveFromLocal:从本地剪切粘贴到HDFS
[atguigu@hadoop102 hadoop-3.1.3]$ touch kongming.txt
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -moveFromLocal ./kongming.txt /sanguo/shuguo
(5)-appendToFile:追加一个文件到已经存在的文件末尾
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -appendToFile mars.txt /xiao/xiaodidi.txt
(6)-cat:显示文件内容
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -cat /xiao/xiaodidi.txt
2020-11-24 20:13:50,795 INFO sasl.SaslDataTransferClient: SASL encryption trust check: localHostTrusted = false, remoteHostTrusted = false
江豪迪怎么这么帅啊
noasjdfoasjdofi
(7)-chgrp 、-chmod、-chown:Linux文件系统中的用法一样,修改文件所属权限
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -chmod 666 /sanguo/shuguo/kongming.txt
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -chown atguigu:atguigu /sanguo/shuguo/kongming.txt
(8)-copyFromLocal:从本地文件系统中拷贝文件到HDFS路径去
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -copyFromLocal README.txt /
(9)-copyToLocal:从HDFS拷贝到本地
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./
(10)-cp :从HDFS的一个路径拷贝到HDFS的另一个路径
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -cp /sanguo/shuguo/kongming.txt /zhuge.txt
(11)-mv:在HDFS目录中移动文件
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -mv /zhuge.txt /sanguo/shuguo/
(12)-get:等同于copyToLocal,就是从HDFS下载文件到本地
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -get /sanguo/shuguo/kongming.txt ./
(13)-getmerge:合并下载多个文件,比如HDFS的目录 /user/atguigu/test下有多个文件:log.1, log.2,log.3,…
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -getmerge /user/atguigu/test/* ./zaiyiqi.txt
(14)-put:等同于copyFromLocal
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -put ./zaiyiqi.txt /user/atguigu/test/
(15)-tail:显示一个文件的末尾
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -tail /sanguo/shuguo/kongming.txt
(16)-rm:删除文件或文件夹
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -rm /user/atguigu/test/jinlian2.txt
(17)-rmdir:删除空目录
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir /test
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -rmdir /test
(18)-du统计文件夹的大小信息 (+-s是汇总的效果,加-h是显示2.7k这种信息的效果)
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -du -s -h /user/atguigu/test
2.7 K /user/atguigu/test
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -du -h /user/atguigu/test
1.3 K /user/atguigu/test/README.txt
15 /user/atguigu/test/jinlian.txt
1.4 K /user/atguigu/test/zaiyiqi.txt
(19)-setrep:设置HDFS中文件的副本数量
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -setrep 5 /README.txt
3.6 hdfs web没有权限的操作
(20)解决web页面中操作没有权限问题,如图2-2
图2-2 web页面操作报错
hadoop默认情况下开启了权限检查,且默认使用dir.who作为http访问的静态用户,因此可通过关闭权限检查或者配置http访问的静态用户为atguigu,二选一即可.
在core-site.xml中修改http访问的静态用户为atguigu
<property>
<name>hadoop.http.staticuser.user</name>
<value>atguigu</value>
</property>
或在hdfs-site.xml中关闭权限检查
<property>
<name>dfs.permissions.enabled</name>
<value>false</value>
</property>
3.7 HDFS客户端环境准备
复制Hadoop3.0 到指定目录
3.7.1 加入环境变量
把hadoop.dll放入windows/system32(防止最后运行的时候报找不到的错误)
最后通过cmd 输入winutils进行测试
重启计算机
3.7.2 新建maven工程,进入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-api</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-runtime</artifactId>
<version>3.1.3</version>
</dependency>
</dependencies>
3.7.3 加入log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true" name="XMLConfig">
<Appenders>
<!-- 类型名为Console,名称为必须属性 -->
<Appender type="Console" name="STDOUT">
<!-- 布局为PatternLayout的方式,
输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
<Layout type="PatternLayout"
pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
</Appender>
</Appenders>
<Loggers>
<!-- 可加性为false -->
<Logger name="test" level="info" additivity="false">
<AppenderRef ref="STDOUT" />
</Logger>
<!-- root loggerConfig设置 -->
<Root level="info">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
3.7.4 创建包名,并创建HdfsClient类
package com.atguigu.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Test;
import java.net.URI;
import java.net.URL;
public class HDFSClient {
@Test
public void test() throws Exception{
//1.获取客户端对象,文件系统对象(这个就是hdfs)
FileSystem fileSystem = FileSystem.get(URI.create("hdfs://hadoop102:8020"), new Configuration(), "atguigu");//注意这里的user和linux对应
//2.操作集群,创建文件夹
fileSystem.mkdirs(new Path("/testJava"));//在hdfs下面创建文件夹
//3.管理资源
fileSystem.close();
}
}
3.8 HDFS的API操作
3.8.1 HDFS文件上传(测试参数优先级)
/**
* 本地文件复制到hadoop上面
* @throws Exception
*/
@Test
public void Test1() throws Exception{
//获取客户端对象
FileSystem a = FileSystem.get(URI.create("hdfs://hadoop102:8020"),new Configuration(), "atguigu");
//API把本地文件复制到hdfs上面
//delSrc 代表是否删除本地的文件(相当于剪切)
//overwrite 代表是否覆盖hdfs上面的文件
a.copyFromLocalFile(false,true,new Path("I:\\aa尚硅谷大数据8月\\大数据_8月结课\\04.0.Hadoop\\Day01\\1_大数据概论.mp4"),new Path("/testJava"));
a.close();
}
在项目的resources中新建hdfs-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>
发现副本数变成了1
在代码中configuration.set(“dfs.replication”,“2”)
entries.set("dfs.replication","2");
FileSystem a = FileSystem.get(URI.create("hdfs://hadoop102:8020"),entries, "atguigu");
发现副本数变成了2
总结:
参数优先级排序:(1)客户端代码中设置的值 >(2)resources中用户自定义配置文件 >(3)然后是服务器的自定义配置(xxx-site.xml) >(4)服务器的默认配置(xxx-default.xml)
3.8.2 hdfs文件下载
@Test
public void download() throws Exception{
//其中useRamLocalFileSystem中true代表不要开启检查文件系统,false代表开启检查文件系统,
//会多出一个crc文件来进行校验(不能下载目录)
fileSystem.copyToLocalFile(false,new Path("/testJava/1_大数据概论.mp4"),new Path("G:\\test"),true);
}
3.8.3 HDFS文件夹删除
@Test
public void delete() throws Exception {
//如果b是true那么删除文件还是目录都可以删除
//如果b是false那么可以删除空目录和文件,但是不能输出有文件的目录
fileSystem.delete(new Path("/testJava"), true);
}
3.8.4 HDFS文件名更改和移动
@Test
public void testRename() throws IOException, InterruptedException, URISyntaxException{
// 1 获取文件系统
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:9820"), configuration, "atguigu");
// 2 修改文件名称
fs.rename(new Path("/banzhang.txt"), new Path("/banhua.txt"));
// 3 关闭资源
fs.close();
}
3.8.5 HDFS文件详细查看
查看文件名称、权限、长度、块信息
@Test
public void testListFiles() throws IOException, InterruptedException, URISyntaxException{
// 1获取文件系统
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:9820"), configuration, "atguigu");
// 2 获取文件详情
//第二个参数是否递归
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
while(listFiles.hasNext()){
LocatedFileStatus status = listFiles.next();
// 输出详情
// 文件名称
System.out.println(status.getPath().getName());
// 长度
System.out.println(status.getLen());
// 权限
System.out.println(status.getPermission());
// 分组
System.out.println(status.getGroup());
// 获取存储的块信息
BlockLocation[] blockLocations = status.getBlockLocations();
for (BlockLocation blockLocation : blockLocations) {
// 获取块存储的主机节点
String[] hosts = blockLocation.getHosts();
for (String host : hosts) {
System.out.println(host);
}
}
System.out.println("-----------漂亮的分割线----------");
}
// 3 关闭资源
fs.close();
}
3.8.6 HDFS文件和文件夹判断
@Test
public void FileStatus() throws Exception {
FileStatus[] fileStatuses = fileSystem.listStatus(new Path("/"));
for (FileStatus fileStatus : fileStatuses) {
if (fileStatus.isDirectory()) {
//要先得到它的路径,再得到他的名字
System.out.println(fileStatus.getPath().getName() + "是一个目录");
}
if (fileStatus.isFile()) {
System.out.println(fileStatus.getPath().getName() + "是一个文件");
}
}
}
3.8.7 基于io流实现文件的上传和下载
@Test
public void io() throws Exception{
//定义输入输出流
FileInputStream fileInputStream = new FileInputStream(new File("H:\\java\\新建文本文档.txt"));
FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path("/aabb.txt"));
//根据配置文件复制流
IOUtils.copyBytes(fileInputStream, fsDataOutputStream, new Configuration());
//关闭资源
IOUtils.closeStream(fileInputStream);
IOUtils.closeStream(fsDataOutputStream);
}
@Test
public void io1() throws Exception {
FSDataInputStream open = fileSystem.open(new Path("/aabb.txt"));
FileOutputStream fileOutputStream = new FileOutputStream("I:\\aabb.txt");
IOUtils.copyBytes(open,fileOutputStream, configuration);
IOUtils.closeStream(fileOutputStream);
IOUtils.closeStream(open);
}
3.9HDFS写读数据流程
3.9.1 刨析文件的写入
1)客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
2)NameNode返回是否可以上传。
3)客户端请求第一个 Block上传到哪几个DataNode服务器上。
4)NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
5)客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
6)dn1、dn2、dn3逐级应答客户端。
7)客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
8)当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)。
如果一个DataNode出现了故障,其他两个重新建立通道进行传输
如果三个DataNode出现了故障,通道断开
3.9.2 网络拓扑-节点距离计算
再HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离DataNode接受数据,那么这个最近距离怎么计算呢>
节点距离:两个节点到达最近的共同祖先的距离总和
3.9.3 机架感知(副本存储节点选择)
1.机架感知说明
For the common case, when the replication factor is three, HDFS’s placement policy is to put one replica on the local machine if the writer is on a datanode, otherwise on a random datanode, another replica on a node in a different (remote) rack, and the last on a different node in the same remote rack.
3.9.4 HDFS读数据流程
1)客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。
2)挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。
3)DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)。
4)客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。
3.10 NameNode和SecondaryNameNode(面试开发重点)
3.10.1 NN和2NN工作机制
思考:NameNode中的元数据是存储在哪里的?
首先,我们做个假设,如果存储在NameNode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。因此,元数据需要存放在内存中。但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了。因此产生在磁盘中备份元数据的FsImage。
这样又会带来新的问题,当在内存中的元数据更新时,如果同时更新FsImage,就会导致效率过低,但如果不更新,就会发生一致性问题,一旦NameNode节点断电,就会产生数据丢失。因此,引入Edits文件(只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。这样,一旦NameNode节点断电,可以通过FsImage和Edits的合并,合成元数据。
但是,如果长时间添加数据到Edits中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行FsImage和Edits的合并,如果这个操作由NameNode节点完成,又会效率过低。因此,引入一个新的节点SecondaryNamenode,专门用于FsImage和Edits的合并。
\1. 第一阶段:NameNode启动
(1)第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。
(2)客户端对元数据进行增删改的请求。
(3)NameNode记录操作日志,更新滚动日志。
(4)NameNode在内存中对元数据进行增删改。
\2.第二阶段:Secondary NameNode工作
(1)Secondary NameNode询问NameNode是否需要CheckPoint。直接带回NameNode是否检查结果。
(2)Secondary NameNode请求执行CheckPoint。
(3)NameNode滚动正在写的Edits日志。
(4)将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。
(5)Secondary NameNode加载编辑日志和镜像文件到内存,并合并。
(6)生成新的镜像文件fsimage.chkpoint。
(7)拷贝fsimage.chkpoint到NameNode。
(8)NameNode将fsimage.chkpoint重新命名成fsimage。
NN和2NN工作机制详解:
Fsimage:NameNode内存中元数据序列化后形成的文件。
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中。
3.10.2 Fsimage和Edits解析
Fsimage和Edits概念
\2. oiv查看Fsimage文件
(1)查看oiv和oev命令
[atguigu@hadoop102 current]$ hdfs
**oiv** apply the offline fsimage viewer to an fsimage
**oev** apply the offline edits viewer to an edits file
(2)基本语法
hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径
(3)案例实操
[atguigu@hadoop102 current]$ pwd
/opt/module/hadoop-3.1.3/data/tmp/dfs/name/current
[atguigu@hadoop102 current]$ hdfs oiv -p XML -i fsimage_0000000000000000025 -o /opt/module/hadoop-3.1.3/fsimage.xml
[atguigu@hadoop102 current]$ cat /opt/module/hadoop-3.1.3/fsimage.xml
将显示的xml文件内容拷贝到Eclipse中创建的xml文件中,并格式化。部分显示结果如下。
<inode>
<id>16386</id>
<type>DIRECTORY</type>
<name>user</name>
<mtime>1512722284477</mtime>
<permission>atguigu:supergroup:rwxr-xr-x</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16387</id>
<type>DIRECTORY</type>
<name>atguigu</name>
<mtime>1512790549080</mtime>
<permission>atguigu:supergroup:rwxr-xr-x</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16389</id>
<type>FILE</type>
<name>wc.input</name>
<replication>3</replication>
<mtime>1512722322219</mtime>
<atime>1512722321610</atime>
<perferredBlockSize>134217728</perferredBlockSize>
<permission>atguigu:supergroup:rw-r--r--</permission>
<blocks>
<block>
<id>1073741825</id>
<genstamp>1001</genstamp>
<numBytes>59</numBytes>
</block>
</blocks>
</inode >
思考:可以看出,Fsimage中没有记录块所对应DataNode,为什么?
在集群启动后,要求DataNode上报数据块信息,并间隔一段时间后再次上报。
\3. oev查看Edits文件
(1)基本语法
hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径
(2)案例实操
[atguigu@hadoop102 current]$ hdfs oev -p XML -i edits_0000000000000000012-0000000000000000013 -o /opt/module/hadoop-3.1.3/edits.xml
[atguigu@hadoop102 current]$ cat /opt/module/hadoop-3.1.3/edits.xml
将显示的xml文件内容拷贝到Eclipse中创建的xml文件中,并格式化。显示结果如下。
<?xml version="1.0" encoding="UTF-8"?>
<EDITS>
<EDITS_VERSION>-63</EDITS_VERSION>
<RECORD>
<OPCODE>OP_START_LOG_SEGMENT</OPCODE>
<DATA>
<TXID>129</TXID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ADD</OPCODE>
<DATA>
<TXID>130</TXID>
<LENGTH>0</LENGTH>
<INODEID>16407</INODEID>
<PATH>/hello7.txt</PATH>
<REPLICATION>2</REPLICATION>
<MTIME>1512943607866</MTIME>
<ATIME>1512943607866</ATIME>
<BLOCKSIZE>134217728</BLOCKSIZE>
<CLIENT_NAME>DFSClient_NONMAPREDUCE_-1544295051_1</CLIENT_NAME>
<CLIENT_MACHINE>192.168.1.5</CLIENT_MACHINE>
<OVERWRITE>true</OVERWRITE>
<PERMISSION_STATUS>
<USERNAME>atguigu</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
<RPC_CLIENTID>908eafd4-9aec-4288-96f1-e8011d181561</RPC_CLIENTID>
<RPC_CALLID>0</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>
<DATA>
<TXID>131</TXID>
<BLOCK_ID>1073741839</BLOCK_ID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_GENSTAMP_V2</OPCODE>
<DATA>
<TXID>132</TXID>
<GENSTAMPV2>1016</GENSTAMPV2>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ADD_BLOCK</OPCODE>
<DATA>
<TXID>133</TXID>
<PATH>/hello7.txt</PATH>
<BLOCK>
<BLOCK_ID>1073741839</BLOCK_ID>
<NUM_BYTES>0</NUM_BYTES>
<GENSTAMP>1016</GENSTAMP>
</BLOCK>
<RPC_CLIENTID></RPC_CLIENTID>
<RPC_CALLID>-2</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_CLOSE</OPCODE>
<DATA>
<TXID>134</TXID>
<LENGTH>0</LENGTH>
<INODEID>0</INODEID>
<PATH>/hello7.txt</PATH>
<REPLICATION>2</REPLICATION>
<MTIME>1512943608761</MTIME>
<ATIME>1512943607866</ATIME>
<BLOCKSIZE>134217728</BLOCKSIZE>
<CLIENT_NAME></CLIENT_NAME>
<CLIENT_MACHINE></CLIENT_MACHINE>
<OVERWRITE>false</OVERWRITE>
<BLOCK>
<BLOCK_ID>1073741839</BLOCK_ID>
<NUM_BYTES>25</NUM_BYTES>
<GENSTAMP>1016</GENSTAMP>
</BLOCK>
<PERMISSION_STATUS>
<USERNAME>atguigu</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
</DATA>
</RECORD>
</EDITS >
3.10.3 CheckPoint时间设置
(1)通常情况下,SecondaryNameNode每隔一小时执行一次。
[hdfs-default.xml]
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
(2)一分钟检查一次操作次数,3当操作次数达到1百万时,SecondaryNameNode执行一次。
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
<description>操作动作次数</description>
</property>
<property>
<name>dfs.namenode.checkpoint.check.period</name>
<value>60</value>
<description> 1分钟检查一次操作次数</description>
</property >
3.10.4 故障处理(被HA取代)
NameNode故障后,可以采用如下两种方法恢复数据。
方法一:将SecondaryNameNode中数据拷贝到NameNode存储数据的目录;
\1. kill -9 NameNode进程
\2. 删除NameNode存储的数据(/opt/module/hadoop-3.1.3/data/tmp/dfs/name)
[atguigu@hadoop102 hadoop-3.1.3]$ rm -rf /opt/module/hadoop-3.1.3/data/tmp/dfs/name/*
\3. 拷贝SecondaryNameNode中数据到原NameNode存储数据目录
[atguigu@hadoop102 dfs]$ scp -r atguigu@hadoop104:/opt/module/hadoop-3.1.3/data/tmp/dfs/namesecondary/* ./name/
\4. 重新启动NameNode
[atguigu@hadoop102 hadoop-3.1.3]$ hdfs --daemon start namenode
方法二:使用-importCheckpoint选项启动NameNode守护进程,从而将SecondaryNameNode中数据拷贝到NameNode目录中。
\1. 修改hdfs-site.xml中的
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>120</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/opt/module/hadoop-3.1.3/data/tmp/dfs/name</value>
</property>
\2. kill -9 NameNode进程
\3. 删除NameNode存储的数据(/opt/module/hadoop-3.1.3/data/tmp/dfs/name)
[atguigu@hadoop102 hadoop-3.1.3]$ rm -rf /opt/module/hadoop-3.1.3/data/tmp/dfs/name/*
\4. 如果SecondaryNameNode不和NameNode在一个主机节点上,需要将SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录,并删除in_use.lock文件
[atguigu@hadoop102 dfs]$ scp -r atguigu@hadoop104:/opt/module/hadoop-3.1.3/data/tmp/dfs/namesecondary ./
[atguigu@hadoop102 namesecondary]$ rm -rf in_use.lock
[atguigu@hadoop102 dfs]$ pwd
/opt/module/hadoop-3.1.3/data/tmp/dfs
[atguigu@hadoop102 dfs]$ ls
data name namesecondary
\5. 导入检查点数据(等待一会ctrl+c结束掉)
[atguigu@hadoop102 hadoop-3.1.3]$ bin/hdfs namenode -importCheckpoint
\6. 启动NameNode
[atguigu@hadoop102 hadoop-3.1.3]$ hdfs --daemon start namenode
3.10.5 集群安全模式
\1. 基本语法
集群处于安全模式,不能执行重要操作(写操作)。集群启动完成后,自动退出安全模式。
(1)bin/hdfs dfsadmin -safemode get (功能描述:查看安全模式状态)
(2)bin/hdfs dfsadmin -safemode enter (功能描述:进入安全模式状态)
(3)bin/hdfs dfsadmin -safemode leave (功能描述:离开安全模式状态)
(4)bin/hdfs dfsadmin -safemode wait (功能描述:等待安全模式状态)
\3. 案例
模拟等待安全模式
(1)查看当前模式
[atguigu@hadoop102 hadoop-3.1.3]$ hdfs dfsadmin -safemode get
Safe mode is OFF
(2)先进入安全模式
[atguigu@hadoop102 hadoop-3.1.3]$ bin/hdfs dfsadmin -safemode enter
(3)创建并执行下面的脚本
在/opt/module/hadoop-3.1.3路径上,编辑一个脚本safemode.sh
[atguigu@hadoop102 hadoop-3.1.3]$ touch safemode.sh
[atguigu@hadoop102 hadoop-3.1.3]$ vim safemode.sh
#!/bin/bash
hdfs dfsadmin -safemode wait
hdfs dfs -put /opt/module/hadoop-3.1.3/README.txt /
[atguigu@hadoop102 hadoop-3.1.3]$ chmod 777 safemode.sh
[atguigu@hadoop102 hadoop-3.1.3]$ ./safemode.sh
(4)再打开一个窗口,执行
[atguigu@hadoop102 hadoop-3.1.3]$ bin/hdfs dfsadmin -safemode leave
(5)观察
(a)再观察上一个窗口
Safe mode is OFF
(b)HDFS集群上已经有上传的数据了。
3.10.6 NameNode多目录配置
1 . NameNode的本地目录可以配置成多个,且每个目录存放内容相同,增加了可靠性
2 . 具体配置如下
(1) 在hdfs-site.xml文件中修改如下内容
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.tmp.dir}/name1,file://${hadoop.tmp.dir}/name2</value>
</property>
(2)分发配置文件
xsync hdfs-site.xml
(3)停止集群,删除data和logs中所有数据
xcall rm - rf data/ logs/
(4)查看结果
[atguigu@hadoop102 dfs]$ ll
总用量 12
drwx------. 3 atguigu atguigu 4096 12月 11 08:03 data
drwxrwxr-x. 3 atguigu atguigu 4096 12月 11 08:03 name1
drwxrwxr-x. 3 atguigu atguigu 4096 12月 11 08:03 name2
3.11 DateNode(面试开发重点)
####3.11.1 DataNode工作机制 DataNode工作机制,如图所示
1)一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳
2)DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
3)心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
4)集群运行中可以安全加入和退出一些机器。
3.11.2 数据完整性
思考:如果电脑磁盘里面存储的数据是控制高铁信号灯的红灯信号(1)和绿灯信号(0),但是存储该数据的磁盘坏了,一直显示是绿灯,是否很危险?同理DataNode节点上的数据损坏了,却没有发现,是否也很危险,那么如何解决呢?
如下是DataNode节点保证数据完整性的方法。
1)当DataNode读取Block的时候,它会计算CheckSum。
2)如果计算后的CheckSum,与Block创建时值不一样,说明Block已经损坏。
3)Client读取其他DataNode上的Block。
4)常见的校验算法 crc(32) md5(128) sha1(160)
5)DataNode在其文件创建后周期验证CheckSum,如图6-2所示。
3.11.3 掉线时限参数设置
需要注意的是hdfs-site.xml配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒
<property>
<name>dfs.namenode.heartbeat.recheck-interval</name>
<value>300000</value>
</property>
<property>
<name>dfs.heartbeat.interval</name>
<value>3</value>
</property>
3.11.4 服役新节点
需求:
随着公司业务的增长,数据量越来越大,原有的数据节点的容量已经不能满足存储数据的需求,需要在原有集群基础上动态添加新的数据节点
1 . 环境准备
(1)在hadoop104主机上再克隆一台Hadoop105的机器,修改主机名和网络ip地址,然后删除data目录和logs目录,
2 .服役新节点的具体步骤
(1)直接启动DataNode,即可关联到集群
[atguigu@hadoop105 hadoop-3.1.3]$ hdfs --daemon start datanode
[atguigu@hadoop105 hadoop-3.1.3]$yarn -–daemon start nodemanager
(2)如果数据不均衡,可以用命令实现集群的再平衡
[atguigu@hadoop102 hadoop-3.1.3]$ sbin/start-balancer.sh
3.11.5 退役旧数据节点
3.11.5.1 添加白名单和黑名单
添加到白名单的主机节点,都允许访问NameNode,不在白名单的主机节点,都会被直接退出。
添加到黑名单的主机节点,不允许访问NameNode,会在数据迁移后退出。
实际情况下,白名单用于确定允许访问NameNode的DataNode节点,内容配置一般与workers文件内容一致。 黑名单用于在集群运行过程中退役DataNode节点。
配置白名单和黑名单的具体步骤如下:
(1)再NameNode的/opt/module/hadoop-3.1.3/etc/hadoop目录下分别创建whitelist和blacklist文件
[atguigu@hadoop102 hadoop]$ pwd
/opt/module/hadoop-3.1.3/etc/hadoop
[atguigu@hadoop102 hadoop]$ touch whitelist
[atguigu@hadoop102 hadoop]$ touch blacklist
3.11.5.2 黑名单退役
(1)准备使用黑名单退役105
编辑blacklist文件,添加105
[atguigu@hadoop102 hadoop] vim blacklist
hadoop105
(2)刷新NameNode
[atguigu@hadoop102 hadoop] hdfs dfsadmin -refreshNodes
(3) 在web端查看DN状态,105正在退役中…进行数据的迁移
(4)如果105也启动的NodeManager,也可以刷新yarn状态。【可选查看]
[atguigu@hadoop102 hadoop-3.1.3]$ yarn rmadmin -refreshNodes
3.11.5.3 白名单退役【不推荐】(会导致数据的丢失,直接断开节点)
白名单退役会直接将节点抛弃,没有迁移数据的过程,会造成数据丢失
(1)删除blacklist中的内容,恢复102 103 104 105 正常工作
(2)修改whitelist,将105删除,保留102,103,104
[atguigu@hadoop102 hadoop]$ vim whitelist
hadoop102
hadoop103
hadoop104
(3)刷新NameNode
[atguigu@hadoop102 hadoop-3.1.3]$ hdfs dfsadmin -refreshNodes
3.11.6 Datanode多目录配置
1 .DataNode也可以配置成多个目录,每个目录存储的数据不一样,即:数据不是副本
2 .具体配置如下
(1)在hdfs-isite.xml中修改如下内容
<property>
<name>dfs.datanode.data.dir</name>
<value>file:///${hadoop.tmp.dir}/data1,file:///${hadoop.tmp.dir}/data2</value>
</property>
分发到全部集群
(2)停止集群,删除data和logs中所有数据
xcall rm -rvf data logs
(3)格式化集群并启动
[atguigu@hadoop102 hadoop-3.1.3]$ bin/hdfs namenode –format
[atguigu@hadoop102 hadoop-3.1.3]$ sbin/start-dfs.sh
(4)查看结果
[atguigu@hadoop102 dfs]$ ll
总用量 12
drwx------. 3 atguigu atguigu 4096 4月 4 14:22 data1
drwx------. 3 atguigu atguigu 4096 4月 4 14:22 data2
drwxrwxr-x. 3 atguigu atguigu 4096 12月 11 08:03 name1
drwxrwxr-x. 3 atguigu atguigu 4096 12月 11 08:03 name2
3.12 小文件存档
1 HDFS存储小文件弊端
每个文件均按块存储,每个块的元数据存储在NameNode的内存中,因此HDFS存储小文件会非常低效,因为大量的小文件会耗尽NameNode中的大部分内存,但注意,存储小文件所需要的磁盘容量和数据块的大小无关,例如,一个1MB的文件设置为128MB的块存储,实际使用的是1MB的磁盘空间,而不是128MB
2 解决存储小文件的办法之一
HDFS存档文件或HAR文件,是一个更高效的文件存档工具,它将文件存入HDFS块,在减少NameNode内存使用的同时,允许对文件进行透明的访问,具体说来,HDFS存档文件对内还是一个一个独立文件,对NameNode而言却是一个整体,减少了NameNode的内存
3 案例实操
(1)需要启动YARN进程
[atguigu@hadoop102 hadoop-3.1.3]$ start-yarn.sh
(2)归档文件
把/user/atguigu/input目录里面的所有文件归档成一个叫input.har的归档文件,并把归档后文件存储到/user/atguigu/output路径下
[atguigu@hadoop102 hadoop-3.1.3]$ bin/hadoop archive -archiveName input.har –p /user/atguigu/input /user/atguigu/output
(3)查看归档
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -lsr /user/atguigu/output/input.har
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -lsr har:///user/atguigu/output/input.har
(4)解归档文件
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -cp har:/// user/atguigu/output/input.har/* /user/atguigu
3.13 设置纠删码策略
纠删码策略是与具体的路径(path)相关联的。也就是说,如果我们要使用纠删码,则要给一个具体的路径设置纠删码策略,后续,所有往此目录下存储的文件,都会执行此策略。默认只开启RS-6-3-1024k策略的支持,如要使用别的策略需要先启用
1)纠删码操作相关的命令
[atguigu@hadoop202 hadoop-3.1.3]$ hdfs ec
Usage: bin/hdfs ec [COMMAND]
[-listPolicies]
[-addPolicies -policyFile <file>]
[-getPolicy -path <path>]
[-removePolicy -policy <policy>]
[-setPolicy -path <path> [-policy <policy>] [-replicate]]
[-unsetPolicy -path <path>]
[-listCodecs]
[-enablePolicy -policy <policy>]
[-disablePolicy -policy <policy>]
[-help <command-name>].
- 开启对RS-3-2-1024k策略的支持
为啥不用默认的RS-6-3-1024k策略呢,理论情况下,该策略需要9台DN的支持,而RS-3-2-1024k策略需要5台DN的支持,所以,你们懂的!!!
[atguigu@hadoop202 hadoop-3.1.3]$ hdfs ec -enablePolicy -policy RS-3-2-1024k
Erasure coding policy RS-3-2-1024k is enabled
- 在HDFS创建目录,并设置擦除策略
[atguigu@hadoop202 hadoop-3.1.3]$ hdfs dfs -mkdir /input
[atguigu@hadoop202 hadoop-3.1.3]$ hdfs ec -setPolicy -path /input -policy RS-3-2-1024k
[atguigu@hadoop202 hadoop-3.1.3]$ hdfs ec -getPolicy -path /input
4)上传文件,并查看文件编码后的存储情况
[atguigu@hadoop202 hadoop-3.1.3]$ hdfs dfs -put README.txt /input
[atguigu@hadoop202 hadoop-3.1.3]$ hdfs fsck /input/README.txt -files -blocks -locations
Connecting to namenode via http://hadoop202:9870/fsck?ugi=atguigu&files=1&blocks=1&locations=1&path=%2Finput%2FREADME.txt
FSCK started by atguigu (auth:SIMPLE) from /192.168.202.202 for path /input/README.txt at Fri Apr 10 22:03:27 CST 2020
/input/README.txt 1366 bytes, erasure-coded: policy=RS-3-2-1024k, 1 block(s): OK
0. BP-1572777420-192.168.202.202-1586521347125:blk_-9223372036854775760_1003 len=1366 Live_repl=3 [blk_-9223372036854775760:DatanodeInfoWithStorage[192.168.202.202:9866,DS-0d2d459f-331a-44b9-9e05-c09f971bfcfc,DISK], blk_-9223372036854775757:DatanodeInfoWithStorage[192.168.202.206:9866,DS-ca91c898-1e07-46de-a0eb-633fdab64bca,DISK], blk_-9223372036854775756:DatanodeInfoWithStorage[192.168.202.204:9866,DS-6ea8118c-fea4-4a69-bb96-7d5a1ffc22d1,DISK]]
第四章 MapReduce概述以及WordCount
4.1 MapReduce定义
MapReduce 是一个分布式运算程序的编程框架,使用户开发“基于Hadoop的数据分析应用”的核心框架
MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上
4.2 MapReduce优缺点
4.2.1 优点
1.MapReduce易于编程
它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行,也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的,就是因为这个特点使得MapReduce编程变得非常流行
2 .良好的扩展性
当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力
3 .高容错性
MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性,比如其中一台机器挂了,它可以把上面的计算任务转移到另一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的
4 .适合PB级以上海量数据的离线处理
可以实现上千台服务器集群并发工作,提供数据处理能力
4.2.2 缺点
1.不擅长实时计算
MapReduce无法像MySql一样,在毫秒或者秒级内返回结果
2.不擅长流式计算
流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化,这是因为MapReduce自身的设计特点决定了数据源必须是静态的
3.不擅长DAG(有向图)计算
多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个Map Reduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下
4.3 MapReduce核心思想
MapReduce核心编程思想
1)分布式的运算程序往往需要分成至少2个阶段。
2)第一个阶段的MapTask并发实例,完全并行运行,互不相干。
3)第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。
4)MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行。
总结:分析WordCount数据流走向深入理解MapReduce核心思想。
4.4 MapReduce进程
一个完整的MapReduce程序在分布式运行时有三类示例进程
1)MrAppMaster:负责整个程序的过程调度及状态协调
2)MapTask:负责Map阶段的整个数据处理流程
3)ReduceTask:负责Reduce阶段的整个数据处理流程
4.5 官方WordCount源码
通过javaanzhuang里面的java反编译打开
I:\1\框架\尚硅谷大数据技术之Hadoop\2.资料\01_jar包\hadoop-3.1.3\share\hadoop\mapreduce\hadoop-mapreduce-examples-3.1.3
里面的WordCount代码,发现wordOCount案例有Map类,Reduce类。且数据的类型时Hadoop自身封装的序列化类型
package org.apache.hadoop.examples;
import java.io.IOException;
import java.io.PrintStream;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Mapper.Context;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Reducer.Context;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class WordCount
{
public static void main(String[] args)
throws Exception
{
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length < 2) {
System.err.println("Usage: wordcount <in> [<in>...] <out>");
System.exit(2);
}
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
for (int i = 0; i < otherArgs.length - 1; i++) {
FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
}
FileOutputFormat.setOutputPath(job, new Path(otherArgs[(otherArgs.length - 1)]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable>
{
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context)
throws IOException, InterruptedException
{
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
this.result.set(sum);
context.write(key, this.result);
}
}
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>
{
private static final IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Mapper<Object, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException
{
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
this.word.set(itr.nextToken());
context.write(this.word, one);
}
}
}
}
4.6 常用数据序列化类型
表1-1 常用的数据类型对应的Hadoop数据序列化类型
Java类型 | Hadoop Writable类型 |
---|---|
Boolean | BooleanWritable |
Byte | ByteWritable |
Int | IntWritable |
Float | FloatWritable |
Long | LongWritable |
Double | DoubleWritable |
String | Text |
Map | MapWritable |
Array | ArrayWritable |
4.7 MapReduce编程规范
用户编写的程序分成三个部分:Mapper、reducer和Driver
1.Mapper阶段
(1)用户自定义的Mapper要继承自己的父类
(2)Mapper的输入数据时KV对的形式(KV的类型可自定义)
(3)Mapper中的业务逻辑写在map()方法中
(4)Mapper的输出数据是KV对的形式(KV的类型可自定义)
(5)map()方法(MapTask进程)对每一个<K,V>调用一次
2 .Reducer阶段
(1)用户自定义的Reducer要继承自己的父类
(2)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
(3)Reducer的业务逻辑写在reduce()方法中
(4)ReducerTask进程对每一组相同k的<k,v>组调用一次reduce()方法
3. Driver阶段
相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象
4.8 WordCount案例实操
1 .需求
在给定的文本文件中统计输出每一个单词出现的总次数
2.需求分析
按照MapReduce编程规范,分别编写Mapper,Reducer,Driver,如图所示
3 . 环境准备
(1)创建maven工程
(2)在pom.xml文件中添加如下依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
</dependencies>
(3)在项目的src/main/resources目录下,新建一个文件,命名为“log4j.xml",在文件中填入
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true" name="XMLConfig">
<Appenders>
<!-- 类型名为Console,名称为必须属性 -->
<Appender type="Console" name="STDOUT">
<!-- 布局为PatternLayout的方式,
输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
<Layout type="PatternLayout"
pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
</Appender>
</Appenders>
<Loggers>
<!-- 可加性为false -->
<Logger name="test" level="info" additivity="false">
<AppenderRef ref="STDOUT" />
</Logger>
<!-- root loggerConfig设置 -->
<Root level="info">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
4 . 编写程序
(1)编写Mapper类
package com.atguigu.mr.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* 自定义Mapper:继承Mapper类,指定四个泛型,四个泛型表示两组kv对
* (KEYIN LongWritable表示从文件中读取数据的偏移量
* VALUEIN Text 表示从文件读取的一行数据)第一对kv
* (KEYOUT Text 表示输出文件的一个单词
* VALUEOUT IntWritable 表示单词出现的个数)第二对kv
*/
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
//设置输出的k,不能写死,下面赋值给它
Text outK = new Text();
//默认次数是1,可以写死
IntWritable outV = new IntWritable(1);
/**
*
* @param key 表示输入的k
* @param value 表示输入的v,就是文件中读取的一行内容
* @param context 负责调度Mapper运行
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//在文件中还是先用String类型,然后在转变成Text类型
//读取一行数据
String line = value.toString();
//用空格切分
String[] s = line.split(" ");
for (String word : s) {
//把单词存放到outK里面去
outK.set(word);
//写出
context.write(outK, outV);
}
}
}
(2)编写Reducer类
package com.atguigu.mr.wordcount;
import com.sun.xml.internal.ws.util.xml.ContentHandlerToXMLStreamWriter;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.Iterator;
/**
* KEYIN Text 表示从map端输入的key,一个单词
* VALUEIN IntWritable表示从map端输入的value,值为1
* KEYOUT Text 表示输出的一个单词
* VALUEOUT IntWriteable 表示这个单词出现的总个数
*/
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable>{
//设置输出的key
Text outK = new Text();
IntWritable outV = new IntWritable();
/**
*
* @param key 表示输入的k,表示一个单词
* @param values 表示封装了当前key对应的所有的value封装成一个迭代器对象
* @param context 用来操作reducer运行
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//用来进行统计个数
int sum = 0;
//用来进行迭代
//如果有下一个就去出,加入到sum,进行迭代
for (IntWritable value : values) {
//先把IntWritable.get(),得到int类型的数据,直接操作不了
sum += value.get();
}
//加入到outV
outV.set(sum);
outK.set(key);
//写出
context.write(outK, outV);
}
}
(3)编写Driver
package com.atguigu.mr.wordcount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WordCountDriver {
public static void main(String[] args) throws Exception{
//1.创建一个job对象
Job job = Job.getInstance(new Configuration());
//2.关联jar
job.setJarByClass(WordCountDriver.class);
//3.关联Mapper和Reducer类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//4.设置Mapper的输出key和value的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//5.设置最终输出的key和value的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//6.设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path("I:\\xiao.txt"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output"));
//7.提交job
job.waitForCompletion(true);
}
}
(4) Mapper类和Reducer类需要调用的方法
Mapper类:
setup()方法: Called once at the beginning of the task . 在每个MapTask中只会在Task开始运行时被调用一次.
map()方法: Called once for each key/value pair in the input split. Most applications
should override this, but the default is the identity function.
一个切片中输入的每一个kv对会调用一次map方法。
cleanup()方法: Called once at the end of the task. 在每个MapTask中只会在Task结束前被调用一次
run()方法: 负责控制Mapper的执行过程.
Reducer类:
setup()方法: Called once at the start of the task. 在每个ReduceTask开始运行时会调用一次该方法.
reduce()方法: This method is called once for each key. Most applications will define
their reduce class by overriding this method. The default implementation
is an identity function.
为同一个key(map端输出的数据可能有相同key的多个kv对,称之为一组kv)执行一次reduce方法。
cleanup()方法: Called once at the end of the task. 在每个ReduceTask结束时会调用一次该方法
run()方法: 负责控制Reducer的执行过程。
4.9 WordCount配置到集群
4.9.1 配置到linux进行集群操作
linux里面启动WordCount本地模式还是完全分布式运行模式取决于配置文件
准备阶段
(1)用maven将工程打包,生成jar包在target里面,把jar包复制到桌面上,改名wc.jar,用rz传到/opt/module/Hadoop里面
(2)首先创建文件夹/input在hdfs,把文件xiao.txt放入/input
hadoop fs -put xiao.txt /input
(3)idea中的Driver文件需要改成,让public static void main(String[] args) 里面的args来传递参数
//6.设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
(4)driver类的输出路径要使用全类名,同时输出路径不能重复
hadoop jar wc.jar com.atguigu.mr.wordcount.WordCountDriver /input/xiao.txt /output1
4.9.2 在Windows上向集群提交任务
(1)添加必要配置信息
public class WordcountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取配置信息以及封装任务
Configuration configuration = new Configuration();
//设置HDFS NameNode的地址
configuration.set("fs.defaultFS", "hdfs://hadoop102:8020");
// 指定MapReduce运行在Yarn上
configuration.set("mapreduce.framework.name","yarn");
// 指定mapreduce可以在远程集群运行
configuration.set("mapreduce.app-submission.cross-platform","true");
//指定Yarn resourcemanager的位置
configuration.set("yarn.resourcemanager.hostname","hadoop103");
Job job = Job.getInstance(configuration);
// 2 设置jar加载路径
job.setJarByClass(WordcountDriver.class);
// 3 设置map和reduce类
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
// 4 设置map输出
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 提交
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
(2)先进行打包,并将jar包设置到Driver中,集群中运行需要指定jar包
package com.atguigu.mr.wordcount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WordCountDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//设置job要访问的默认文件系统
configuration.set("fs.defaultFS", "hdfs://hadoop102:8020");
//设置job提交到哪里运行
configuration.set("mapreduce.framework.name","yarn");
//windows跨平台操作需要设置可远程操作
configuration.set("mapreduce.app-submission.cross-platform","true");
//配置yarn的在哪个机器上运行
configuration.set("yarn.resourcemanager.hostname","hadoop103");
Job job = Job.getInstance(configuration);
// 2 设置jar加载路径
job.setJar("C:\\Users\\10185\\IdeaProjects\\day01\\target\\day01-1.0-SNAPSHOT.jar");
//2.关联jar
//job.setJarByClass(WordCountDriver.class);
//3.关联Mapper和Reducer类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//4.设置Mapper的输出key和value的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//5.设置最终输出的key和value的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//6.设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//7.提交job
job.waitForCompletion(true);
}
}
(3)编辑任务配置
-DHADOOP_USER_NAME=atguigu
hdfs://hadoop102:8020/input/xiao.txt hdfs://hadoop102:8020/output6
第五章 Hadoop序列化
5.1序列化的概述
序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便存储到磁盘(持久化)和网络传输
反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象
5.1.1 为什么要序列化
一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机,然而序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机
5.1.2 为什么不用Java的序列化
Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附加很多格外的信息(各种校验信息,Header,继承体系等),不便于在网络上高效的传输,所以,Hadoop自己开发了一套序列化机制(Writable)
Hadoop序列化特点
(1)紧凑:高效使用存储空间
(2)快速:读写数据的格外开销小
(3)可扩展:随着通信协议的升级可以升级
(4)互操作:支持多语言的交互
5.2 自定义bean对象实现序列化接口(Writable)
在企业开发中往往常用的基本序列化类型不能满足所有需求,比如在Hadoop框架内部传递一个bean对象,那么该对象就需要实现序列化接口。
具体实现bean对象序列化步骤如下7步。
(1)必须实现writable接口
(2)反序列化时,需要使用无参构造函数,所以必须由无参构造
(3)重写序列化方法
(4)重写反序列化方法(注意反序列化的顺序必须和序列化进来的一致)
(5)要想把结果显示在文件中,需要重写toString(),可用"\t"分开,方便后续用
(6)如果需要将自定义的bean放在key中传输,还需要实现Comparable接口,因为MapReduce框中的Shuffle过程要求对key必须能排序。详见后面排序案例。
5.3 序列化案例实操
1 .需求
统计每一个手机号耗费的总上行流量、下行流量、总流量
(1)输入数据
1 13736230513 192.196.100.1 www.atguigu.com 2481 24681 200
2 13846544121 192.196.100.2 264 0 200
3 13956435636 192.196.100.3 132 1512 200
4 13966251146 192.168.100.1 240 0 404
5 18271575951 192.168.100.2 www.atguigu.com 1527 2106 200
6 84188413 192.168.100.3 www.atguigu.com 4116 1432 200
7 13590439668 192.168.100.4 1116 954 200
8 15910133277 192.168.100.5 www.hao123.com 3156 2936 200
9 13729199489 192.168.100.6 240 0 200
10 13630577991 192.168.100.7 www.shouhu.com 6960 690 200
11 15043685818 192.168.100.8 www.baidu.com 3659 3538 200
12 15959002129 192.168.100.9 www.atguigu.com 1938 180 500
13 13560439638 192.168.100.10 918 4938 200
14 13470253144 192.168.100.11 180 180 200
15 13682846555 192.168.100.12 www.qq.com 1938 2910 200
16 13992314666 192.168.100.13 www.gaga.com 3008 3720 200
17 13509468723 192.168.100.14 www.qinghua.com 7335 110349 404
18 18390173782 192.168.100.15 www.sogou.com 9531 2412 200
19 13975057813 192.168.100.16 www.baidu.com 11058 48243 200
20 13768778790 192.168.100.17 120 120 200
21 13568436656 192.168.100.18 www.alibaba.com 2481 24681 200
22 13568436656 192.168.100.19 1116 954 200
(2)输入数据格式:
7 13560436666 120.196.100.99 1116 954 200 id 手机号码 网络ip 上行流量 下行流量 网络状态码
(3)期望输出数据格式
13560436666 1116 954 2070 手机号码 上行流量 下行流量 总流量
2 .需求分析
3 .编写MapReducer程序
(1)编写流量统计的Bean对象
package com.atguigu.mr.flow;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class FlowBean implements Writable {
private Long upFlow;
private Long downFlow;
private Long sumFlow;
public FlowBean() {
}
public Long getUpFlow() {
return upFlow;
}
public void setUpFlow(Long upFlow) {
this.upFlow = upFlow;
}
public void setSumFlow() {
sumFlow=upFlow+downFlow;
}
public Long getDownFlow() {
return downFlow;
}
public void setDownFlow(Long downFlow) {
this.downFlow = downFlow;
}
public Long getSumFlow() {
return sumFlow;
}
public void setSumFlow(Long sumFlow) {
this.sumFlow = sumFlow;
}
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
public void readFields(DataInput dataInput) throws IOException {
upFlow=dataInput.readLong();
downFlow=dataInput.readLong();
sumFlow=dataInput.readLong();
}
@Override
public String toString() {
return upFlow+"\t"+downFlow+"\t"+sumFlow;
}
}
(2)编写Mapper类
package com.atguigu.mr.flow;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowMapper extends Mapper<LongWritable, Text,Text,FlowBean> {
FlowBean outV = new FlowBean();
Text outK = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//读取一行
String line = value.toString();
//进行切分
String[] split = line.split("\t");
//得到包含电话号码的和上行流量和下行流量的值
outK.set(split[1]);
//由于中间有空的值,因此需要从一行字符串的后面取值
outV.setUpFlow(Long.parseLong(split[split.length-3].trim()));
outV.setDownFlow(Long.parseLong(split[split.length-2].trim()));
outV.setSumFlow();
//把数据写出去
context.write(outK, outV);
}
}
(3)编写Reducer类
package com.atguigu.mr.flow;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.yarn.webapp.hamlet2.HamletSpec;
import java.io.IOException;
public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
Text outK = new Text();
FlowBean outV = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
Long sumUpFlow = 0L;
Long sumDownFlow = 0L;
for (FlowBean value : values) {
sumUpFlow=sumUpFlow+value.getUpFlow();
sumDownFlow=sumDownFlow+value.getDownFlow();
}
outV.setUpFlow(sumUpFlow);
outV.setDownFlow(sumDownFlow);
outV.setSumFlow(sumUpFlow+sumDownFlow);
outK=key;
context.write(outK, outV);
}
}
(4)编写Driver类
package com.atguigu.mr.flow;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class FlowDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(FlowDriver.class);
//定义mapper和reducer的类
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
//定义最后输出的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
//定义输出路径
FileInputFormat.setInputPaths(job, new Path("I:\\test.txt"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output1"));
//设置提交
job.waitForCompletion(true);
}
}
第六章 MapReduce框架原理
MapReduce的大致流程:
Map --> shuffle -->reduce
MapReduce的详细流程: map –> sort –> copy –> sort –> reduce
InputFormat: 负责Map端数据的输入 重要的方法: getSplits(): 生成切片信息。 createRecordReader(): 负责输入数据的读取处理 子抽象类: FileInputFormat getSplits(): 做了具体的实现. Hadoop默认的切片规则。 具体实现类: TextInputFormat : Hadoop默认使用的InputFormat 默认使用FileInputFormat中的getSplits方法来生成切片信息。 使用LineRecordReader来读取数据,就是一行一行读取. CombineFileInputFormat NLineInputFormat KeyValueTextInputFormat
6.1 InputFormat数据输入
6.1.1 切片与MapTask并行度决定机制
1.问题引出
MapTask的并行度决定Map阶段的任务处理并发度,进而影响到整个Job的处理速度
思考:1G的数据,启动8个MapTask,可以提高集群的并发处理能力。那么1K的数据,也启动8个MapTask,会提高集群性能吗?MapTask并行任务是否越多越好呢?哪些因素影响了MapTask并行度?
2.MapTask并行度决定机制
数据块:Block是HDFS物理上把数据分成一块一块
**数据切片:**数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。(实际生成的每个切片就是记录以下读文件时从哪里读到哪里)(读取数据的范围)
图片
6.1.2 Job提交流程源码
waitForCompletion()
submit();
// 1建立连接
connect();
// 1)创建提交Job的代理
new Cluster(getConfiguration());
// (1)判断是本地yarn还是远程
initialize(jobTrackAddr, conf);
// 2 提交job
submitter.submitJobInternal(Job.this, cluster)
// 1)创建给集群提交数据的Stag路径
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
// 2)获取jobid ,并创建Job路径
JobID jobId = submitClient.getNewJobID();
// 3)拷贝jar包到集群
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
// 4)计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
// 5)向Stag路径写XML配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 6)提交Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());
注意:如果在yarn配置,stag路径里面除了xml配置文件,split信息,还有一个job.jar包
6.1.3 FileInputFormat切片源码解析
切片的源码: FileInputFormat类中:
public List<InputSplit> getSplits(JobContext job) throws IOException {
StopWatch sw = new StopWatch().start();
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); // 1
// minSize ==> mapreduce.input.fileinputformat.split.minsize
long maxSize = getMaxSplitSize(job); // Long.MAX_VALUE
// maxSize ==> mapreduce.input.fileinputformat.split.maxsize
// generate splits
List<InputSplit> splits = new ArrayList<InputSplit>();
List<FileStatus> files = listStatus(job);
boolean ignoreDirs = !getInputDirRecursive(job)
&& job.getConfiguration().getBoolean(INPUT_DIR_NONRECURSIVE_IGNORE_SUBDIRS, false);
// 循环每个文件,为每个文件单独生成切片.
for (FileStatus file: files) {
if (ignoreDirs && file.isDirectory()) {
continue;
}
Path path = file.getPath();
long length = file.getLen();
if (length != 0) {
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {
FileSystem fs = path.getFileSystem(job.getConfiguration());
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
if (isSplitable(job, path)) {
long blockSize = file.getBlockSize();
//计算切片的大小
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
// return Math.max(minSize, Math.min(maxSize, blockSize));
long bytesRemaining = length;
// 当前文件剩余的大小 除以 切片大小 >1.1 ,继续切片,否则,剩余的大小生成一个切片。
// 避免数据倾斜问题
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
} else { // not splitable
if (LOG.isDebugEnabled()) {
// Log only if the file is big enough to be splitted
if (length > Math.min(file.getBlockSize(), minSize)) {
LOG.debug("File is not splittable so no parallelization "
+ "is possible: " + file.getPath());
}
}
splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
blkLocations[0].getCachedHosts()));
}
} else {
//Create empty hosts array for zero length files
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
// Save the number of input files for metrics/loadgen
job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size()
+ ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
}
// 返回切片信息的集合
return splits;
}
提取到的信息:
1 .本地的块大小默认是32M
2 .long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); // 1 // minSize ==> mapreduce.input.fileinputformat.split.minsize 通过该参数改变minSize long maxSize = getMaxSplitSize(job); // Long.MAX_VALUE // maxSize ==> mapreduce.input.fileinputformat.split.maxsize 通过该参数改变maxSize
3 .long splitSize = computeSplitSize(blockSize, minSize, maxSize); // return Math.max(minSize, Math.min(maxSize, blockSize)); 计算切片大小
4 .一个切片信息: file:/D:/input/inputflow/phone_data.txt:0+1178
意思就是读取/D:/input/inputflow/phone_data.txt的 0~1178 范围的数据.
(1)程序先找到你数据存储的目录
(2)开始遍历处理(规划切片)目录下的每一个文件
(3)遍历第一个文件ss.txt
a)获取文件大小fs.sizeOf(ss.txt)
b)计算切片大小
computeSplitSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M
c)默认情况下,切片大小=blocksize
d)开始切,形成第1个切片:ss.txt-0:128M第2个切片ss.txt-128M:256M第3个切片ss.txt-256M:300M
(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片)
e)将切片信息写到一个切片规划文件中
f)整个切片的核心过程在getSplit()方法中完成 g)InputSplit只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等。
(4)提交切片规划文件到YARN上,YARN上的MrAppMaster就可以根据切片规划文件计算开启MapTask个数
6.1.4 FileInputFormat切片机制
1 .切片机制
(1)简单的按照文件的内容长度进行切片
(2)切片大小,默认等于Block大小
(3) 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
2 .案例分析 (2)经过FileInputFormat的切片机制运算后,形成的切片信息如下
(1)输入数据有两个文件 file1.txt.split1 – 0~128
file1.txt 320M file1.txt.split2– 128~256
File2.txt 10M file1.txt.split3– 256~320
file2.txt.split1– 0~10M
6.1.5 FileInputFormat切片大小的参数配置
(1)源码中计算切片大小的公式
Math.max(minSize,Math.min(maxSize,blockSize));
mapreduce.input.fileinputformat.split.minsize=1 默认值为1
mapreduce.input.fileinputformat.split.maxsize=LongMaxValue默认值Long.MaxValue
因此,默认情况下,切片带下=blocksize
(2)切片大小设置
maxsize(切片最大值):参数如果调的比blockSize小,则会让切片变小,而且就等于配置的这个参数的值
minsize(切片最小值):参数调的比blockSize大,则可以让切片变得比blockSize还大
(3)获取切片信息API
//获取切片的文件名称
String name = inputSplit.getPath().getName();
//根据文件类型获取切片信息
FileSplit inputSplit = (FIleSplit) context.getInputSplit();
6.1.6 FileInputFormat实现类
思考:在运行MapReduce程序时,输入的文件格式包括:基于行的日志文件、二进制格式文件、数据库表等。那么,针对不同的数据类型,MapReduce是如何读取这些数据的呢
FileInputFormat常见的接口实现类包括:TextInputFormat、KeyValueTextinputFormat、NLineInputFormat、CombineTextInputFormat和自定义InputFormat等
1 TextInputFormat
TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。键是存储该行在整个文件起始字节偏移量,LongWritable类型。值是这行内容,不包括任何行终止符(换行符和回车符),Text类型
2 KeyValueTextInputFormat
每一行均为一条记录,被分隔符分割为key,value,可以通过在驱动类中设置conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR,"\t");来设定分割符。默认分割符是tab(\t).
以下是一个示例,输入是一个包含4条记录的分片。其中—>表示一个(水平方向的)制表符
line1 ——>Rich learning form
line2 ——>Intelligent learning engine
line3 ——>Learning more convenient
line4 ——>From the real demand for more close to the enterprise
每条记录表示为以下键/值对:
(line1,Rich learning form)
(line2,Intelligent learning engine)
(line3,Learning more convenient)
(line4,From the real demand for more close to the enterprise)
此时的键是每行排在制表符之前的Text序列
3 NlineInputFormat
如果使用NlineInputFormat,代表每个map进程处理的inputSplit不再按Block块去划分,而是按NlineinputFormat指定的行数N来划分。即输入文件的总行数/N=切片数,如果不整除,切片数=商+1.
以下是一个示例,仍然以下面的4行输入为例
Rich learning form
Intelligent learning engine
Learning more convenient
From the real demand for more close to the enterprise
例如,如果N是2,则每个输入分片包含两行。开启2个MapTask
(0,Rich learning form)
(19,Intelligent learning engine)
另一个mapper则收到后两行
(47,Learning more convenient)
(72,From the real demand for more close to the enterprise)
这里的键和值与TextInputFormat生成的一样
6.1.7 CombineTextInputFormat切片机制
1 .应用场景
CombineTextInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个MapTask处理。
2 .虚拟存储切片最大值设置
CombineTextInputFormat.setMaxInputSplitSize(job,4194304)//4M
注意:虚拟存储切片最大值设置最好根据实际的小文件情况来设置具体的值
3 .切片机制
(1)虚拟存储过程:
将输入目录下所有文件大小,
将输入目录下所有文件大小,依次和设置的setMaxInputSplitSize值比较,如果不大于设置的最大值,逻辑上划分一个块。如果输入文件大于设置的最大值且大于两倍,那么以最大值切割一块;当剩余数据大小超过设置的最大值且不大于最大值2倍,此时将文件均分成2个虚拟存储块(防止出现太小切片)。
例如setMaxInputSplitSize值为4M,输入文件大小为8.02M,则先逻辑上分成一个4M。剩余的大小为4.02M,如果按照4M逻辑划分,就会出现0.02M的小的虚拟存储文件,所以将剩余的4.02M文件切分成(2.01M和2.01M)两个文件。
(2)切片过程:
(a)判断虚拟存储的文件大小是否大于setMaxInputSplitSize值,大于等于则单独形成一个切片。
(b)如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。
(c)测试举例:有4个小文件大小分别为1.7M、5.1M、3.4M以及6.8M这四个小文件,则虚拟存储之后形成6个文件块,大小分别为:
1.7M,(2.55M、2.55M),3.4M以及(3.4M、3.4M)
最终会形成3个切片,大小分别为:
(1.7+2.55)M,(2.55+3.4)M,(3.4+3.4)M
4 案例实操
需求
将输入的大量小文件合并成一个切片统一处理
(1)输入数据
准备四个小文件
(2)期望
期望一个切片处理4个文件
2 .实现过程
(1)不做任何处理,运行WordCount案例程序,观察切片个数为4
number of splits : 4
(2)在WordcountDriver中增加如下代码,运行程序,并观察运行的切片个数为3
(a)驱动类中添加代码如下:
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
(b)运行结果为3个切片
(3)在wordcountDirver中增加如下代码,运行程序,并观察运行的切片个数为1
(a)驱动中添加代码如下
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置20m
CombineTextInputFormat.setMaxInputSplitSize(job, 20971520);
(b)运行结果为1个切片 number of splits:1
6.1.8 KeyValueTextInputFormat使用案例(适用于求出每一行第一个数据出现的个数等)
1 需求
统计输入文件中每一行的第一个单词相同的行数
(1)输入数据
banzhang ni hao
xihuan hadoop banzhang
banzhang ni hao
xihuan hadoop banzhang
(2)期望结果数据
banzhang 2
xihuan 2
2 .需求分析
Mapper类
package com.atguigu.mr.mv;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class MvMapper extends Mapper<Text, Text,Text, IntWritable> {
Text k = new Text();
IntWritable v = new IntWritable(1);
@Override
protected void map(Text key, Text value, Context context) throws IOException, InterruptedException {
k.set(key);
context.write(k,v);
}
}
Reducer类
package com.atguigu.mr.mv;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class MvReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
v.set(sum);
context.write(key,v);
}
}
Driver类
package com.atguigu.mr.mv;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.KeyValueLineRecordReader;
import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class MvDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration configuration = new Configuration();
configuration.set(KeyValueLineRecordReader.KEY_VALUE_SEPARATOR," ");
Job job = Job.getInstance(configuration);
job.setJarByClass(MvDriver.class);
job.setMapperClass(MvMapper.class);
job.setReducerClass(MvReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input1"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output3"));
job.setInputFormatClass(KeyValueTextInputFormat.class);
job.waitForCompletion(true);
}
}
6.1.9 NlineInputFormat使用案例(适用于一行里面数据特别少的情况)
1 需求
对每个单词进行个数统计,要求根据每个输入文件的行数来规定输出多少个切片。此案例要求每三行放入一个切片中
(1)输入数据
banzhang ni hao
xihuan hadoop banzhang
banzhang ni hao
xihuan hadoop banzhang
banzhang ni hao
xihuan hadoop banzhang
banzhang ni hao
xihuan hadoop banzhang
banzhang ni hao
xihuan hadoop banzhang banzhang ni hao
xihuan hadoop banzhang
(2)期望输出数据
Number of splits:4
Driver类
package com.atguigu.mapreduce.nline;
import java.io.IOException;
import java.net.URISyntaxException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.NLineInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class NLineDriver {
public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/input/inputword", "e:/output1" };
// 1 获取job对象
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 7设置每个切片InputSplit中划分三条记录
NLineInputFormat.setNumLinesPerSplit(job, 3);
// 8使用NLineInputFormat处理记录数
job.setInputFormatClass(NLineInputFormat.class);
// 2设置jar包位置,关联mapper和reducer
job.setJarByClass(NLineDriver.class);
job.setMapperClass(NLineMapper.class);
job.setReducerClass(NLineReducer.class);
// 3设置map输出kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
// 4设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
// 5设置输入输出数据路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 6提交job
job.waitForCompletion(true);
}
}
6.1.10 自定义inputFormat
无论HDFS还是MapReduce,在处理小文件时效率都非常低,但又难免面临处理大量小文件的场景,此时,就需要有相应解决方案。可以自定义InputFormat实现小文件的合并。
1.需求
将多个小文件合并成一个SequenceFile文件(SequenceFile文件是Hadoop用来存储二进制形式的key-value对的文件格式),SequenceFile里面存储着多个文件,存储的形式为文件路径+名称为key,文件内容为value。
2 . 需求分析
自定义一个类继承FileInputFormat
(1)重写isSplitable()方法,返回false不可切割
(2)重写createRecordReader(),创建自定义的RecordReader对象,并初始化
改写RecordReader,实现一次读取一个完整文件封装为KV
(1)采用IO流一次读取一个文件输出到value中,因为设置了不可切分,最终把所有文件都封装到了value中
(2)获取文件路径信息+名称,并设置key
设置Driver
(1) 设置输入的inputFormat
MyInputFormat类
package com.atguigu.mr.myinputFormat;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.ByteWritable;
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;
public class MyInputFormat extends FileInputFormat<Text, BytesWritable> {
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
return new MyRecordReader();
}
}
MyRecordReader类
package com.atguigu.mr.myinputFormat;
import java.io.IOException;
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.NullWritable;
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.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyRecordReader extends RecordReader<Text, BytesWritable> {
FileSplit fileSplit;
Configuration configuration;
boolean isRead = false;
Text k = new Text();
BytesWritable v = new BytesWritable();
FileInputStream fileInputStream;
//进行文件的初始化,把文件的切片信息文件那出来,把配置文件拿出来,(由于是遍历所有文件执行这个方法)可以通过当前文件的路径拿出来
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
//把文件转型到FileSplit,可以拿出路径
fileSplit = (FileSplit)split;
//把文件的路径封装称为key
k.set(fileSplit.getPath().toString());
//获取配置文件
configuration = context.getConfiguration();
}
//相当于hasNext,由于这里面只有一个切片,因此只执行一次,执行的第一次把文件读取到bytes数组,把文件放入输出v
public boolean nextKeyValue() throws IOException, InterruptedException {
if (!isRead) {
Path path = fileSplit.getPath();
//得到当前路径的文件系统,以便进行数据传输
FileSystem fileSystem = path.getFileSystem(configuration);
//打开当前文件
FSDataInputStream open = fileSystem.open(path);
//新建立一个新数组,(来存放数据)
byte[] bytes = new byte[(int) fileSplit.getLength()];
//读取文件的数据
open.read(bytes);
v.set(bytes, 0, bytes.length);
isRead = true;
return true;
}
return false;
}
//得到当前的key,把这个key传入到mapper
public Text getCurrentKey() throws IOException, InterruptedException {
return k;
}
//得到当前的value,把这个value传入到Reducer
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return v;
}
//得到当前的进度,只有0和1
public float getProgress() throws IOException, InterruptedException {
return 0;
}
//关闭资源
public void close() throws IOException {
IOUtils.closeStream(fileInputStream);
}
}
不用写mapper和reducer,因为不用改变,都用默认的
MyDriver类
package com.atguigu.mr.myinputFormat;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
public class MyDriver {
public static void main(String[] args) throws Exception{
Job job = Job.getInstance(new Configuration());
job.setJarByClass(MyDriver.class);
// job.setMapperClass(MyRecordMapper.class);
job.setMapOutputValueClass(BytesWritable.class);
job.setMapOutputKeyClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
//设置输入的inputFormat
job.setInputFormatClass(MyInputFormat.class);
//设置输出的OutFormat
//因为默认的是文本输出,因此要改变输出的类型为二进制类型
job.setOutputFormatClass(SequenceFileOutputFormat.class);
//job.setMapperClass(MyRecordMapper.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input2"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output2"));
job.waitForCompletion(true);
}
}
6.2 Shuffle机制
6.2.1 Shuffle机制
Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle,如图所示
6.2.2 Partition分区
1 . 问题引出
要求将统计结果按照条件输出到不同文件中(分区)。比如:将统计结果按照手机归属地不同省份输出到不同文件中(分区)
2 .默认Partitioner分区
public class HashPartitioner<K,V> extends Partitioner<K,V>{
public int getPartition(K key, V value,int numReduceTask){
return(key.hashCode() & Integer.MAX_Value) % numReduceTasks;
}
}
}
默认分区是根据key的hashCode对ReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区
3 . 自定义Parttioner步骤
(1) 自定义类继承Partitioner,重写getPartition()方法
public class CustomPartitioner extends Partitioner<Text, FlowBean> {
@Override
public int getPartition(Text key, FlowBean value, int numPartitions) {
// 控制分区代码逻辑
… …
return partition;
}
}
(2)在Job驱动中,设置自定义Partitioner
job.setPartitionerClass(CustomPartitioner.class);
(3)自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask
job.setNumReduceTasks(5);
6.2.3 Partition分区的案例实操
1 .需求
将统计结果按照收集归属地不同省份输出到不同文件中(分区)
(1) 输入数据
1 13736230513 192.196.100.1 www.atguigu.com 2481 24681 200
2 13846544121 192.196.100.2 264 0 200
3 13956435636 192.196.100.3 132 1512 200
4 13966251146 192.168.100.1 240 0 404
5 18271575951 192.168.100.2 www.atguigu.com 1527 2106 200
6 84188413 192.168.100.3 www.atguigu.com 4116 1432 200
7 13590439668 192.168.100.4 1116 954 200
8 15910133277 192.168.100.5 www.hao123.com 3156 2936 200
9 13729199489 192.168.100.6 240 0 200
10 13630577991 192.168.100.7 www.shouhu.com 6960 690 200
11 15043685818 192.168.100.8 www.baidu.com 3659 3538 200
12 15959002129 192.168.100.9 www.atguigu.com 1938 180 500
13 13560439638 192.168.100.10 918 4938 200
14 13470253144 192.168.100.11 180 180 200
15 13682846555 192.168.100.12 www.qq.com 1938 2910 200
16 13992314666 192.168.100.13 www.gaga.com 3008 3720 200
17 13509468723 192.168.100.14 www.qinghua.com 7335 110349 404
18 18390173782 192.168.100.15 www.sogou.com 9531 2412 200
19 13975057813 192.168.100.16 www.baidu.com 11058 48243 200
20 13768778790 192.168.100.17 120 120 200
21 13568436656 192.168.100.18 www.alibaba.com 2481 24681 200
22 13568436656 192.168.100.19 1116 954 200
(2)期望输出数据
手机号136、137、138、139开头都分别放到一个独立的4个文件中,其他开头的放到一个文件中
(3)在案例上下行流量的基础上,增加一个分区类
package com.atguigu.mr.flow;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class MyPartition extends Partitioner<Text,FlowBean> {
public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
int partition = 4;
String s = text.toString();
if (s.startsWith("136")) {
partition = 0;
} else if (s.startsWith("137")) {
partition = 1;
} else if (s.startsWith("138")) {
partition = 2;
} else if (s.startsWith("139")) {
partition =3;
}
return partition;
}
}
(4)在FlowDriver上面增加
job.setPartitionerClass(MyPartition.class);
job.setNumReduceTasks(5);
(4)分区总结
(1)如果ReduceTask的数量>getPartition的结果数,则会产生几个空的输出文件part-r-000xx
(2)如果1<ReduceTask的数量<getPartition的结果数,则有一部分会无法安放,会Exception (3)如果ReduceTask=1,(默认情况下就是1),则不管MapTask端输出多少个分区文件,最终结果都交给ReduceTask,最终也就会产生一个结果文件part-r-00000; (4)分区号必须从零开始,逐一累加
6.2.3 WritableCompareable排序
排序是Reduce框架中最重要的操作之一
MapTask和ReduceTask均会对数据按照key进行排序,该操作属于hadoop的默认行为,任何应用程序中的数据均会被排序,而不管逻辑上是否需要
默认排序是按照字典顺序排序,且实现该排序的方法是快速排序
1 排序概述
对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲去中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序 对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。
2 排序的分类
(1) 部分排序
MapReduce根据输入记录的键对数据集排序,保证输出的每个文件内部有序
(2)全排序
最终输出结果只有一个文件,且文件内部有序,实现方式只设置一个ReduceTask,但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce所提供的并行架构
(3)辅助排序 :(GroupingComparator分组)
在Reduce端对key进行分组,应用于:在接收key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到一个reduce方法时,可以采用分组排序
(4)二次排序
在自定义排序中,如果compareTo中的判断条件为两个即为二次排序
6.2.4 WritableComparable案例实操
1 需求以及图解过程
根据案例2.3产生的结果再次对总流量进行排序
2 代码实现
(1)FlowBean
package com.atguigu.mr.writableComparable;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;
public class FlowBean implements WritableComparable<FlowBean> {
private Long upFlow;
private Long downFlow ;
private Long sumFlow ;
private String phoneNumber;
public Long getUpFlow() {
return upFlow;
}
public void setUpFlow(Long upFlow) {
this.upFlow = upFlow;
}
public Long getDownFlow() {
return downFlow;
}
public void setDownFlow(Long downFlow) {
this.downFlow = downFlow;
}
public Long getSumFlow() {
return sumFlow;
}
public void setSumFlow(Long sumFlow) {
this.sumFlow = sumFlow;
}
public void setSumFlow() {
this.sumFlow = this.upFlow + this.downFlow;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
public FlowBean(){}
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(phoneNumber);
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}
@Override
public void readFields(DataInput in) throws IOException {
phoneNumber = in.readUTF();
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong();
}
/**
* 比较规则: 按照总流量倒序
* @param o
* @return
*/
@Override
public int compareTo(FlowBean o) {
return -this.sumFlow.compareTo(o.getSumFlow());
}
}
(2)编写Mapper类
package com.atguigu.mr.writableComparable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowMapper extends Mapper<LongWritable, Text,FlowBean,FlowBean> {
private FlowBean outK = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//处理一行数据
// 1 13736230513 192.196.100.1 www.atguigu.com 2481 24681 200
String line = value.toString();
String[] splits = line.split("\t");
// 封装
outK.setPhoneNumber(splits[1]);
outK.setUpFlow(Long.parseLong(splits[splits.length-3].trim()));
outK.setDownFlow(Long.parseLong(splits[splits.length-2].trim()));
outK.setSumFlow();
//写出
System.out.println(outK.getPhoneNumber());
context.write(outK,outK);
}
}
(3)编写reducer类
package com.atguigu.mr.writableComparable;
import com.atguigu.mr.mycompare.ComBean;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<FlowBean,FlowBean, Text,FlowBean> {
Text k = new Text();
@Override
protected void reduce(FlowBean key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
for (FlowBean value : values) {
//默认这个compare相同的key归并在了一次,因此这些key都是同一个但是values有很多
k.set(value.getPhoneNumber());
context.write(k, value);
}
}
}
(4)driver类
package com.atguigu.mr.writableComparable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class FlowDriver1 {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1. 创建Job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2. 关联jar
job.setJarByClass(FlowDriver1.class);
//3. 关联mapper和reducer
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//4. 设置map输出的k 和 v的类型
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(FlowBean.class);
//5. 设置最终输出的k 和 v的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
//6. 设置输入和输出路径
FileInputFormat.setInputPaths(job,new Path("i:/input"));
FileOutputFormat.setOutputPath(job,new Path("i:/output3"));
/*job.setPartitionerClass(PhoneNumPartitioner.class);
job.setNumReduceTasks(5);*/
job.waitForCompletion(true);
}
}
6.2.5 WritableComparable进行分区并且排序
加入Paritioner类
package com.atguigu.mr.writableComparable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.yarn.webapp.hamlet2.HamletSpec;
public class PhoneNumPartitioner extends Partitioner<FlowBean, FlowBean> {
@Override
public int getPartition(FlowBean flowBean1, FlowBean flowBean2, int numPartitions) {
int partitioner ;
String phoneNum = flowBean1.getPhoneNumber();
if(phoneNum.startsWith("136")){
partitioner = 0;
}else if (phoneNum.startsWith("137")){
partitioner = 1;
}else if (phoneNum.startsWith("138")){
partitioner = 2;
}else if (phoneNum.startsWith("139")){
partitioner = 3;
}else{
partitioner = 4 ;
}
return partitioner;
}
}
driver里面加入
job.setPartitionerClass(PhoneNumPartitioner.class);
job.setNumReduceTasks(5);
6.2.6 Combiner合并(求平均数等操作时不能用)
(1) Combiner时MR程序中Mapper和Reducer之外的一种组件
(2)Combiner组件的父类就是Reducer
(3)Combiner和Reducer的区别在于运行的位置
Combiner是在每一个MapTask所在的节点运行
Reducer是接收全局所有Mapper的输出结果
(4)Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减少网络传输量
(5**)Combiner能够应用的前提是不能影响最终的业务逻辑**,而且,Combiner的输出kv应该跟Reducer输入kv类型要对应起来
Mapper Reducer
3 5 7 ->(3+5+7)/3=5 (3+5+7+2+6)/5 = 23/5 不等于
2 6 ->(2+6)/2=4
然后再Reducer端
(5+4)/2=4.5
6.2.7 Combiner合并案例实操
1 .需求
统计过程中对每一个MapTask的输出进行局部汇总,以减少网络量即采用Combiner功能
(1)数据输入
wordcount数据 (2)期望输出数据
期望:Combine输入数居多,输出时经过合并,输出数据降低
2 .需求分析
需求:对每一个MapTask的输出局部汇总(Combiner)
3 .案例实操-方案一
1)增加一个WordcountCombiner类继承Reducer
package com.atguigu.mr.combiner;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WordcountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// 1 汇总
int sum = 0;
for(IntWritable value :values){
sum += value.get();
}
v.set(sum);
// 2 写出
context.write(key, v);
}
}
2)在WordcountDriver驱动类指定Combiner
//指定需要使用的combiner,以及用哪个类作为combiner的逻辑
job.setCombinerClass(WordcountCombiner.class);
4 . 案例实操-方案二(直接让reducer作为combiner
1)将WordcountReducer作为Combiner在WordcountDriver驱动类中指定
//指定需要使用的Combiner,以及用哪个类作为Combiner的逻辑
job.setCombinerClass(WordcountReducer.class);
运行程序
6.2.7 GroupingComparator分组(辅助排序)
对Reduce阶段的数据根据某一个或几个字段进行分组
分组排序步骤:
(1)自定义类继承WritableCOmparator
(2)重写compare()方法
(1)自定义类继承WritableComparator
(2)重写compare()方法
@Override
public int compare(WritableComparable a, WritableComparable b) {
// 比较的业务逻辑
return result;
}
(3)创建一个构造将比较对象的类传给父类
protected OrderGroupingComparator() {
super(OrderBean.class, true);
}
6.2.8 GroupingComparator分组案例实操
1 .需求
有如下订单数据
订单id | 商品id | 成交金额 |
---|---|---|
0000001 | Pdt_01 | 222.8 |
0000001 | Pdt_02 | 33.8 |
0000002 | Pdt_03 | 522.8 |
0000002 | Pdt_04 | 122.4 |
0000002 | Pdt_05 | 722.4 |
0000003 | Pdt_06 | 232.8 |
0000003 | Pdt_02 | 33.8 |
现在需要求出每一个订单中最贵的商品
(1)输入数据
0000001 Pdt_01 222.8
0000002 Pdt_05 722.4
0000001 Pdt_02 33.8
0000003 Pdt_06 232.8
0000003 Pdt_02 33.8
0000002 Pdt_03 522.8
0000002 Pdt_04 122.4
(2)期望输出数据
1 222.8
2 722.4
3 232.8
(3)需求分析
先通过订单id为第一排序,成交金额为第二排序,把每一个数据进行排序
然后通过订单编号进行分组,求出成交价格最高的商品
2 代码实现
(1)定义订单信息OrderBean类
package com.atguigu.mr.order;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class OrderBean implements WritableComparable<OrderBean> {
private String orderId;
private Double money;
@Override
public String toString() {
return orderId+"\t"+money;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public int compareTo(OrderBean o) {
int i = orderId.compareTo(o.getOrderId());
if (i > 0) {
return 1;
} else if (i < 0) {
return -1;
} else {
return -money.compareTo(o.getMoney());
}
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(orderId);
dataOutput.writeDouble(money);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
orderId=dataInput.readUTF();
money = dataInput.readDouble();
}
}
(2)重写WritableComparator
package com.atguigu.mr.order;
import org.apache.avro.Schema;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class MyComparator extends WritableComparator {
protected MyComparator() {
super(OrderBean.class,true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
OrderBean orderBean = (OrderBean)a;
return -orderBean.getOrderId().compareTo(((OrderBean) b).getOrderId());
}
}
(3)Mapper类
package com.atguigu.mr.order;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class OrderMapper extends Mapper<LongWritable, Text,OrderBean, NullWritable> {
OrderBean k = new OrderBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String s = value.toString();
String[] s1 = s.split("\t");
System.out.println(s1);
System.out.println();
char c = s1[0].charAt(s1[0].length() - 1);
String s2 = String.valueOf(c);
Double money = Double.parseDouble(s1[2]);
k.setMoney(money);
k.setOrderId(s2);
context.write(k,NullWritable.get());
}
}
(4)Reducer类
package com.atguigu.mr.order;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class OrderReducer extends Reducer<OrderBean, NullWritable, Text, DoubleWritable> {
Text k = new Text();
DoubleWritable v = new DoubleWritable();
@Override
protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
//key里面存放的是分组排序的第一条数据
k.set(key.getOrderId());
v.set(key.getMoney());
context.write(k,v);
}
}
(5)OrderDriver类
package com.atguigu.mr.order;
import com.atguigu.mr.wordcount.MyCombine;
import com.atguigu.mr.wordcount.WordCountDriver;
import com.atguigu.mr.wordcount.WordCountMapper;
import com.atguigu.mr.wordcount.WordCountReducer;
import org.apache.avro.Schema;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.kerby.config.Conf;
import java.sql.Driver;
public class OrderDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 设置jar加载路径
// job.setJar("C:\\Users\\10185\\IdeaProjects\\day01\\target\\day01-1.0-SNAPSHOT.jar");
job.setJarByClass(OrderDriver.class);
//3.关联Mapper和Reducer类
job.setMapperClass(OrderMapper.class);
job.setReducerClass(OrderReducer.class);
//4.设置Mapper的输出key和value的类型
job.setMapOutputKeyClass(OrderBean.class);
job.setMapOutputValueClass(NullWritable.class);
//5.设置最终输出的key和value的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(DoubleWritable.class);
//6.设置输入和输出路径
// FileInputFormat.setInputPaths(job, new Path(args[0]));
// FileOutputFormat.setOutputPath(job, new Path(args[1]));
FileInputFormat.setInputPaths(job, new Path("I://input3"));
FileOutputFormat.setOutputPath(job, new Path("I://output11"));
job.setGroupingComparatorClass(MyComparator.class);
// job.setNumReduceTasks(3);
// job.setPartitionerClass(MyPartition.class);
//7.提交job
job.waitForCompletion(true);
}
}
6.3 OutputFormat数据输出
6.3.1 OutputFormat接口实现类
outputFormat接口实现类
OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了OutputFormat接口,下面我们介绍几种常见的OutputFormat实现类
1 .文本输出TextOutputFormat
默认的输出格式是TextOutputFormat,它把每条记录写为文本行,它的键和值可以是任意类型,因为TextOutputFormat调用toString()方法把它们转换成字符串
2 . SequenceFileOutputFormat
将SequenceFileOutputFormat输出作为后续MapReduce任务输入,这便是一种好的输出格式,因为它的格式紧凑,很容易被压缩
3 .自定义OutputFormat
根据用户需求,自定义实现输出
6.4.2 自定义OutputFormat
1 .使用场景
为了实现控制最终文件的输出路径和输出格式,可以自定义OutputFormat
例如:要在一个MapReuce程序中根据数据的不同输出两类结果到不同目录,这类灵活的输出需求可以通过自定义OuputFormat来实现
2 . 自定义OutputFormat步骤
(1)自定义一个类继承FileOutputFormat
(2)改写RecordWriter,具体改写输出数据的方法write().
6.4.3 自定义OutputFormat案例实操
1 需求
过滤输入的log日志,包含atguigu的网站输出到e:/atguigu.log,不包含atguigu的网站输出到e:/other.log
(1)输入数据
http://www.baidu.com
http://www.google.com
http://cn.bing.com
http://www.atguigu.com
http://www.sohu.com
http://www.sina.com
http://www.sin2a.com
http://www.sin2desa.com
http://www.sindsafa.com
期望输出
2 需求分析
3 案例实操
(1)编写FilterMapper类
package com.atguigu.mr.myoutputformat;
import org.apache.commons.lang.ObjectUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class OutMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
Text k = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
k.set(line);
context.write(k, NullWritable.get());
}
}
(2)编写FilterOutputFormat类
package com.atguigu.mr.myoutputformat;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class MyOutputFormat extends FileOutputFormat<Text, NullWritable> {
@Override
public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
return new MyRecordWriter(job);
}
}
(3)编写FilterMyRecordWriter
package com.atguigu.mr.myoutputformat;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import java.io.IOException;
public class MyRecordWriter extends RecordWriter<Text, NullWritable> {
private TaskAttemptContext context;
Path atguigu1=new Path("i:\\output\\atguigu.log");
Path other2=new Path("i:\\output\\other.log");
//注意文件系统只能有一个,可以通过Configuration来获取,不能新建一个,需要用以前的配置项,都已经写好了
private FileSystem fileSystem;
FSDataOutputStream fsDataOutputStream;
FSDataOutputStream fsDataOutputStream1;
public MyRecordWriter(TaskAttemptContext context) throws IOException {
this.context=context;
fileSystem = FileSystem.get(context.getConfiguration());
fsDataOutputStream = fileSystem.create(atguigu1);
fsDataOutputStream1 = fileSystem.create(other2);
}
@Override
public void write(Text key, NullWritable value) throws IOException, InterruptedException {
System.out.println(key.toString());
if (key.toString().contains("atguigu")) {
fsDataOutputStream.writeBytes(key.toString()+"\n\r");
} else {
fsDataOutputStream1.writeBytes(key.toString()+"\n\r");
}
}
@Override
public void close(TaskAttemptContext context) throws IOException, InterruptedException {
IOUtils.closeStream(fsDataOutputStream);
IOUtils.closeStream(fsDataOutputStream1);
}
}
FilterDriver类
package com.atguigu.mr.myoutputformat;
import com.atguigu.mr.order.*;
import org.apache.commons.lang.ObjectUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class OutDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
job.setJarByClass(OutDriver.class);
//3.关联Mapper和Reducer类
job.setMapperClass(OutMapper.class);
//4.设置Mapper的输出key和value的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
//5.设置最终输出的key和value的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("I://input4"));
FileOutputFormat.setOutputPath(job, new Path("I://output"));
job.setOutputFormatClass(MyOutputFormat.class);
);
//7.提交job
job.waitForCompletion(true);
}
}
6.4 MapReduce工作机制
(1)Read阶段:MapTask通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。
(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。
(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中。
(4)Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
溢写阶段详情:
步骤1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号Partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。
步骤2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢写次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。
步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index中。
(5)Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。
在进行文件合并过程中,MapTask以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并io.sort.factor(默认10)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。
让每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。
6.5 ReduceTask工作机制
ReduceTask工作机制
(1)Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
(2)Merge阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。
(3)Sort阶段:按照MapReduce语义,用户编写reduce()函数输入数据是按key进行聚集的一组数据。为了将key相同的数据聚在一起,Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部排序,因此,ReduceTask只需对所有数据进行一次归并排序即可。
(4)Reduce阶段:reduce()函数将计算结果写到HDFS上。
设置ReduceTask并行度(个数)
ReduceTask的并行度同样影响整个Job的执行并发度和执行效率,但与MapTask的并发数由切片数决定不同,ReduceTask数量的决定是可以直接手动设置:
// 默认值是1,手动设置为4
job.setNumReduceTasks(4);
6.6 MapReduce注意事项
6.6.1 Hadoop注意事项
reduce方法参数中的key和value,在执行过程中,变量指向的对象是同一个,每次迭代只是把新的值直接在内存中对对象的内容进行修改(看上去key不可变,其实是可变的,key地址不变化但值在变化,value类型也是一样)
package com.atguigu.mr.writableComparable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<FlowBean, NullWritable,FlowBean,NullWritable> {
FlowBean k = new FlowBean();
@Override
protected void reduce(FlowBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
Long upFlow = 0L;
Long downFlow=0L;
for (NullWritable value : values) {
upFlow += key.getUpFlow();
downFlow += key.getDownFlow();
}
k.setPhoneNumber(key.getPhoneNumber());
k.setUpFlow(upFlow);
k.setDownFlow(downFlow);
context.write(k, NullWritable.get());
}
}
6.7 MapReduce源码解读
MR源码解读:
一、 Job提交的流程
1. job.waitForCompletion(true); 在Driver中提交job
1) sumbit() 提交
(1) connect():
<1> return new Cluster(getConfiguration());
① initialize(jobTrackAddr, conf);
通过YarnClientProtocolProvider | LocalClientProtocolProvider 根据配置文件的参数信息
获取当前job需要执行到本地还是Yarn
最终:LocalClientProtocolProvider ==> LocalJobRunner
(2) return submitter.submitJobInternal(Job.this, cluster); 提交job
<1> . checkSpecs(job); 检查job的输出路径。
<2> . Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
生成Job提交的临时目录
D:\tmp\hadoop\mapred\staging\Administrator1777320722\.staging
<3> . JobID jobId = submitClient.getNewJobID(); 为当前Job生成Id
<4> . Path submitJobDir = new Path(jobStagingArea, jobId.toString()); Job的提交路径
d:/tmp/hadoop/mapred/staging/Administrator1777320722/.staging/job_local1777320722_0001
<5> . copyAndConfigureFiles(job, submitJobDir);
① rUploader.uploadResources(job, jobSubmitDir);
[1] uploadResourcesInternal(job, submitJobDir);
{1}.submitJobDir = jtFs.makeQualified(submitJobDir);
mkdirs(jtFs, submitJobDir, mapredSysPerms);
创建Job的提交路径
<6> . int maps = writeSplits(job, submitJobDir); //生成切片信息 ,并返回切片的个数
<7> . conf.setInt(MRJobConfig.NUM_MAPS, maps); //通过切片的个数设置MapTask的个数
<8> . writeConf(conf, submitJobFile); //将当前Job相关的配置信息写到job提交路径下
路径下: job.split job.splitmetainfo job.xml xxx.jar
<9> .status = submitClient.submitJob(
jobId, submitJobDir.toString(), job.getCredentials());
//真正提交Job
<10> . jtFs.delete(submitJobDir, true); //等job执行完成后,删除Job的临时工作目录的内容
二、 MapTask的工作机制
1. 从Job提交流程的(2)--><9> 进去
Job job = new Job(JobID.downgrade(jobid), jobSubmitDir); 构造真正执行的Job , LocalJobRunnber$Job
2. LocalJobRunnber$Job 的run()方法
1) TaskSplitMetaInfo[] taskSplitMetaInfos =
SplitMetaInfoReader.readSplitMetaInfo(jobId, localFs, conf, systemJobDir);
// 读取job.splitmetainfo
2) int numReduceTasks = job.getNumReduceTasks(); // 获取ReduceTask个数
3) List<RunnableWithThrowable> mapRunnables = getMapTaskRunnables(
taskSplitMetaInfos, jobId, mapOutputFiles);
// 根据切片的个数, 创建执行MapTask的 MapTaskRunnable
4) ExecutorService mapService = createMapExecutor(); // 创建线程池
5) runTasks(mapRunnables, mapService, "map"); //执行 MapTaskRunnable
6) 因为Runnable提交给线程池执行,接下来会执行MapTaskRunnable的run方法。
7) 执行 LocalJobRunner$Job$MapTaskRunnable 的run()方法.
(1) MapTask map = new MapTask(systemJobFile.toString(), mapId, taskId,
info.getSplitIndex(), 1); //创建MapTask对象
(2) map.run(localConf, Job.this); //执行MapTask中的run方法
<1> .runNewMapper(job, splitMetaInfo, umbilical, reporter);
① org.apache.hadoop.mapreduce.TaskAttemptContext taskContext = JobContextImpl
② org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE> mapper = WordConutMapper
③ org.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE> inputFormat = TextInputFormat
④ split = getSplitDetails(new Path(splitIndex.getSplitLocation()),
splitIndex.getStartOffset()); // 重构切片对象
切片对象的信息 : file:/D:/input/inputWord/JaneEyre.txt:0+36306679
⑤ org.apache.hadoop.mapreduce.RecordReader<INKEY,INVALUE> input = MapTask$NetTrackingRecordReader
⑥ output = new NewOutputCollector(taskContext, job, umbilical, reporter); //构造缓冲区对象
[1] collector = createSortingCollector(job, reporter); //获取缓冲区对象
MapTask$MapOutputBuffer
{1} . collector.init(context); //初始化缓冲区对象
1>>.final float spillper =
job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8);
// 溢写百分比 0.8
2>>.final int sortmb = job.getInt(MRJobConfig.IO_SORT_MB,
MRJobConfig.DEFAULT_IO_SORT_MB);
// 缓冲区大小 100M
3>>.sorter = ReflectionUtils.newInstance(job.getClass(
MRJobConfig.MAP_SORT_CLASS, QuickSort.class,
IndexedSorter.class), job);
// 排序对象
// 排序使用的是快排,并且基于索引排序。
4>> . // k/v serialization // kv序列化
5>> . // output counters // 计数器
6>> . // compression // 压缩
7>> . // combiner // combiner
⑦ mapper.run(mapperContext); // 执行WordCountMapper中的run方法。 实际执行的是
WordCountMapper继承的Mapper中的run方法。
[1] . 在Mapper中的run方法中
map(context.getCurrentKey(), context.getCurrentValue(), context);
执行到WordCountMapper中的map方法。
[2] . 在WordCountMapper中的map方法中将kv写出
context.write(outK,outV);
三、 Shuffle流程(溢写,归并)
1. map中的kv持续往 缓冲区写, 会达到溢写条件,发生溢写,最后发生归并。
2. map中的 context.write(k,v)
1) . mapContext.write(key, value);
(1). output.write(key, value);
<1> collector.collect(key, value,
partitioner.getPartition(key, value, partitions));
// 将map写出的kv 计算好分区后,收集到缓冲区中。
<2> . 当满足溢写条件后 ,开始发生溢写
startSpill();
① spillReady.signal(); //线程间通信,通知溢写线程开始溢写
② 溢写线程调用 sortAndSpill() 方法发生溢写操作
③ final SpillRecord spillRec = new SpillRecord(partitions);
final Path filename =
mapOutputFile.getSpillFileForWrite(numSpills, size);
out = rfs.create(filename)
//根据分区的个数,创建溢写文件,
/tmp/hadoop-Administrator/mapred/local/localRunner/Administrator/jobcache/job_local277309786_0001/attempt_local277309786_0001_m_000000_0/output/spill0.out
④ sorter.sort(MapOutputBuffer.this, mstart, mend, reporter); // 溢写前先排序
⑤ writer.close(); 通过writer进行溢写,溢写完成后,关闭流,可以查看磁盘中的溢写文件
⑥ if (totalIndexCacheMemory >= indexCacheMemoryLimit)
// create spill index file
Path indexFilename =
mapOutputFile.getSpillIndexFileForWrite(numSpills, partitions
// 判断索引使用的内存空间是否超过限制的大小,如果超过也需要溢写到磁盘
⑦ map持续往缓冲区写,达到溢写条件,就继续溢写 ........ 可能整个过程中发生N次溢写。
⑦ MapTask中的runNewMapper 中 output.close(mapperContext);
假如上一次溢写完后,剩余进入的到缓冲区的数据没有达到溢写条件,那么当map中的所有的数据
都已经处理完后,在关闭output时,会把缓冲区中的数据刷到磁盘中(其实就是没有达到溢写条件的数据也要写到磁盘)
[1] collector.flush(); //刷写
{1} . sortAndSpill(); 通过溢写的方法进行剩余数据的刷写
{2} . 最后一次刷写完后,磁盘中会有N个溢写文件
spill0.out spill1.out .... spillN.out
{3} . 归并 mergeParts();
>>1. for(int i = 0; i < numSpills; i++) {
filename[i] = mapOutputFile.getSpillFile(i);
finalOutFileSize += rfs.getFileStatus(filename[i]).getLen();
}
//根据溢写的次数,得到要归并多少个溢写文件
>>2. Path finalOutputFile =
mapOutputFile.getOutputFileForWrite(finalOutFileSize);
/tmp/hadoop-Administrator/mapred/local/localRunner/Administrator/jobcache/job_local1987086776_0001/attempt_local1987086776_0001_m_000000_0/output/file.out
Path finalIndexFile =
mapOutputFile.getOutputIndexFileForWrite(finalIndexFileSize);
/tmp/hadoop-Administrator/mapred/local/localRunner/Administrator/jobcache/job_local1987086776_0001/attempt_local1987086776_0001_m_000000_0/output/file.out.index
//生成最终存储数据的两个文件
>>3. for (int parts = 0; parts < partitions; parts++) {
// 按照分区的, 进行归并。
>>4 .awKeyValueIterator kvIter = Merger.merge(job, rfs,
keyClass, valClass, codec,
segmentList, mergeFactor,
new Path(mapId.toString()),
job.getOutputKeyComparator(), reporter, sortSegments,
null, spilledRecordsCounter, sortPhase.phase(),
TaskType.MAP);
//归并操作
>>5 Writer<K, V> writer =
new Writer<K, V>(job, finalPartitionOut, keyClass, valClass, codec,
spilledRecordsCounter);
//通过writer写归并后的数据到磁盘
>>6 . if (combinerRunner == null || numSpills < minSpillsForCombine) {
Merger.writeFile(kvIter, writer, reporter, job);
} else {
combineCollector.setWriter(writer);
combinerRunner.combine(kvIter, combineCollector);
}
在归并时,如果有combine,且溢写的次数大于等于minSpillsForCombine的值3,
会使用Combine
>>7. for(int i = 0; i < numSpills; i++) {
rfs.delete(filename[i],true);
}
归并完后,将溢写的文件删除
>> 8. 最后在磁盘中存储map处理完后的数据,等待reduce的拷贝。
file.out file.out.index
四、 ReduceTask工作机制
1. 在LocalJobRunner$Job中的run()方法中
try {
if (numReduceTasks > 0) {
//根据reduceTask的个数,创建对应个数的LocalJobRunner$Job$ReduceTaskRunnable
List<RunnableWithThrowable> reduceRunnables = getReduceTaskRunnables(
jobId, mapOutputFiles);
// 线程池
ExecutorService reduceService = createReduceExecutor();
//将 ReduceTaskRunnable提交给线程池执行
runTasks(reduceRunnables, reduceService, "reduce");
}
1) . 执行 LocalJobRunner$Job$ReduceTaskRunnable 中的run方法
(1) . ReduceTask reduce = new ReduceTask(systemJobFile.toString(),
reduceId, taskId, mapIds.size(), 1);
//创建ReduceTask对象
(2) . reduce.run(localConf, Job.this); // 执行ReduceTask的run方法
<1> . runNewReducer(job, umbilical, reporter, rIter, comparator,
keyClass, valueClass);
[1] . org.apache.hadoop.mapreduce.TaskAttemptContext taskContext = TaskAttemptContextImpl
[2] . org.apache.hadoop.mapreduce.Reducer<INKEY,INVALUE,OUTKEY,OUTVALUE> reducer = WordCountReducer
[3] . org.apache.hadoop.mapreduce.RecordWriter<OUTKEY,OUTVALUE> trackedRW = ReduceTask$NewTrackingRecordWriter
[4] . reducer.run(reducerContext);
//执行WordCountReducer的run方法 ,实际执行的是WordCountReducer继承的Reducer类中的run方法.
{1} .reduce(context.getCurrentKey(), context.getValues(), context);
//执行到WordCountReducer中的 reduce方法.
{2} . context.write(k,v) 将处理完的kv写出.
>>1 . reduceContext.write(key, value);
>>2 . output.write(key, value);
>>3 . real.write(key,value); // 通过RecordWriter将kv写出
>>4 . out.write(NEWLINE); //通过输出流将数据写到结果文件中
注意: 我们是在本地进行调试,所以N个MapTask 和 N个 ReduceTask没有并行的效果。
但是如果在集群上,N个 MapTask 和 N 个ReduceTask 是并行运行.
6.8 ETL数据清理
在运行核心业务MapReduce程序之前,往往要先对数据进行清理,清理掉不符合用户要求的数据。清理的过程往往只需要运行Mapper程序,不需要运行Reduce程序
6.8.1 需求
对Web访问日志中的各字段识别切分,去除日志中不合法的记录,根据清洗规则,输出过滤后的数据
(1)输入数据
58.248.178.212 - - [18/Sep/2013:06:51:37 +0000] "GET /nodejs-grunt-intro/ HTTP/1.1" 200 51770 "http://blog.fens.me/series-nodejs/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.248.178.212 - - [18/Sep/2013:06:51:40 +0000] "GET /wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1 HTTP/1.1" 200 7200 "http://blog.fens.me/nodejs-grunt-intro/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.248.178.212 - - [18/Sep/2013:06:51:40 +0000] "GET /wp-includes/js/comment-reply.min.js?ver=3.6 HTTP/1.1" 200 786 "http://blog.fens.me/nodejs-grunt-intro/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.248.178.212 - - [18/Sep/2013:06:51:40 +0000] "GET /wp-includes/js/jquery/jquery.js?ver=1.10.2 HTTP/1.1" 200 45307 "http://blog.fens.me/nodejs-grunt-intro/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.248.178.212 - - [18/Sep/2013:06:51:40 +0000] "GET /wp-includes/js/jquery/jquery.js?ver=1.10.2 HTTP/1.1" 200 93128 "http://blog.fens.me/nodejs-grunt-intro/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.248.178.212 - - [18/Sep/2013:06:51:40 +0000] "GET /wp-includes/js/comment-reply.min.js?ver=3.6 HTTP/1.1" 200 786 "http://blog.fens.me/nodejs-grunt-intro/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.215.204.118 - - [18/Sep/2013:06:51:41 +0000] "-" 400 0 "-" "-"
58.215.204.118 - - [18/Sep/2013:06:51:41 +0000] "-" 400 0 "-" "-"
58.215.204.118 - - [18/Sep/2013:06:51:41 +0000] "-" 400 0 "-" "-"
157.55.35.40 - - [18/Sep/2013:06:51:43 +0000] "GET /rhadoop-java-basic/ HTTP/1.1" 200 26780 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
58.248.178.212 - - [18/Sep/2013:06:51:43 +0000] "GET /wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1 HTTP/1.1" 200 7200 "http://blog.fens.me/nodejs-grunt-intro/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.248.178.212 - - [18/Sep/2013:06:51:45 +0000] "GET /wp-content/uploads/2013/08/grunt.png HTTP/1.1" 200 199040 "http://blog.fens.me/nodejs-grunt-intro/" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MDDR; InfoPath.2; .NET4.0C)"
58.215.204.118 - - [18/Sep/2013:06:52:26 +0000] "GET /nodejs-async/ HTTP/1.1" 200 12647 "http://blog.fens.me/nodejs-socketio-chat/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"
58.215.204.118 - - [18/Sep/2013:06:52:27 +0000] "GET /wp-includes/js/jquery/jquery.js?ver=1.10.2 HTTP/1.1" 304 0 "http://blog.fens.me/nodejs-async/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"
58.215.204.118 - - [18/Sep/2013:06:52:27 +0000] "GET /wp-includes/js/jquery/jquery-migrate.min.js?ver=1.2.1 HTTP/1.1" 304 0 "http://blog.fens.me/nodejs-async/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"
58.215.204.118 - - [18/Sep/2013:06:52:27 +0000] "GET /wp-includes/js/comment-reply.min.js?ver=3.6 HTTP/1.1" 304 0 "http://blog.fens.me/nodejs-async/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"
163.177.71.12 - - [18/Sep/2013:06:52:29 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
58.215.204.118 - - [18/Sep/2013:06:52:29 +0000] "GET /nodejs-async/?cf_action=sync_comments&post_id=2357 HTTP/1.1" 200 48 "http://blog.fens.me/nodejs-async/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"
163.177.71.12 - - [18/Sep/2013:06:52:32 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
58.215.204.118 - - [18/Sep/2013:06:52:32 +0000] "-" 400 0 "-" "-"
58.215.204.118 - - [18/Sep/2013:06:52:33 +0000] "-" 400 0 "-" "-"
58.215.204.118 - - [18/Sep/2013:06:52:33 +0000] "-" 400 0 "-" "-"
101.226.68.137 - - [18/Sep/2013:06:52:36 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
101.226.68.137 - - [18/Sep/2013:06:52:39 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
183.195.232.138 - - [18/Sep/2013:06:53:12 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
183.195.232.138 - - [18/Sep/2013:06:53:12 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
222.66.59.174 - - [18/Sep/2013:06:53:30 +0000] "GET /images/my.jpg HTTP/1.1" 200 19939 "http://www.angularjs.cn/A00n" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0"
222.66.59.174 - - [18/Sep/2013:06:53:36 +0000] "-" 400 0 "-" "-"
216.24.201.254 - - [18/Sep/2013:06:53:57 +0000] "GET /feed/ HTTP/1.1" 200 33867 "-" "Mozilla/5.0 (Ubuntu; X11; Linux x86_64; rv:8.0) Gecko/20100101 Firefox/8.0"
58.215.204.118 - - [18/Sep/2013:06:54:48 +0000] "GET /r-json-rjson/ HTTP/1.1" 200 11500 "http://blog.fens.me/nodejs-async/" "Mozilla/5.0 (Windows NT 5.1; rv:23.0) Gecko/20100101 Firefox/23.0"
61.135.216.105 - - [18/Sep/2013:06:54:51 +0000] "GET /comments/feed/ HTTP/1.1" 304 0 "-" "Mozilla/5.0 (compatible;YoudaoFeedFetcher/1.0;http://www.youdao.com/help/reader/faq/topic006/;1 subscribers;)"
222.66.59.174 - - [18/Sep/2013:06:55:19 +0000] "-" 400 0 "-" "-"
163.177.71.12 - - [18/Sep/2013:06:55:24 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
163.177.71.12 - - [18/Sep/2013:06:55:27 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
101.226.68.137 - - [18/Sep/2013:06:55:30 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
101.226.68.137 - - [18/Sep/2013:06:55:33 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
222.66.59.174 - - [18/Sep/2013:06:55:51 +0000] "GET /images/my.jpg HTTP/1.1" 200 19939 "http://www.angularjs.cn/" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)"
183.195.232.138 - - [18/Sep/2013:06:56:07 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
183.195.232.138 - - [18/Sep/2013:06:56:07 +0000] "HEAD / HTTP/1.1" 200 20 "-" "DNSPod-Monitor/1.0"
71.96.108.116 - - [18/Sep/2013:06:56:40 +0000] "GET /vps-ip-dns/ HTTP/1.1" 200 11403 "http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=6&cad=rja&ved=0CHIQFjAF&url=http%3A%2F%2Fblog.fens.me%2Fvps-ip-dns%2F&ei=j045UrP5AYX22AXsg4G4DQ&usg=AFQjCNGsJfLMNZnwWXNpTSUl6SOEzfF6tg&sig2=YY1oxEybUL7wx3IrVIMfHA&bvm=bv.52288139,d.b2I" "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0"
71.96.108.116 - - [18/Sep/2013:06:56:40 +0000] "GET /wp-content/themes/silesia/style.css HTTP/1.1" 200 7554 "http://blog.fens.me/vps-ip-dns/" "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0"
(2)期望输出数据
都是合法的数据
(1)定义一个bean,用来记录日志数据中的各数据字段
package com.atguigu.mapreduce.log;
public class LogBean {
private String remote_addr;// 记录客户端的ip地址
private String remote_user;// 记录客户端用户名称,忽略属性"-"
private String time_local;// 记录访问时间与时区
private String request;// 记录请求的url与http协议
private String status;// 记录请求状态;成功是200
private String body_bytes_sent;// 记录发送给客户端文件主体内容大小
private String http_referer;// 用来记录从那个页面链接访问过来的
private String http_user_agent;// 记录客户浏览器的相关信息
private boolean valid = true;// 判断数据是否合法
public String getRemote_addr() {
return remote_addr;
}
public void setRemote_addr(String remote_addr) {
this.remote_addr = remote_addr;
}
public String getRemote_user() {
return remote_user;
}
public void setRemote_user(String remote_user) {
this.remote_user = remote_user;
}
public String getTime_local() {
return time_local;
}
public void setTime_local(String time_local) {
this.time_local = time_local;
}
public String getRequest() {
return request;
}
public void setRequest(String request) {
this.request = request;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getBody_bytes_sent() {
return body_bytes_sent;
}
public void setBody_bytes_sent(String body_bytes_sent) {
this.body_bytes_sent = body_bytes_sent;
}
public String getHttp_referer() {
return http_referer;
}
public void setHttp_referer(String http_referer) {
this.http_referer = http_referer;
}
public String getHttp_user_agent() {
return http_user_agent;
}
public void setHttp_user_agent(String http_user_agent) {
this.http_user_agent = http_user_agent;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.valid);
sb.append("\001").append(this.remote_addr);
sb.append("\001").append(this.remote_user);
sb.append("\001").append(this.time_local);
sb.append("\001").append(this.request);
sb.append("\001").append(this.status);
sb.append("\001").append(this.body_bytes_sent);
sb.append("\001").append(this.http_referer);
sb.append("\001").append(this.http_user_agent);
return sb.toString();
}
}
(2)编写LogMapper类
package com.atguigu.mapreduce.log;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class LogMapper extends Mapper<LongWritable, Text, Text, NullWritable>{
Text k = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取1行
String line = value.toString();
// 2 解析日志是否合法
LogBean bean = parseLog(line);
if (!bean.isValid()) {
return;
}
k.set(bean.toString());
// 3 输出
context.write(k, NullWritable.get());
}
// 解析日志
private LogBean parseLog(String line) {
LogBean logBean = new LogBean();
// 1 截取
String[] fields = line.split(" ");
if (fields.length > 11) {
// 2封装数据
logBean.setRemote_addr(fields[0]);
logBean.setRemote_user(fields[1]);
logBean.setTime_local(fields[3].substring(1));
logBean.setRequest(fields[6]);
logBean.setStatus(fields[8]);
logBean.setBody_bytes_sent(fields[9]);
logBean.setHttp_referer(fields[10]);
if (fields.length > 12) {
logBean.setHttp_user_agent(fields[11] + " "+ fields[12]);
}else {
logBean.setHttp_user_agent(fields[11]);
}
// 大于400,HTTP错误
if (Integer.parseInt(logBean.getStatus()) >= 400) {
logBean.setValid(false);
}
}else {
logBean.setValid(false);
}
return logBean;
}
}
(3)编写LogDriver类
package com.atguigu.mapreduce.log;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class LogDriver {
public static void main(String[] args) throws Exception {
// 1 获取job信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 加载jar包
job.setJarByClass(LogDriver.class);
// 3 关联map
job.setMapperClass(LogMapper.class);
// 4 设置最终输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// 5 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 6 提交
job.waitForCompletion(true);
}
}
6.8 Join多种应用
6.8.1 Reduce Join
Map端的主要工作:为来自不同表或文件的key/value对,打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出
Reduce端的主要工作。在Reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在Map阶段已经标志)分开,最后进行合并就ok了
6.8.2 Reduce Join案例实操
1)需求
将商品信息表中数据根据商品pid合并到订单数据表中
2)需求分析
通过将关联条件作为Map输出的key,将两表满足Join条件的数据并携带数据所来源的文件信息,发往同一个ReduceTask,在Reduce中进行数据的串联
3)代码实现
(1)创建商品和订单合并后的Bean类
package com.atguigu.mr.join;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class JoinBean implements WritableComparable<JoinBean> {
/*id pid amount
1001 01 1
1002 02 2
1003 03 3
1004 01 4
1005 02 5
1006 03 6
表4-5 商品信息表t_product
pid pname
01 小米
02 华为
03 格力
*/
private String id;
private String pid;
private Integer amount;
private String pname;
private String fileType;
@Override
public String toString() {
return id+"\t"+pname+"\t"+amount;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(id);
dataOutput.writeUTF(pid);
dataOutput.writeInt(amount);
dataOutput.writeUTF(pname);
dataOutput.writeUTF(fileType);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
id = dataInput.readUTF();
pid=dataInput.readUTF();
amount=dataInput.readInt();
pname=dataInput.readUTF();
fileType=dataInput.readUTF();
}
@Override
public int compareTo(JoinBean o) {
return this.getPid().compareTo(o.getPid())==0?this.getId().compareTo(o.getId()):this.getPid().compareTo(o.getPid());
}
}
JoinMapper类
package com.atguigu.mr.join;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
public class JoinMapper extends Mapper<LongWritable, Text,JoinBean, NullWritable> {
private String name;
private JoinBean k = new JoinBean();
@Override
protected void setup(Context context) throws IOException, InterruptedException {
InputSplit split = context.getInputSplit();
FileSplit split1 = (FileSplit) split;
//通过切片信息获取文件
name = split1.getPath().getName();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
//如果文件名带有order,通过FileType判断是哪一个文件
if (name.contains("order")) {
String[] split = line.split("\t");
String id = split[0];
String pid = split[1];
Integer amount = Integer.parseInt(split[2]);
k.setId(id);
k.setPid(pid);
k.setAmount(amount);
k.setPname("");
k.setFileType("order");
} else {
String[] split = line.split("\t");
String pid = split[0];
String pname = split[1];
k.setId("");
k.setPid(pid);
k.setPname(pname);
k.setFileType("protect");
k.setAmount(0);
}
context.write(k,NullWritable.get());
}
}
joinReducer类
package com.atguigu.mr.join;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
public class JoinReducer extends Reducer<JoinBean, NullWritable,JoinBean,NullWritable> {
@Override
protected void reduce(JoinBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
ArrayList<JoinBean> beans = new ArrayList<JoinBean>();
String pname = "";
for (NullWritable value : values) {
//取出每一个key,由于地址不会变,但是值会变,所有把order文件取出来的map加入到数组,把
//product文件中取出来的map的pname取出来
try {
if (key.getFileType().equals("order")) {
JoinBean a = (JoinBean) BeanUtils.cloneBean(key);
beans.add(a);
} else {
pname = key.getPname();
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//遍历每一个bean对象
for (JoinBean bean : beans) {
bean.setPname(pname);
context.write(bean, NullWritable.get());
}
}
}
JoinDriver类
package com.atguigu.mr.join;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class JoinDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(JoinDriver.class);
//定义mapper和reducer的类
job.setMapperClass(JoinMapper.class);
job.setReducerClass(JoinReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(JoinBean.class);
job.setMapOutputValueClass(NullWritable.class);
//定义最后输出的类型
job.setOutputKeyClass(JoinBean.class);
job.setOutputValueClass(NullWritable.class);
job.setGroupingComparatorClass(JoinComparator.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input5"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output10"));
job.waitForCompletion(true);
}
}
Joincomparator
package com.atguigu.mr.join;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class JoinComparator extends WritableComparator {
public JoinComparator() {
super(JoinBean.class,true);
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
return ((JoinBean)a).getPid().compareTo(((JoinBean)b).getPid());
}
}
另一种方法
package com.atguigu.mr.join;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class JoinBean implements WritableComparable<JoinBean> {
/*id pid amount
1001 01 1
1002 02 2
1003 03 3
1004 01 4
1005 02 5
1006 03 6
表4-5 商品信息表t_product
pid pname
01 小米
02 华为
03 格力
*/
private String id;
private String pid;
private Integer amount;
private String pname;
private String fileType;
@Override
public String toString() {
return id+"\t"+pname+"\t"+amount;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(id);
dataOutput.writeUTF(pid);
dataOutput.writeInt(amount);
dataOutput.writeUTF(pname);
dataOutput.writeUTF(fileType);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
id = dataInput.readUTF();
pid=dataInput.readUTF();
amount=dataInput.readInt();
pname=dataInput.readUTF();
fileType=dataInput.readUTF();
}
@Override
public int compareTo(JoinBean o) {
//先通过pid排序,然后通过pname倒序,最后只要取出第一个就是product文件的pname,直接取出来
return this.getPid().compareTo(o.getPid())==0?-this.getPname().compareTo(o.getPname()):this.getPid().compareTo(o.getPid());
}
}
reducer
package com.atguigu.mr.join;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class JoinReducer extends Reducer<JoinBean, NullWritable,JoinBean,NullWritable> {
@Override
protected void reduce(JoinBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
String name = key.getPname();
for (NullWritable value : values) {
//取出每一个key,由于地址不会变,但是值会变,所有把order文件取出来的map加入到数组,把
//product文件中取出来的map的pname取出来
if (key.getFileType().equals("order")) {
key.setPname(name);
context.write(key, NullWritable.get());
}
}
//遍历每一个bean对象
}
}
6.8.3 MapJoin
1)使用场景
Map Join适用于一张表十分小、一张表很大的场景
2)优点
思考:在Reduce端处理过多的表,非常容易产生数据倾斜。怎么办?
在Map端存储多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜
3)具体办法:采用DistribtedCache
(1)在Mapper的setup阶段,将文件读取到缓存集合中
(2)在驱动函数中加载缓存
//缓存普通文件到Task运行节点
job.addCacheFile(new URI(“file:///e:/cache/pd.txt”));
6.8.4 Map Join 案例实操
1)需求
表 订单数据表t_order
id | pid | amount |
---|---|---|
1001 | 01 | 1 |
1002 | 02 | 2 |
1003 | 03 | 3 |
1004 | 01 | 4 |
1005 | 02 | 5 |
1006 | 03 | 6 |
表 商品信息表t_product
pid | pname |
---|---|
01 | 小米 |
02 | 华为 |
03 | 格力 |
将商品信息表中数据根据商品pid合并到订单数据表中
2)需求分析
MapJoin使用于关联表中有小表的情形
3)代码实现
(1)现在驱动模块中添加缓存文件
package com.atguigu.mr.mapJoin;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.net.URI;
public class MapJoinDriver {
public static void main(String[] args) throws Exception {
Job job = Job.getInstance(new Configuration());
job.addCacheFile(new URI("file:///I:/input6/product.txt"));
//设置不要执行reducer
job.setNumReduceTasks(0);
job.setMapperClass(MapJoinMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputValueClass(Text.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("i:\\input5"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output1"));
job.waitForCompletion(true);
}
}
(2)读取缓存的文件数据
package com.atguigu.mr.mapJoin;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
public class MapJoinMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
FileSystem fs;
Text v = new Text();
Map<String,String> map;
@Override
protected void setup(Context context) throws IOException, InterruptedException {
map = new HashMap<>();
//读取缓存文件
URI[] cacheFiles = context.getCacheFiles();
URI file = cacheFiles[0];
//获取缓存文件文件系统,以便进行传输
fs = FileSystem.get(context.getConfiguration());
FSDataInputStream open = fs.open(new Path(file));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(open));
String len;
//读取缓存文件所有数据,通过\t切割,放入map集合中,后面直接通过key获取value
while ((len = bufferedReader.readLine()) != null) {
String[] split = len.split("\t");
map.put(split[0],split[1]);
}
IOUtils.closeStream(bufferedReader);
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//新建一个map
String line = value.toString();
String[] split1 = line.split("\t");
String a = split1[0]+"\t"+map.get(split1[1])+"\t"+split1[2];
v.set(a);
context.write(v,NullWritable.get());
}
}
6.9 计数器的使用
Hadoop为每个作业维护若干内置计数器,以描述多项指标。例如,某些计数器记录已处理的字节数和记录数,使用户可监控已处理的输入数据量和已产生的输出数据量。
1 计数器API
(1)采用枚举的方式统计计数
enum.MyCount{MALFORORMED,NORMAL}
//对枚举定义的自定义计数器加1
context.getCounter(MyCount.MALFORORMED).increment(1);
(2)采用计数器组、计数器名称的方式统计
context.getCounter(“counterGroup”,“counter”).increment(1);
组名和计数器名称随便起,但最好有意义
2 计数器案例
@Override
protected void setup(Context context) throws IOException, InterruptedException {
context.getCounter("小迪迪","setupCount").increment(1);
map = new HashMap<>();
//读取缓存文件
URI[] cacheFiles = context.getCacheFiles();
URI file = cacheFiles[0];
//获取缓存文件文件系统,以便进行传输
fs = FileSystem.get(context.getConfiguration());
FSDataInputStream open = fs.open(new Path(file));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(open));
String len;
//读取缓存文件所有数据,通过\t切割,放入map集合中,后面直接通过key获取value
while ((len = bufferedReader.readLine()) != null) {
String[] split = len.split("\t");
map.put(split[0],split[1]);
}
IOUtils.closeStream(bufferedReader);
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//新建一个map
context.getCounter("小迪迪","mapCount").increment(1);
Counter counter = context.getCounter("小迪迪", "mapCount");
System.out.println(counter.getValue());
String line = value.toString();
String[] split1 = line.split("\t");
String a = split1[0]+"\t"+map.get(split1[1])+"\t"+split1[2];
v.set(a);
context.write(v,NullWritable.get());
}
}
加入getcounter
6.10 MapReduce扩展案例
第一题 倒排索引案例
解题思路
第一次
先让里面用空格划分的一个数据和文件名组成一个key,到reducer端进行合并,并以atguigu 文件名–>数量的形式进行输出
atguigu a.txt-->3
atguigu b.txt-->2
atguigu c.txt-->2
pingping a.txt-->1
pingping b.txt-->3
pingping c.txt-->1
ss a.txt-->2
ss b.txt-->1
ss c.txt-->1
第二次在通过相同的atguigu进行合并
atguigu c.txt-->2 b.txt-->2 a.txt-->3
pingping c.txt-->1 b.txt-->3 a.txt-->1
ss c.txt-->1 b.txt-->1 a.txt-->2
代码实现
OneIndexBean代码
package com.atguigu.mr.test1.oneindex;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class OneIndexBean implements WritableComparable<OneIndexBean> {
private String name;
private String fileName;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
@Override
public int compareTo(OneIndexBean o) {
return name.compareTo(o.getName())==0?fileName.compareTo(o.getFileName()):name.compareTo(o.getName());
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(name);
dataOutput.writeUTF(fileName);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
name = dataInput.readUTF();
fileName=dataInput.readUTF();
}
}
OneIndexMapper
package com.atguigu.mr.test1.oneindex;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
public class OneIndexMapper extends Mapper<LongWritable, Text,OneIndexBean, IntWritable> {
String name;
OneIndexBean k = new OneIndexBean();
IntWritable v = new IntWritable(1);
@Override
protected void setup(Context context) throws IOException, InterruptedException {
InputSplit inputSplit = context.getInputSplit();
FileSplit fileSplit = (FileSplit) inputSplit;
name = fileSplit.getPath().getName();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] s = line.split(" ");
for (String s1 : s) {
k.setName(s1);
k.setFileName(name);
context.write(k, v);
}
}
}
OneIndexReducer
package com.atguigu.mr.test1.oneindex;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class OneIndexReducer extends Reducer<OneIndexBean, IntWritable,Text,Text> {
Text k = new Text();
Text v = new Text();
@Override
protected void reduce(OneIndexBean key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
k.set(key.getName());
v.set(key.getFileName()+"-->"+sum);
context.write(k,v);
}
}
OneIndexDriver
package com.atguigu.mr.test1.oneindex;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class OneIndexDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(OneIndexDriver.class);
//定义mapper和reducer的类
job.setMapperClass(OneIndexMapper.class);
job.setReducerClass(OneIndexReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(OneIndexBean.class);
job.setMapOutputValueClass(IntWritable.class);
//定义最后输出的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input8"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output1"));
job.waitForCompletion(true);
}
}
第二次
TwoIndexMapper
package com.atguigu.mr.test1.twoindex;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class TwoIndexMapper extends Mapper<LongWritable, Text,Text,Text> {
Text k = new Text();
Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split("\t");
k.set(split[0]);
v.set(split[1]);
context.write(k, v);
}
}
TwoIndexReducer
package com.atguigu.mr.test1.twoindex;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class TwoIndexReducer extends Reducer<Text, Text,Text,Text> {
Text v = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
StringBuilder str = new StringBuilder();
for (Text value : values) {
str.append(value.toString()).append(" ");
}
v.set(str.toString());
context.write(key, v);
}
}
TwoIndexDriver
package com.atguigu.mr.test1.twoindex;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class TwoIndexDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(TwoIndexDriver.class);
//定义mapper和reducer的类
job.setMapperClass(TwoIndexMapper.class);
job.setReducerClass(TwoIndexReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//定义最后输出的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.setInputPaths(job, new Path("I:\\output1\\part-r-00000"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output10"));
job.waitForCompletion(true);
}
}
第二题 TopN案例
1 .需求
对需求2.3 输出结果进行加工,输出流量使用量在前10的用户信息
2 .解题思路
如果有重复的先进行合并一次job
用计数器进行计数,当输出到达10不在输出
3.代码实现
FlowBean的代码
package com.atguigu.mr.Test2;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class FlowBean implements WritableComparable<FlowBean> {
private Long upFlow;
private Long downFlow;
private Long sumFlow;
private String phoneNumber;
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public FlowBean() {
}
public Long getUpFlow() {
return upFlow;
}
public void setUpFlow(Long upFlow) {
this.upFlow = upFlow;
}
public void setSumFlow() {
sumFlow=upFlow+downFlow;
}
public Long getDownFlow() {
return downFlow;
}
public void setDownFlow(Long downFlow) {
this.downFlow = downFlow;
}
public Long getSumFlow() {
return sumFlow;
}
public void setSumFlow(Long sumFlow) {
this.sumFlow = sumFlow;
}
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(phoneNumber);
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
public void readFields(DataInput dataInput) throws IOException {
phoneNumber=dataInput.readUTF();
upFlow=dataInput.readLong();
downFlow=dataInput.readLong();
sumFlow=dataInput.readLong();
}
@Override
public String toString() {
return phoneNumber+"\t"+upFlow+"\t"+downFlow+"\t"+sumFlow;
}
@Override
public int compareTo(FlowBean o) {
return -sumFlow.compareTo(o.sumFlow);
}
}
FlowDriver类
package com.atguigu.mr.Test2;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class FlowDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(FlowDriver.class);
//定义mapper和reducer的类
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(NullWritable.class);
//定义最后输出的类型
job.setOutputKeyClass(FlowBean.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input9"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output5"));
job.waitForCompletion(true);
}
}
FlowMapper类
package com.atguigu.mr.Test2;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowMapper extends Mapper<LongWritable, Text,FlowBean, NullWritable> {
FlowBean k = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split("\t");
k.setPhoneNumber(split[0]);
k.setUpFlow(Long.parseLong(split[1]));
k.setDownFlow(Long.parseLong(split[2]));
k.setSumFlow();
context.write(k, NullWritable.get());
}
}
FlowReducer类
package com.atguigu.mr.Test2;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Counter;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<FlowBean, NullWritable,FlowBean,NullWritable> {
Counter counter;
@Override
protected void setup(Context context) throws IOException, InterruptedException {
counter = context.getCounter("writeCount", "writeCount");
}
@Override
protected void reduce(FlowBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
if (counter.getValue() > 10) {
return;
}
for (NullWritable value : values) {
if (counter.getValue() < 10) {
context.write(key, NullWritable.get());
counter.increment(1);
} else {
return;
}
}
}
}
第三题 找博客共同好友案例
1.需求
以下是博客的好友列表数据,冒号前是一个用户,冒号后是该用户的所有好友(数据中的好友是单向的)
求出哪些人两两之间有共同好友,及他俩的共同好友都有谁?
(1)数据输入
A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
D:A,E,F,L
E:B,C,D,M,L
F:A,B,C,D,E,O,M
G:A,C,D,E,F
H:A,C,D,E,O
I:A,O
J:B,O
K:A,C,D
L:D,E,F
M:E,F,G
O:A,H,I,J
2.解题思路
在map端先把每个冒号后的数据作为key
求出自己在哪里有好友
比如:
A:B,C,D,F,E,O
B在A那里有好友
C在A那里有好友
在map端进行B:A
C:A
D:A
F:A
然后再reducer端进行合并
第一次输出结果
A:I,K,C,B,G,F,H,O,D
B:A,F,J,E
C:A,E,B,H,F,G,K
D:G,C,K,A,L,F,E,H
E:G,M,L,H,A,F,B,D
F:L,M,D,C,G,A
G:M
H:O
I:O,C
J:O
K:B
L:D,E
M:E,F
O:A,H,I,J,F
第二次job 现在map端把用逗号隔开的两两组合,作为key,把冒号前面的作为value
先把所有数据放入到arrays数组,然后进行一个判断把小的放到前面
或者可以直接再arrays数组中进行排序,防止出现A->B和B->A的情况
最后的输出结果
A-->B:C,E
A-->C:D,F
A-->D:F,E
A-->E:B,D,C
A-->F:C,B,O,D,E
A-->G:F,D,E,C
A-->H:D,O,E,C
A-->I:O
A-->J:O,B
A-->K:D,C
A-->L:E,D,F
A-->M:F,E
B-->C:A
B-->D:E,A
B-->E:C
B-->F:E,A,C
B-->G:C,A,E
B-->H:E,C,A
B-->I:A
B-->K:C,A
B-->L:E
B-->M:E
B-->O:A
C-->D:F,A
C-->E:D
C-->F:D,A
C-->G:D,F,A
C-->H:A,D
C-->I:A
C-->K:D,A
C-->L:D,F
C-->M:F
C-->O:I,A
D-->E:L
D-->F:A,E
D-->G:E,A,F
D-->H:E,A
D-->I:A
D-->K:A
D-->L:E,F
D-->M:E,F
D-->O:A
E-->F:B,M,D,C
E-->G:D,C
E-->H:D,C
E-->J:B
E-->K:D,C
E-->L:D
F-->G:C,D,A,E
F-->H:A,E,O,C,D
F-->I:O,A
F-->J:O,B
F-->K:A,C,D
F-->L:E,D
F-->M:E
F-->O:A
G-->H:A,D,E,C
G-->I:A
G-->K:C,A,D
G-->L:F,D,E
G-->M:F,E
G-->O:A
H-->I:A,O
H-->J:O
H-->K:A,D,C
H-->L:D,E
H-->M:E
H-->O:A
I-->J:O
I-->K:A
I-->O:A
K-->L:D
K-->O:A
L-->M:F,E
3 代码实现
第一次job
OneFriendMapper
package com.atguigu.mr.test3.one;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class OneFriendMapper extends Mapper<LongWritable, Text,Text,Text> {
Text k = new Text();
Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split(",");
String[] split1 = split[0].split(":");
v.set(split1[0]);
for (int i = 0; i < split.length; i++) {
if (i == 0) {
k.set(split1[1]);
context.write(k, v);
} else {
k.set(split[i]);
context.write(k,v);
}
}
}
}
OneFriendReducer
package com.atguigu.mr.test3.one;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.Iterator;
public class OneFriendReducer extends Reducer<Text,Text, Text, NullWritable> {
Text k = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
StringBuilder stringBuilder = new StringBuilder(key.toString() + ":");
Iterator<Text> iterator = values.iterator();
while (iterator.hasNext()) {
stringBuilder.append(iterator.next());
if (iterator.hasNext()) {
stringBuilder.append(",");
}
}
k.set(stringBuilder.toString());
context.write(k, NullWritable.get());
}
}
OneFriendDriver
package com.atguigu.mr.test3.one;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class OneFriendDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(OneFriendDriver.class);
//定义mapper和reducer的类
job.setMapperClass(OneFriendMapper.class);
job.setReducerClass(OneFriendReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//定义最后输出的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input10"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output7"));
job.waitForCompletion(true);
}
}
第二次job
TwoFriendMapper
package com.atguigu.mr.test3.two;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
import java.util.ArrayList;
public class TwoFriendMapper extends Mapper<LongWritable, Text,Text,Text> {
Text k = new Text();
Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
ArrayList<String> strings = new ArrayList<>();
String line = value.toString();
String[] split = line.split(",");
String[] split1 = split[0].split(":");
//strings.add(split1[0]);
strings.add(split1[1]);
for (int i = 1; i < split.length ; i++) {
strings.add(split[i]);
}
for (int i = 0; i < strings.size(); i++) {
for (int j = i+1; j < strings.size(); j++) {
String str = getReversal(strings.get(i), strings.get(j));
k.set(str);
v.set(split1[0]);
context.write(k,v);
}
}
}
//进行反转,防止出现A-->B B-->A的情况
private String getReversal(String a,String b) {
if (a.compareTo(b) < 0) {
return a + "-->" + b;
} else {
return b + "-->"+a;
}
}
}
TwoFriendReducer
package com.atguigu.mr.test3.two;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class TwoFriendReducer extends Reducer<Text, Text,Text, NullWritable> {
Text k = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
StringBuffer stringBuffer = new StringBuffer(key.toString()+":");
for (Text value : values) {
stringBuffer.append(value).append(",");
}
stringBuffer.deleteCharAt(stringBuffer.length()-1);
k.set(stringBuffer.toString());
context.write(k, NullWritable.get());
}
}
TwoFriendDriver
package com.atguigu.mr.test3.two;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class TwoFriendDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(TwoFriendDriver.class);
//定义mapper和reducer的类
job.setMapperClass(TwoFriendMapper.class);
job.setReducerClass(TwoFriendReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//定义最后输出的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input11"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output2"));
job.waitForCompletion(true);
}
}
第七章 yarn资源调度器
Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源,相当于一个分布式的操作系统平台,而MapReduce等运算程序相当于运行于操作系统之上的应用程序
7.1 Yarn基础架构
YARN主要由ResourceManager、NodeManager、ApplicationMaster和Container等组件构成
7.2 Yarn工作机制
1 . Yarn运行机制
2 .工作机制详解
(1)MR程序提交到客户端所在的节点。
(2)YarnRunner向ResourceManager申请一个Application。
(3)RM将该应用程序的资源路径返回给YarnRunner。
(4)该程序将运行所需资源提交到HDFS上。
(5)程序资源提交完毕后,申请运行mrAppMaster。
(6)RM将用户的请求初始化成一个Task。
(7)其中一个NodeManager领取到Task任务。
(8)该NodeManager创建容器Container,并产生MRAppmaster。
(9)Container从HDFS上拷贝资源到本地。
(10)MRAppmaster向RM 申请运行MapTask资源。
(11)RM将运行MapTask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务并创建容器。
(12)MR向两个接收到任务的NodeManager发送程序启动脚本,这两个NodeManager分别启动MapTask,MapTask对数据分区排序。
(13)MrAppMaster等待所有MapTask运行完毕后,向RM申请容器,运行ReduceTask。
(14)ReduceTask向MapTask获取相应分区的数据。
(15)程序运行完毕后,MR会向RM申请注销自己。
7.3 作业提交全过程
作业提交全过程详解
(1)作业提交
第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。
第2步:Client向RM申请一个作业id。
第3步:RM给Client返回该job资源的提交路径和作业id。
第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。
第5步:Client提交完资源后,向RM申请运行MrAppMaster。
(2)作业初始化
第6步:当RM收到Client的请求后,将该job添加到容量调度器中。
第7步:某一个空闲的NM领取到该Job。
第8步:该NM创建Container,并产生MRAppmaster。
第9步:下载Client提交的资源到本地。
(3)任务分配
第10步:MrAppMaster向RM申请运行多个MapTask任务资源。
第11步:RM将运行MapTask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务并创建容器。
(4)任务运行
第12步:MR向两个接收到任务的NodeManager发送程序启动脚本,这两个NodeManager分别启动MapTask,MapTask对数据分区排序。
第13步:MrAppMaster等待所有MapTask运行完毕后,向RM申请容器,运行ReduceTask。
第14步:ReduceTask向MapTask获取相应分区的数据。
第15步:程序运行完毕后,MR会向RM申请注销自己。
(5)进度和状态更新
YARN中的任务将其进度和状态(包括counter)返回给应用管理器, 客户端每秒(通过mapreduce.client.progressmonitor.pollinterval设置)向应用管理器请求进度更新, 展示给用户。
(6)作业完成
除了向应用管理器请求作业进度外, 客户端每5秒都会通过调用waitForCompletion()来检查作业是否完成。时间间隔可以通过mapreduce.client.completion.pollinterval来设置。作业完成之后, 应用管理器和Container会清理工作状态。作业的信息会被作业历史服务器存储以备之后用户核查。
7.4 资源调度器
目前,Hadoop作业调度器主要有三种:FIFO、Capacity Scheduler和Fair Scheduler。Hadoop3.1.3默认的资源调度器是Capacity Scheduler。
具体设置详见:yarn-default.xml文件
<property>
<description>The class to use as the resource scheduler.</description>
<name>yarn.resourcemanager.scheduler.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value>
</property>
1.先进先出调度器(FIFO)
Hadoop最初设计目的是支持大数据批处理作业,如日志挖掘、Web索引等作业,
为此,Hadoop仅提供了一个非常简单的调度机制:FIFO,即先来先服务,在该调度机制下,所有作业被统一提交到一个队列中,Hadoop按照提交顺序依次运行这些作业。
但随着Hadoop的普及,单个Hadoop集群的用户量越来越大,不同用户提交的应用程序往往具有不同的服务质量要求,典型的应用有以下几种:
批处理作业:这种作业往往耗时较长,对时间完成一般没有严格要求,如数据挖掘、机器学习等方面的应用程序。
交互式作业:这种作业期望能及时返回结果,如SQL查询(Hive)等。
生产性作业:这种作业要求有一定量的资源保证,如统计值计算、垃圾数据分析等。
此外,这些应用程序对硬件资源需求量也是不同的,如过滤、统计类作业一般为CPU密集型作业,而数据挖掘、机器学习作业一般为I/O密集型作业。因此,简单的FIFO调度策略不仅不能满足多样化需求,也不能充分利用硬件资源。
2.容量调度器(Capacity Scheduler)
Capacity Scheduler Capacity Scheduler 是Yahoo开发的多用户调度器,它以队列为单位划分资源,每个队列可设定一定比例的资源最低保证和使用上限,同时,每个用户也可设定一定的资源使用上限以防止资源滥用。而当一个队列的资源有剩余时,可暂时将剩余资源共享给其他队列。
总之,Capacity Scheduler 主要有以下几个特点:
①容量保证。管理员可为每个队列设置资源最低保证和资源使用上限,而所有提交到该队列的应用程序共享这些资源。
②灵活性,如果一个队列中的资源有剩余,可以暂时共享给那些需要资源的队列,而一旦该队列有新的应用程序提交,则其他队列释放的资源会归还给该队列。这种资源灵活分配的方式可明显提高资源利用率。
③多重租赁。支持多用户共享集群和多应用程序同时运行。为防止单个应用程序、用户或者队列独占集群中的资源,管理员可为之增加多重约束(比如单个应用程序同时运行的任务数等)。
④安全保证。每个队列有严格的ACL列表规定它的访问用户,每个用户可指定哪些用户允许查看自己应用程序的运行状态或者控制应用程序(比如杀死应用程序)。此外,管理员可指定队列管理员和集群系统管理员。
⑤动态更新配置文件。管理员可根据需要动态修改各种配置参数,以实现在线集群管理。
3.公平调度器(Fair Scheduler)
Fair Scheduler Fair Schedulere是Facebook开发的多用户调度器。
公平调度器的目的是让所有的作业随着时间的推移,都能平均地获取等同的共享资源!当一个作业在运行时,它会使用整个集群但是如果有其他作业提交上来,系统会将空闲的资源分配给新的作业!每个任务大致上会获取平等数量的资源!和传统的调度策略不同的是
它会让小的任务在合理的时间完成,同时不会让需要长时间运行的耗费大量资源的应用挨饿!
同Capacity Scheduler类似,它以队列为单位划分资源,每个队列可设定一定比例的资源最低保证和使用上限,同时,每个用户也可设定一定的资源使用上限以防止资源滥用;当一个队列的资源有剩余时,可暂时将剩余资源共享给其他队列。
当然,Fair Scheduler也存在很多与Capacity Scheduler不同之处,这主要体现在以下几个方面:
①资源公平共享。在每个队列中,Fair Scheduler 可选择按照FIFO、Fair或DRF策略为应用程序分配资源。其中,Fair 策略(默认)是一种基于最大最小公平算法实现的资源多路复用方式,默认情况下,每个队列内部采用该方式分配资源。这意味着,如果一个队列中有两个应用程序同时运行,则每个应用程序可得到1/2的资源;如果三个应用程序同时运行,则每个应用程序可得到1/3的资源。
②支持资源抢占。当某个队列中有剩余资源时,调度器会将这些资源共享给其他队列,而当该队列中有新的应用程序提交时,调度器要为它回收资源。为了尽可能降低不必要的计算浪费,调度器采用了先等待再强制回收的策略,即如果等待一段时间后尚有未归还的资源,则会进行资源抢占:从那些超额使用资源的队列中杀死一部分任务,进而释放资源。
③负载均衡。Fair Scheduler提供了一个基于任务数目的负载均衡机制,该机制尽可能将系统中的任务均匀分配到各个节点上。此外,用户也可以根据自己的需要设计负载均衡机制。
④调度策略配置灵活。Fair Scheduler允许管理员为每个队列单独设置调度策略(当前支持FIFO、Fair或DRF三种)。
⑤提高小应用程序响应时间。由于采用了最大最小公平算法,小作业可以快速获取资源并运行完成
7.5 最大最小算法
####不加权
二十个资源平均分配,然后通过剩下的取出来平均分配
加权
###7.6 DRF
DRF 我们之前说的资源,都是单一标准,例如只考虑内存(也是yarn默认的情况)。但是很多时候我们资源有很多种,例如内存,CPU,网络带宽等,这样我们很难衡量两个应用应该分配的资源比例 那么在Yarn中,我们用DRF来决定如何调度,假设集群有10Tmem和100CPU,而应用A需要(2CPU,300GB),应用B需要(6CPU,100GB)则两个应用分别需要系统(2%,3%)和(6%,1%)的资源,这就意味着A是mem主导的,B是CPU主导的,并且他俩的比例3%:6%:1:2,这样两者就按照1:2的比例区分配资源
7.7 容量调度器多队列提交案例
7.7.1 需求
Yarn默认的容量调度器是一条单队列的调度器,在实际使用过程中会出现单个任务阻塞整个队列的情况。同时,随着业务的增长,公司需要分业务限制集群使用率。这就需要我们按照业务种类配置多条任务队列
7.7.2 配置多队列的容量调度器
默认Yarn的配置下,容量调度器只有一条Default队列,在capacity-scheduler.xml中可以配置多条队列,并降低default队列资源占比
<property>
<name>yarn.scheduler.capacity.root.queues</name>
<value>default,hive</value>
<description>
The queues at the this level (root is the root queue).
</description>
</property>
<property>
<name>yarn.scheduler.capacity.root.default.capacity</name>
<value>40</value>
</property>
同时为新加队列添加必要属性
<!--队列目标资源百分比,所有队列相加必须等于100-->
<property>
<name>yarn.scheduler.capacity.root.hive.capacity</name>
<value>60</value>
</property>
<!--队列最大资源百分比-->
<property>
<name>yarn.scheduler.capacity.root.hive.maximum-capacity</name>
<value>100</value>
</property>
<!—单用户可用队列资源占比-->
<property>
<name>yarn.scheduler.capacity.root.hive.user-limit-factor</name>
<value>1</value>
</property>
<!--队列状态(RUNNING或STOPPING)-->
<property>
<name>yarn.scheduler.capacity.root.hive.state</name>
<value>RUNNING</value>
</property>
<!—队列允许哪些用户提交-->
<property>
<name>yarn.scheduler.capacity.root.hive.acl_submit_applications</name>
<value>*</value>
</property>
<!—队列允许哪些用户管理-->
<property>
<name>yarn.scheduler.capacity.root.hive.acl_administer_queue</name>
<value>*</value>
</property>
7.7.3 向Hive队列提交任务
默认的任务提交都是提交到default队列的,如果希望向其他队列提交任务,需要在driver中声明
public class TwoFriendDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS", "hdfs://hadoop102:8020");
// 指定MapReduce运行在Yarn上
configuration.set("mapreduce.framework.name","yarn");
// 指定mapreduce可以在远程集群运行
configuration.set("mapreduce.app-submission.cross-platform","true");
//指定Yarn resourcemanager的位置
configuration.set("yarn.resourcemanager.hostname","hadoop103");
configuration.set("mapred.job.queue.name", "hive");
//新建一个job对象
Job job = Job.getInstance(configuration);
job.setJar("C:\\Users\\10185\\IdeaProjects\\day01\\target\\day01-1.0-SNAPSHOT.jar");
//定义需要执行的类
// job.setJarByClass(TwoFriendDriver.class);
//定义mapper和reducer的类
job.setMapperClass(TwoFriendMapper.class);
job.setReducerClass(TwoFriendReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//定义最后输出的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("hdfs://hadoop102:8020/input1"));
FileOutputFormat.setOutputPath(job, new Path("hdfs://hadoop102:8020/output7"));
job.waitForCompletion(true);
}
}
7.8 任务的推测执行
1.作业完成时间取决于最慢的任务完成时间
一个作业由若干个Map任务和Reduce任务构成。因硬件老化、软件Bug等,某些任务可能运行非常慢。
思考:系统中有99%的Map任务都完成了,只有少数几个Map老是进度很慢,完不成,怎么办?
2.推测执行机制
发现拖后腿的任务,比如某个任务运行速度远慢于任务平均速度。为拖后腿任务启动一个备份任务,同时运行。谁先运行完,则采用谁的结果。
3.执行推测任务的前提条件
(1)每个Task只能有一个备份任务
(2)当前Job已完成的Task必须不小于0.05(5%)
(3)开启推测执行参数设置。mapred-site.xml文件中默认是打开的。
<property>
<name>mapreduce.map.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some map tasks may be executed in parallel.</description>
</property>
<property>
<name>mapreduce.reduce.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some reduce tasks may be executed in parallel.</description>
</property>
4.不能启用推测执行机制情况
(1)任务间存在严重的负载倾斜;
(2)特殊任务,比如任务向数据库中写数据。
5 . 算法 原理
第八章 Hadoop数据压缩
8.1 概述
压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在运行MR程序时,I/O操作、网络数据传输、 Shuffle和Merge要花大量的时间,尤其是数据规模很大和工作负载密集的情况下,因此,使用数据压缩显得非常重要。
鉴于磁盘I/O和网络带宽是Hadoop的宝贵资源,数据压缩对于节省资源、最小化磁盘I/O和网络传输非常有帮助。可以在任意MapReduce阶段启用压缩。不过,尽管压缩与解压操作的CPU开销不高,其性能的提升和资源的节省并非没有代价。
压缩是提高Hadoop运行效率的一种优化策略。
通过对Mapper、Reducer运行过程中的数据进行压缩,以减少磁盘IO,提高MR程序运行速度
注意:采用压缩技术减少了磁盘IO,但同时增加了CPU运算负担,所有,压缩特征运用得当能提高性能,但运用不当也可能降低性能
压缩的基本规则:
(1)运算密集型的job,少用压缩
(2)运算IO密集型的job,多用压缩
8.2 MR支持的压缩编码
压缩格式 | hadoop自带? | 算法 | 文件扩展名 | 是否可切分 | 换成压缩格式后,原来的程序是否需要修改 |
---|---|---|---|---|---|
DEFLATE | 是,直接使用 | DEFLATE | .deflate | 否 | 和文本处理一样,不需要修改 |
Gzip | 是,直接使用 | DEFLATE | .gz | 否 | 和文本处理一样,不需要修改 |
bzip2 | 是,直接使用 | bzip2 | .bz2 | 是 | 和文本处理一样,不需要修改 |
LZO | 否,需要安装 | LZO | .lzo | 是 | 需要建索引,还需要指定输入格式 |
Snappy | 否,需要安装 | Snappy | .snappy | 否 | 和文本处理一样,不需要修改 |
为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示
压缩格式 | 对应的编码/解码器 |
---|---|
DEFLATE | org.apache.hadoop.io.compress.DefaultCodec |
gzip | org.apache.hadoop.io.compress.GzipCodec |
bzip2 | org.apache.hadoop.io.compress.BZip2Codec |
LZO | com.hadoop.compression.lzo.LzopCodec |
Snappy | org.apache.hadoop.io.compress.SnappyCodec |
压缩性能比较
压缩算法 | 原始文件大小 | 压缩文件大小 | 压缩速度 | 解压速度 |
---|---|---|---|---|
gzip | 8.3GB | 1.8GB | 17.5MB/s | 58MB/s |
bzip2 | 8.3GB | 1.1GB | 2.4MB/s | 9.5MB/s |
LZO | 8.3GB | 2.9GB | 49.3MB/s | 74.6MB/s |
http://google.github.io/snappy/
On a single core of a Core i7 processor in 64-bit mode, Snappy compresses at about 250 MB/sec or more and decompresses at about 500 MB/sec or more.
8.3 压缩方式选择
8.3.1 Gzip压缩
优点:压缩率比较高,而且压缩/解压速度也比较快;Hadoop本身支持,在应用中处理Gzip格式的文件就和直接处理文本一样;大部分Linux系统都自带Gzip命令,使用方便。
缺点:不支持切片
应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用Gzip压缩格式。例如说一天或者一个小时的日志压缩成一个Gzip文件。
8.3.2 Bzip2压缩
优点:支持Split;具有很高的压缩率,比Gzip压缩率都高;Hadoop本身自带,使用方便
缺点:压缩/解压速度慢
应用场景:适合对速度要求不高,但需要较高的压缩率的时候;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持Split,而且兼容之前的应用程序的情况。
8.3.3 Lzo压缩
优点:压缩/解压速度也比较快,合理的压缩率;支持Split,是Hadoop中最流行的压缩格式;可以在Linux系统下安装lzop命令,使用方便。
缺点:压缩率比Gzip要低一些;Hadoop本身不支持,需要安装;在应用中对Lzo格式的文件需要做一些特殊处理(为了支持Split需要建索引,还需要指定InputFormat为Lzo格式)。
应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,Lzo优点越明显。
8.3.4 Snappy压缩
优点:高速压缩速度和合理的压缩率
缺点:不支持Split;压缩率比Gzip要低;
应用场景:
当MapReduce作业的Map输出的数据比较大的时候,作为Map到Reduce的中间数据的压缩格式;或者作为一个MapReduce作业的输出和另外一个MapReduce作业的输入。
8.4 压缩位置选择
压缩可以在MapReduce作用的任意阶段启用
8.5 数据压缩配置
要在Hadoop中启用压缩,可以配置如下参数:
表4-4 配置参数
参数 | 默认值 | 阶段 | 建议 |
---|---|---|---|
io.compression.codecs (在core-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec | 输入压缩 | Hadoop使用文件扩展名判断是否支持某种编解码器 |
mapreduce.map.output.compress(在mapred-site.xml中配置) | false | mapper输出 | 这个参数设为true启用压缩 |
mapreduce.map.output.compress.codec(在mapred-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec | mapper输出 | 企业多使用LZO或Snappy编解码器在此阶段压缩数据 |
mapreduce.output.fileoutputformat.compress(在mapred-site.xml中配置) | false | reducer输出 | 这个参数设为true启用压缩 |
mapreduce.output.fileoutputformat.compress.codec(在mapred-site.xml中配置) | org.apache.hadoop.io.compress.DefaultCodec | reducer输出 | 使用标准工具或者编解码器,如gzip和bzip2 |
mapreduce.output.fileoutputformat.compress.type(在mapred-site.xml中配置) | RECORD | reducer输出 | SequenceFile输出使用的压缩类型:NONE和BLOCK |
8.6 压缩实操案例
8.6.1 数据流的压缩与解压缩
CompressionCodec有两种方法可以用于轻松地压缩或解压缩数据
要想对正在被写入一个数据流的数据进行压缩,我们可以使用createOutputStream(OutputStreamout)方法创建一个CompressionOutputStream,将其以压缩格式写入底层的流
相反,要想对从输入流读取而来的数据进行解压缩,则调用createInputStream(InputStreamin)函数,从而获得一个CompressionInputStream,从而从底层的流读取未压缩的数据。
测试一下如下压缩方式:
表4-5
DEFLATE | org.apache.hadoop.io.compress.DefaultCodec |
---|---|
gzip | org.apache.hadoop.io.compress.GzipCodec |
bzip2 | org.apache.hadoop.io.compress.BZip2Codec |
package com.atguigu;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionCodecFactory;
import org.apache.hadoop.io.compress.CompressionInputStream;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.junit.Test;
import java.io.IOException;
public class Test2 {
@Test
public void compressFile() throws IOException {
//定义输入地址
String inPath = "I:\\1\\文件【高淇python400集】.pdf";
//定义输出地址
String outPath = "i:\\output1\\文件【高淇python400集】.pdf";
//获取编解码器对象
Configuration configuration = new Configuration();
CompressionCodec codecByClassName = new CompressionCodecFactory(configuration).getCodecByClassName("org.apache.hadoop.io.compress.DefaultCodec");
FileSystem fileSystem = FileSystem.get(configuration);
//获取输入流
FSDataInputStream open = fileSystem.open(new Path(inPath));
//获取输出流
FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path(outPath+codecByClassName.getDefaultExtension()));
//通过编解码器对象获取一个可压缩的输出流
CompressionOutputStream outputStream = codecByClassName.createOutputStream(fsDataOutputStream);
//进行文件的拷贝
IOUtils.copyBytes(open, outputStream, configuration);
//关闭流
IOUtils.closeStream(open);
IOUtils.closeStream(fsDataOutputStream);
IOUtils.closeStream(outputStream);
}
@Test
public void upCompressFile() throws IOException {
//定义输出输入地址
String output = "I:\\output1\\文件【高淇python400集】.pdf";
String in = "I:\\output1\\文件【高淇python400集】.pdf.deflate";
//获取编码器对象
Configuration configuration = new Configuration();
CompressionCodec codec = new CompressionCodecFactory(configuration).getCodec(new Path(in));
FileSystem fileSystem = FileSystem.get(configuration);
FSDataInputStream open = fileSystem.open(new Path(in));
FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path(output));
//获取可以解压缩文件的输入流
CompressionInputStream inputStream = codec.createInputStream(open);
IOUtils.copyBytes(inputStream, fsDataOutputStream, configuration);
//关闭流
IOUtils.closeStream(open);
IOUtils.closeStream(fsDataOutputStream);
IOUtils.closeStream(inputStream);
}
}
8.6.2 Map输出端采用压缩
即使你的MapReduce的输入输出文件都是未压缩的文件,你仍然可以对Map任务的中间结果输出做压缩,因为它要写在硬盘并且通过网络传输到Reduce节点,对其压缩可以提高很多性能,这些工作只要设置两个属性即可,我们来看下代码怎么设置。
package com.atguigu.mr.writableComparable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.compress.BZip2Codec;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class FlowDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
configuration.set("mapreduce.map.output.compress","true");
configuration.set("mapreduce.map.output.compress.codec", "org.apache.hadoop.io.compress.DefaultCodec");
// configuration.set("mapreduce.output.fileoutputformat.compress","true");
// configuration.set("mapreduce.output.fileoutputformat.compress.codec", "org.apache.hadoop.io.compress.DefaultCodec");
//新建一个job对象
Job job = Job.getInstance(configuration);
//定义需要执行的类
job.setJarByClass(FlowDriver.class);
//定义mapper和reducer的类
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
//定义map端输出的类型
job.setMapOutputKeyClass(FlowBean.class);
job.setMapOutputValueClass(NullWritable.class);
//定义最后输出的类型
job.setOutputKeyClass(FlowBean.class);
job.setOutputValueClass(NullWritable.class);
//job.setPartitionerClass(MyPartition.class);
//job.setNumReduceTasks(5);
//定义输出路径
// job.setInputFormatClass(CombineTextInputFormat.class);
//输出压缩格式也可以这里设置
FileOutputFormat.setCompressOutput(job, true);
FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
FileInputFormat.setInputPaths(job, new Path("I:\\input\\test.txt.deflate"));
FileOutputFormat.setOutputPath(job, new Path("I:\\output5"));
job.setGroupingComparatorClass(FlowComparator.class);
//设置提交
job.waitForCompletion(true);
}
}
如果要保持长久的使用,还需要再配置文件里面进行设置
第九章 企业优化
9.1 MapReduce程序效率的瓶颈在于两点:
1.计算机性能
CPU、内存、磁盘监控、网络
2.I/O操作优化
(1)数据倾斜
(2)Map和Reduce数设置不合理
(3)Map运行时间太长,导致Reduce等待过久
(4)小文件过多
(5)大量的不可分块的超大文件
(6)Spill次数过多
(7)Merge次数过多
9.2 MapReduce优化方法
MapReduce优化方法主要从六个方面考虑:数据输入、Map阶段、Reduce阶段、IO传输、数据倾斜问题和常用的调优参数
9.2.1 数据输入
(1)合并小文件:在执行MR任务前将小文件进行合并,大量的小文件会产生大量的Map任务,增大Map任务装载次数,而任务的装载比较耗时,从而导致MR运行较慢。
(2)采用CombineTextInputFormat来作为输入,解决输入端大量小文件场景
9.2.2 Map阶段
(1)减少溢写(Spill)次数:通过调整io.sort.mb及sort.spill.percent参数值,增大触发Spill的内存上限,减少Spill次数,从而减少磁盘IO
(2)减少合并(Merge)次数:通过调整io.sort.factor参数,增大Merge的文件数目,减少Merge的次数,从而缩短MR处理时间
(3)在Map之后,不影响业务逻辑前提下,先进行Combine处理,减少 I/O。
9.2.3 Reduce阶段
(1)合理设置Map和Reduce数:两个都不能设置太少,也不能设置太多。太少,会导致Task等待,延长处理时间;太多,会导致Map、Reduce任务间竞争资源,造成处理超时等错误。
(2)设置Map、Reduce共存:调整slowstart.completedmaps参数,使Map运行到一定程度后,Reduce也开始运行,减少Reduce的等待时间。
(3)规避使用Reduce:因为Reduce在用于连接数据集的时候将会产生大量的网络消耗
(4)合理设置Reduce端的Buffer:默认情况下,数据达到一个阈值的时候,Buffer中的数据就会写入磁盘,然后Reduce会从磁盘中获得所有的数据。也就是说,Buffer和Reduce是没有直接关联的,中间多次写磁盘->读磁盘的过程,既然有这个弊端,那么就可以通过参数来配置,使得Buffer中的一部分数据可以直接输送到Reduce,从而减少IO开销:mapreduce.reduce.input.buffer.percent,默认为0.0。当值大于0的时候,会保留指定比例的内存读Buffer中的数据直接拿给Reduce使用。这样一来,设置Buffer需要内存,读取数据需要内存,Reduce计算也要内存,所以要根据作业的运行情况进行调整。
9.2.4 I/O传输
1)采用数据压缩的方式,减少网络IO的时间,安装Snappy和LZO压缩编码器
2)使用SequenceFile二进制文件
9.2.5 数据倾斜问题
1.数据倾斜现象
数据频率倾斜—某一个区域的数据量要远远大于其他区域
数据大小倾斜—部分的记录的大小远远大于平均值
2.减少数据倾斜的方法
方法1:抽样的范围分区
可以通过对原始数据进行抽样得到的结果集来预设分区边界值
方法2:自定义分区
基于输出键的背景知识进行自定义分区,例如,如果Map输出键的单词来源于一本书,且其中某个专业词汇较多,那么就可以自定义分区将这些专业词汇发送给固定的一部分Reduce示例,而将其他的都发送给剩余的Reduce示例
方法3:Combine
使用Combine可以大量的减少数据倾斜。在可能的情况下,Combine的目的就是聚合并精简数据
方法4:采用Map Join,尽量避免Reduce Join、
9.2.6 常用的调优参数
1 . 资源相关参数
(1)以下参数是在用户自己的MR应用程序中配置就可以生效(mapred-default.xml)
配置参数 | 参数说明 |
---|---|
mapreduce.map.memory.mb | 一个MapTask可使用的资源上限(单位:MB),默认为1024。如果MapTask实际使用的资源量超过该值,则会被强制杀死。 |
mapreduce.reduce.memory.mb | 一个ReduceTask可使用的资源上限(单位:MB),默认为1024。如果ReduceTask实际使用的资源量超过该值,则会被强制杀死。 |
mapreduce.map.cpu.vcores | 每个MapTask可使用的最多cpu core数目,默认值: 1 |
mapreduce.reduce.cpu.vcores | 每个ReduceTask可使用的最多cpu core数目,默认值: 1 |
mapreduce.reduce.shuffle.parallelcopies | 每个Reduce去Map中取数据的并行数。默认值是5 |
mapreduce.reduce.shuffle.merge.percent | Buffer中的数据达到多少比例开始写入磁盘。默认值0.66 |
mapreduce.reduce.shuffle.input.buffer.percent | Buffer大小占Reduce可用内存的比例。默认值0.7 |
mapreduce.reduce.input.buffer.percent | 指定多少比例的内存用来存放Buffer中的数据,默认值是0.0 |
(2)应该在YARN启动之前就配置在服务器的配置文件中才能生效
(yarn-default.xml)
配置参数 | 参数说明 |
---|---|
yarn.scheduler.minimum-allocation-mb | 给应用程序Container分配的最小内存,默认值:1024 |
yarn.scheduler.maximum-allocation-mb | 给应用程序Container分配的最大内存,默认值:8192 |
yarn.scheduler.minimum-allocation-vcores | 每个Container申请的最小CPU核数,默认值:1 |
yarn.scheduler.maximum-allocation-vcores | 每个Container申请的最大CPU核数,默认值:32 |
yarn.nodemanager.resource.memory-mb | 给Containers分配的最大物理内存,默认值:8192 |
(3)Shuffle性能优化的关键参数,应在YARN启动之前就配置好(mapred-default.xml)
配置参数 | 参数说明 |
---|---|
mapreduce.task.io.sort.mb | Shuffle的环形缓冲区大小,默认100m |
mapreduce.map.sort.spill.percent | 环形缓冲区溢出的阈值,默认80% |
2.容错相关参数(MapReduce性能优化)
配置参数 | 参数说明 |
---|---|
mapreduce.map.maxattempts | 每个Map Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 |
mapreduce.reduce.maxattempts | 每个Reduce Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 |
mapreduce.task.timeout | Task超时时间,经常需要设置的一个参数,该参数表达的意思为:如果一个Task在一定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该Task处于Block状态,可能是卡住了,也许永远会卡住,为了防止因为用户程序永远Block住不退出,则强制设置了一个该超时时间(单位毫秒),默认是600000。如果你的程序对每条输入数据的处理时间过长(比如会访问数据库,通过网络拉取数据等),建议将该参数调大,该参数过小常出现的错误提示是“AttemptID:attempt_14267829456721_123456_m_000224_0 Timed out after 300 secsContainer killed by the ApplicationMaster.”。 |
9.3 HDFS小文件优化方法
9.3.1 HDFS小文件的弊端
HDFS上每个文件都要在NameNode上建立一个索引,这个索引的大小约为150byte,这样当小文件比较多的时候,就会产生很多的索引文件,一方面会大量占用NameNode的内存空间,另一方面就是索引文件过大使得索引速度变慢。
9.3.2 HDFS小文件解决方案
小文件的优化无非以下几种方式
(1)在数据采集的时候,就将小文件或小屁数据合成大文件再上传HDFS
(2)在业务处理之前,在HDFS上使用MapReduce程序对小文件进行合并
(3)在MapReduce处理时,可采用CombineTextInputFormat提高效率
1.Hadoop Archive
是一个高效地将小文件放入HDFS块中的文件存档工具,它能够将多个小文件打包成一个HAR文件,这样就减少了NameNode的内存使用。
2.Sequence File
Sequence File由一系列的二进制key/value组成,如果key为文件名,value为文件内容,则可以将大批小文件合并成一个大文件。
3.CombineFileInputFormat
CombineFileInputFormat是一种新的InputFormat,用于将多个文件合并成一个单独的Split,另外,它会考虑数据的存储位置。
4.开启JVM重用
开启uber模式,实现jvm重用,默认情况下,每个Task任务都需要启动一个jvm来运行,如果Task任务计算的数据量很小,我们可以让同一个Job的多个Task运行在一个JVM中,不必为每个Task都开启一个JVM
开启uber模式,在mapred-site.xml中添加如下配置
<!--开启uber模式-->
<property>
<name>mapreduce.job.ubertask.enable</name>
<value>true</value>
</property>
<!--uber模式中最大的mapTask数量,可向下修改-->
<property>
<name>mapreduce.job.ubertask.maxmaps</name>
<value>9</value>
</property>
<!--uber模式中最大的输入数据量,如果不配置,则使用dfs.blocksize的值,可以向下修改-->
<property>
<name>mapreduce.job.ubertask.maxbytes</name>
<value></value>
</property>
<!--uber模式中最大的reduce数量,可向下修改-->
<property>
<name>mapreduce.job.ubertask.maxreduces</name>
<value>1</value>
</property>
对于大量小文件Job,可以开启JVM重用会减少45%运行时间
JVM重用原理:一个Map运行在一个JVM上,开启重用的话,该Map在JVM上运行完毕后,JVM继续运行其他Map
具体设置:mapreduce.job.jvm.numtasks值在10-20之间(现在版本好像没有效果)
第十章 zookeeper
10.1 Zookeeper入门
10.1.1 概述
Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。
Zookeeper从设计模式角度来理解,是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生了变化,Zookeeper就负责通知已经在Zookeeper上注册的那些观察者做出相应的反应.
Zookeeper = 文件系统 + 通知机制
10.1.2 特点
10.1.3 数据结构
10.1.4 应用场景
提供的服务包括:统一命名服务,统一配置管理,统一集群管理,服务器节点动态上下线,软负载均衡等.
10.2 Zookeeper安装
10.2.1 本地模式安装部署
1)安装前准备
(1)安装jdk
(2)拷贝Zookeeper安装包到Linux系统下
(3)解压到指定目录
[atguigu@hadoop102 software]$ tar -zxvf zookeeper-3.5.7.tar.gz -C /opt/module/
2)配置修改
(1)将/opt/module/zookeeper-3.5.7/conf这个路径下的zoo_sample.cfg修改为zoo.cfg;
[atguigu@hadoop102 conf]$ mv zoo_sample.cfg zoo.cfg
(2)打开zoo.cfg文件,修改dataDir路径
[atguigu@hadoop102 zookeeper-3.5.7]$ vim zoo.cfg
修改如下内容
dataDir=/opt/module/zookeeper-3.5.7/zkData
(3)在/opt/module/zookeeper-3.5.7/zkData
[atguigu@hadoop102 zookeeper-3.5.7]$ mkdir zkData
3)操作Zookeeper
(1)启动Zookeeper
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh start
(2)查看进程是否启动
[atguigu@hadoop102 zookeeper-3.5.7]$ jps
4020 Jps
4001 QuorumPeerMain
(3)查看状态
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: standalone
(4)启动客户端
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkCli.sh
(5)退出客户端
[zk: localhost:2181(CONNECTED) 0] quit
(6)停止Zookeeper
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh stop
10.2.2 配置参数的解读
Zookeeper中的配置文件zoo.cfg中参数含义解读如下:
1)tickTime =2000:通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)
2)initLimit =10:LF初始通信时限
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
3)syncLimit =5:LF同步通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
4)dataDir:数据文件目录+数据持久化路径
主要用于保存Zookeeper中的数据。
5)clientPort =2181:客户端连接端口
监听客户端连接的端口。
10.2.3 Zookeeper的四字命令【了解】
Zookeeper支持某些特定的四字命令(The Four Letter Words)与其进行交互,它们大多是查询命令,用来获取Zookeeper服务的当前状态及相关信息,用户在客户端可以通过telnet
或nc 向Zookeeper提交相应的命令。
需要在Zookeeper的配置文件中加入如下配置:
4lw.commands.whitelist=*
Zookeeper常用四字命令主要如下:
ruok | 测试服务是否处于正确状态,如果确实如此,那么服务返回 imok ,否则不做任何响应。 |
---|---|
conf | 3.3.0版本引入的,打印出服务相关配置的详细信息 |
cons | 列出所有连接到这台服务器的客户端全部会话详细信息。包括 接收/发送的包数量,会话id,操作延迟、最后的操作执行等等信息 |
crst | 重置所有连接的连接和会话统计信息 |
dump | 列出那些比较重要的会话和临时节点。这个命令只能在leader节点上有用 |
envi | 打印出服务环境的详细信息 |
先通过 nc hadoop102 2181连上,输入ruok
[atguigu@hadoop102 zookeeper-3.5.7]$ nc hadoop102 2181
ruok
imok
如果没有nc命令 通过yun -y install nc 进行安装
10.3 Zookeeper内部原理
10.3.1 节点类型
10.3.2 Stat结构体
(1)czxid-创建节点的事务zxid
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
(2)ctime - znode被创建的毫秒数(从1970年开始)
(3)mzxid - znode最后更新的事务zxid
(4)mtime - znode最后修改的毫秒数(从1970年开始)
(5)pZxid-znode最后更新的子节点zxid
(6)cversion - znode子节点变化号,znode子节点修改次数
(7)dataversion - znode数据变化号
(8)aclVersion - znode访问控制列表的变化号
(9)ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。
(10)dataLength- znode的数据长度
(11)numChildren - znode子节点数量
10.3.3 监听器原理(面试重点)
10.3.4 Paxos算法(扩展)
Paxos算法是一种基于消息传递且具有高度容错特征的一致性算法
分布式系统中的节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing)。基于消息传递通信模型的分布式系统,不可避免的会发生以下错误:进程可能会慢、被杀死或者重启,消息可能会延迟、丢失、重复,在基础 Paxos 场景中,先不考虑可能出现消息篡改即拜占庭错误的情况。Paxos 算法解决的问题是在一个可能发生上述异常的分布式系统中如何就某个值达成一致,保证不论发生以上任何异常,都不会破坏决议的一致性。
Paxos算法流程中的每条消息描述如下:
(1)Prepare: Proposer生成全局唯一且递增的Proposal ID (可使用时间戳加Server ID),向所有Acceptors发送Prepare请求,这里无需携带提案内容,只携带Proposal ID即可。
(2)Promise: Acceptors收到Prepare请求后,做出“两个承诺,一个应答”。
两个承诺:
Ø 不再接受Proposal ID小于等于(注意:这里是<= )当前请求的Prepare请求。
Ø 不再接受Proposal ID小于(注意:这里是< )当前请求的Propose请求。
一个应答:
Ø 不违背以前做出的承诺下,回复已经Accept过的提案中Proposal ID最大的那个提案的Value和Proposal ID,没有则返回空值。
(3)Propose: Proposer 收到多数Acceptors的Promise应答后,从应答中选择Proposal ID最大的提案的Value,作为本次要发起的提案。如果所有应答的提案Value均为空值,则可以自己随意决定提案Value。然后携带当前Proposal ID,向所有Acceptors发送Propose请求。
(4)Accept: Acceptor收到Propose请求后,在不违背自己之前做出的承诺下,接受并持久化当前Proposal ID和提案Value。
(5)Learn: Proposer收到多数Acceptors的Accept后,决议形成,将形成的决议发送给所有Learners。
下面我们针对上述描述做三种情况的推演举例:为了简化流程,我们这里不设置Learner。
Paxos算法缺陷:在网络复杂的情况下,一个应用Paxos算法的分布式系统,可能很久无法收敛,甚至陷入活锁的情况
造成这种情况的原因是系统中有一个以上的Proposer,多个Proposers相互争夺Acceptors,造成迟迟无法达成一致的情况。针对这种情况,一种改进的Paxos算法被提出:从系统中选出一个节点作为Leader,只有Leader能够发起提案。这样,一次Paxos流程中只有一个Proposer,不会出现活锁的情况,此时只会出现例子中第一种情况。
10.3.5 选举机制(面试重点)
1)半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
(2)Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
(3)以一个简单的例子来说明整个选举的过程。
假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么。
第一种情况
Zookeeper的选举机制
(1)服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;
(2)服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ID比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING
(3)服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
(4)服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;
(5)服务器5启动,同4一样当小弟。
第二种情况
如果所有的机器都是同时启动的,那么就没有先后顺序之分,就不会出现先当上领导的情况,会是最大id的直接当选
但是如果leader发生故障,会用zxid进行选举,大的变成领导者,但是一般zxid都是一样的
10.3.6 写数据流程
10.4 Zookeeper实战(开发重点)
10.4.1 分布式安装部署
1 集群规划
在hadoop102、Hadoop103和hadoop104三个节点上部署Zookeeper
2 解压安装
在hadoop102解压Zookeeper安装包到/opt/module/目录下
[atguigu@hadoop102 software]$ tar -zxvf zookeeper-3.5.7.tar.gz -C /opt/module/
3 配置服务器编号
(1)在/opt/module/zookeeper-3.5.7/这个目录下创建zkData
[atguigu@hadoop102 zookeeper-3.5.7]$ mkdir -p zkData
(2)在/opt/module/zookeeper-3.5.7/zkData目录下创建一个myid的文件
[atguigu@hadoop102 zkData]$ touch myid
(3)编辑myid文件
[atguigu@hadoop102 zkData]$ vi myid 2
4 配置zoo.cfg文件
(1)重命名/opt/module/zookeeper-3.5.7/conf这个目录下的zoo_sample.cfg为zoo.cfg
[atguigu@hadoop102 conf]$ mv zoo_sample.cfg zoo.cfg
(2)打开zoo.cfg文件
[atguigu@hadoop102 conf]$ vim zoo.cfg
在文件中修改数据存储路径配置
dataDir=/opt/module/zookeeper-3.5.7/zkData
增加如下配置
#######################cluster##########################
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
server.4=hadoop104:2888:3888
配置参数解读
server.A=B:C:D
A是一个数字,表示这个是第几号服务器:
集群模式下配置一个文件myid,这个文件在dataDir目录下,这个文件里面有一个数据就是A的值,Zookeeper启动时读取此文件,拿到里面的数据与zoo.cfg里面的配置信息比较从而判断到底是哪个server
B是这个服务器的地址
C是这个服务器Follower与集群中的Leader服务器交换信息的端口
D是万一集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口
5 同步所有集群,并修改集群中的myid文件
6 集群操作
(1)分别启动Zookeeper
[atguigu@hadoop102 zookeeper-3.5.7]$ bin/zkServer.sh start
[atguigu@hadoop103 zookeeper-3.5.7]$ bin/zkServer.sh start
[atguigu@hadoop104 zookeeper-3.5.7]$ bin/zkServer.sh start
(2)查看状态
[atguigu@hadoop102 zookeeper-3.5.7]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: follower
[atguigu@hadoop103 zookeeper-3.5.7]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: leader
[atguigu@hadoop104 zookeeper-3.4.5]# bin/zkServer.sh status
JMX enabled by default
Using config: /opt/module/zookeeper-3.5.7/bin/../conf/zoo.cfg
Mode: follower
10.4.2 建立zk脚本
#!/bin/bash
if [ $# == 0 ]
then
echo "请输入需要执行的命令"
exit;
fi
case $1 in
"start")
xcall /opt/module/zookeeper-3.5.7/bin/zkServer.sh start
;;
"stop")
xcall /opt/module/zookeeper-3.5.7/bin/zkServer.sh stop
;;
"status")
xcall /opt/module/zookeeper-3.5.7/bin/zkServer.sh status
;;
*)
echo "参数错误,参数为start/stop"
;;
esac
10.4.3 客户端命令行操作
命令基本语法 | 功能描述 |
---|---|
help | 显示所有操作命令 |
ls path | 使用 ls 命令来查看当前znode的子节点 [可监听] -w 监听子节点变化 -s 附加次级信息 |
create | 普通创建 -s 含有序列 -e 临时(重启或者超时消失) |
get path | 获得节点的值 [可监听] -w 监听节点内容变化 -s 附加次级信息 |
set | 设置节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
deleteall | 递归删除节点 |
1 启动客户端
[atguigu@hadoop103 zookeeper-3.5.7]$ bin/zkCli.sh
2 显示所有操作命令
[zk: localhost:2181(CONNECTED) 1] help
3 查看当前znode中所有包含的内容
[zk: localhost:2181(CONNECTED) 1] ls /
[zookeeper]
4 查看当前节点详细数据
[zk: localhost:2181(CONNECTED) 2] ls -s /
'ls2' has been deprecated. Please use 'ls [-s] path' instead.
[zookeeper]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x0
cversion = -1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 1
5 分别创建2个普通节点
[zk: localhost:2181(CONNECTED) 3] create /sanguo "jinlian"
Created /sanguo
[zk: localhost:2181(CONNECTED) 4] create /sanguo/shuguo "liubei"
Created /sanguo/shuguo
6 获得节点的值
[zk: localhost:2181(CONNECTED) 5] get /sanguo
jinlian
cZxid = 0x100000003
ctime = Wed Aug 29 00:03:23 CST 2018
mZxid = 0x100000003
mtime = Wed Aug 29 00:03:23 CST 2018
pZxid = 0x100000004
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 1
[zk: localhost:2181(CONNECTED) 6]
[zk: localhost:2181(CONNECTED) 6] get /sanguo/shuguo
liubei
cZxid = 0x100000004
ctime = Wed Aug 29 00:04:35 CST 2018
mZxid = 0x100000004
mtime = Wed Aug 29 00:04:35 CST 2018
pZxid = 0x100000004
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
7 创建短暂节点
zk: localhost:2181(CONNECTED) 34] create -e /sanguo/woshi "小迪迪"
Created /sanguo/woshi
可以看到woshi
[zk: localhost:2181(CONNECTED) 40] ls /sanguo
[shuguo, wosd0000000002, woshi]
退出当前客户端然后重启客户端,发现没有了
[zk: localhost:2181(CONNECTED) 1] ls /sanguo
[shuguo, wosd0000000002]
8 创建带序号的节点
[zk: localhost:2181(CONNECTED) 3] create -s /sanguo/weiguo
Created /sanguo/weiguo0000000003
如果原来没有节点,序号从0开始递增,如果当中有节点,就从该节点个数后面递增
比如:刚开始没有节点,创建一个带序号的节点,变成00000000,然后插入一个不带序号的节点,在创建一个带序号的节点的时候,变成了000000002了,如果刚开始就是有节点的化,就从该节点的父节点的子节点数量来开始计数
9 修改节点的数据值
[zk: localhost:2181(CONNECTED) 8] get /sanguo/shuguo
liubei
[zk: localhost:2181(CONNECTED) 9] set /sanguo/shuguo "marsxiaodidi"
[zk: localhost:2181(CONNECTED) 10] get /sanguo/shuguo
marsxiaodidi
10 节点的值的变化监听
(1)在hadoop104主机上注册监听/sanguo/shuguo节点数据变化
[zk: localhost:2181(CONNECTED) 11] get -w /sanguo/shuguo
marsxiaodidi
(2)在hadoop103主机上修改/sanguo/shuguo节点的数据
[zk: localhost:2181(CONNECTED) 2] set /sanguo/shuguo "帅"
(3)观察hadoop104主机收到数据变化的监听
[zk: localhost:2181(CONNECTED) 12]
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo/shuguo
11 节点的子节点变化监听(路径变化)
(1)在hadoop104主机上注册监听/sanguo节点的子节点变化
[zk: localhost:2181(CONNECTED) 12] ls -w /sanguo
[shuguo, weiguo0000000003, wosd0000000002]
(2)在hadoop103主机/sanguo节点上创建子节点
[zk: localhost:2181(CONNECTED) 3] create /sanguo/marxiao "帅帅帅"
Created /sanguo/marxiao
(3)观察hadoop104主机收到子节点变化的监听
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo
12 删除节点
[zk: localhost:2181(CONNECTED) 14] ls /sanguo
[marxiao, shuguo, weiguo0000000003, wosd0000000002]
[zk: localhost:2181(CONNECTED) 15] ls /sanguo/shuguo
[]
[zk: localhost:2181(CONNECTED) 16] delete /sanguo/shuguo
[zk: localhost:2181(CONNECTED) 17] ls /sanguo
[marxiao, weiguo0000000003, wosd0000000002]
13 递归删除节点
[zk: localhost:2181(CONNECTED) 18] ls /
[saguo, sanguo, zookeeper]
[zk: localhost:2181(CONNECTED) 19] deleteall /sanguo
[zk: localhost:2181(CONNECTED) 20] ls /
[saguo, zookeeper]
[zk: localhost:2181(CONNECTED) 21]
14 查看节点状态
[zk: localhost:2181(CONNECTED) 23] stat /saguo
cZxid = 0x300000005
ctime = Mon Dec 07 19:24:52 CST 2020
mZxid = 0x300000005
mtime = Mon Dec 07 19:24:52 CST 2020
pZxid = 0x300000005
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0
10.4.4 API应用
1 IDEA环境搭建
添加pom文件
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
拷贝log4j.properties文件到项目根目录
需要在项目的src/main/resources目录下,新建一个文件,命名为"log4j.properties",在文件中填入
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
2 创建ZooKeeper客户端
public class MyTest {
ZooKeeper zooKeeper;
@Before
public void before() throws IOException {
//创建一个zookeeper客户端
String str = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
//minSessionTimeout=4000 //最小超时时间
//maxSessionTimeout=40000 //最大超时时间
//如果不在这个区间,就用最大最小超时时间
//一般来说设置多少无所谓,反正都能连上
int sessionTimeout = 10000;
zooKeeper = new ZooKeeper(str, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
}
});
}
@After
public void after() throws InterruptedException {
zooKeeper.close();
}
3 创建子节点
@Test
public void create() throws KeeperException, InterruptedException {
// 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
/* PERSISTENT,(持久的)
PERSISTENT_SEQUENTIAL,(持久可连续的)
EPHEMERAL,(短暂的)
EPHEMERAL_SEQUENTIAL,(短暂可持续的)
*/
String s = zooKeeper.create("/gege2", "marxiaodidi".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(s);
}
4 获取子节点并监听节点变化
@Test
public void Test01() throws IOException, KeeperException, InterruptedException {
//watch代表是否要监听,如果是true就用默认的监听,也可以传入一个使用抽象方法
List<String> children = zooKeeper.getChildren("/", (watchedEvent)->
System.out.println("我最帅"));
for (String child : children) {
System.out.println(child);
}
//让main线程终止结束,让监听线程监听变化
Thread.sleep(Long.MAX_VALUE);
}
5 获取子节点数据,并判断Znode是否存在
@Test
public void getChildrenData() throws KeeperException, InterruptedException {
Stat exists = zooKeeper.exists("/gege1", false);
if (exists == null) {
System.out.println("不存在");
return;
}
byte[] data = zooKeeper.getData("/gege1", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.getPath());
}
}, exists);
System.out.println(new String(data));
//注意版本号一定要和stat里面的版本号保持一致,用了乐观锁,否则无法进行修改
zooKeeper.setData("/gege1", "我是人".getBytes(), exists.getVersion());
Thread.sleep(Long.MAX_VALUE);
6 删除节点
@Test
public void deleteNode() throws KeeperException, InterruptedException {
Stat exists = zooKeeper.exists("/mar", false);
zooKeeper.delete("/mar", exists.getVersion());
}
10.4.5 监听服务器节点动态上下线案例(扩展)
server
package com.atguigu.zookeeper;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
public class server {
private static ZooKeeper zooKeeper =null;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
//进行初始化给zookeeper赋值
init();
//新建一个server就创建一个节点
//判断是否有server根节点,如果没有创建一个永久节点
// /server/server1 "hadoop102"
zooKeeper.create("/server/"+args[0],args[1].getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println(args[0]+"节点创建成功");
Thread.sleep(Long.MAX_VALUE);
}
//进行zookeeper的初始化,如果没有/server就创建server
private static void init() throws IOException, KeeperException, InterruptedException {
String str = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
Integer i = 10000;
zooKeeper = new ZooKeeper(str, i, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
Stat exists = zooKeeper.exists("/server", false);
if (exists == null) {
zooKeeper.create("/server", "server".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
}
client
package com.atguigu.zookeeper;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class client {
private static ZooKeeper zooKeeper = null;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
init();
monitorServer();
Thread.sleep(Long.MAX_VALUE);
}
private static void monitorServer() throws KeeperException, InterruptedException {
//进行递归,通过递归重新创建一个process对象,防止之只能监听一次的情况
List<String> children = zooKeeper.getChildren("/server", new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
monitorServer();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
ArrayList<String> arrayLists = new ArrayList<>();
for (String child : children) {
byte[] data = zooKeeper.getData("/server/"+child, false, null);
arrayLists.add(new String(data));
}
System.out.println(arrayLists);
}
private static void init() throws IOException, KeeperException, InterruptedException {
String str = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
Integer i = 10000;
zooKeeper = new ZooKeeper(str, i, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
Stat exists = zooKeeper.exists("/server", false);
if (exists == null) {
zooKeeper.create("/server", "server".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
}
第十一章 HA
11.1 HA概述
1)所谓HA(High Availablity),即高可用(7*24小时不中断服务)。
2)实现高可用最关键的策略是消除单点故障。HA严格来说应该分成各个组件的HA机制:HDFS的HA和YARN的HA。
3)Hadoop2.0之前,在HDFS集群中NameNode存在单点故障SPOF(Single Points Of Failure)。
4)NameNode主要在以下两个方面影响HDFS集群
NameNode机器发生意外,如宕机,集群将无法使用,直到管理员重启
NameNode机器需要升级,包括软件、硬件升级,此时集群也将无法使用
HDFS HA功能通过配置Active/Standby两个NameNodes实现在集群中对NameNode的热备来解决上述问题。如果出现故障,如机器崩溃或机器需要升级维护,这时可通过此种方式将NameNode很快的切换到另外一台机器。
11.2 HDFS-HA工作机制
通过多NameNode消除单点故障
11.3 HDFS-HA手动故障转移
11.3.1 工作要点
1.元数据管理方式需要改变
内存中各自保存一份元数据;
Edits日志只有Active状态的NameNode节点可以做写操作;
两个NameNode都可以读取Edits;
共享的Edits放在一个共享存储中管理(qjournal和NFS两个主流实现)
2.必须保证多NameNode之间能够ssh无密码登录
3.隔离(Fence),即同一时刻仅仅有一个NameNode对外提供服务
11.3.2 配置HDFS-HA集群
1.在opt目录下创建一个ha文件夹
[atguigu@hadoop202 opt]$ mkdir ha
2 .将/opt/module/下的hadoop-3.1.3 拷贝到/opt/ha目录下
[atguigu@hadoop202 opt] cp -r hadoop-3.1.3 ha
3.配置core-site.xml
<configuration>
<!-- 把多个NameNode的地址组装成一个集群mycluster -->
<property>
<name>fs.defaultFS</name>
<value>hdfs://mycluster</value>
</property>
<!-- 指定hadoop运行时产生文件的存储目录 -->
<property>
<name>hadoop.tmp.dir</name>
<value>/opt/module/ha/hadoop-3.1.3/data/tmp</value>
</property>
<!-- 声明journalnode服务器存储目录-->
<property>
<name>dfs.journalnode.edits.dir</name>
<value>file://${hadoop.tmp.dir}/jn</value>
</property>
<!--兼容性配置,现在可以不配置-->
<property>
<name>hadoop.proxyuser.atguigu.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.atguigu.groups</name>
<value>*</value>
</property>
<property>
<name>hadoop.http.staticuser.user</name>
<value>atguigu</value>
</property>
</configuration>
-
配置hdfs-site.xml
<configuration> <!-- 完全分布式集群名称 --> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <!-- NameNode数据存储目录 --> <property> <name>dfs.namenode.name.dir</name> <value>file://${hadoop.tmp.dir}/name</value> </property> <!-- DataNode数据存储目录 --> <property> <name>dfs.datanode.data.dir</name> <value>file://${hadoop.tmp.dir}/data</value> </property> <!-- 集群中NameNode节点都有哪些 --> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2,nn3</value> </property> <!-- nn1的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>hadoop102:9000</value> </property> <!-- nn2的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>hadoop103:9000</value> </property> <!-- nn3的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn3</name> <value>hadoop104:9000</value> </property> <!-- nn1的http通信地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>hadoop102:9870</value> </property> <!-- nn2的http通信地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>hadoop103:9870</value> </property> <!-- nn3的http通信地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn3</name> <value>hadoop104:9870</value> </property> <!-- 指定NameNode元数据在JournalNode上的存放位置 --> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://hadoop102:8485;hadoop103:8485;hadoop104:8485/mycluster</value> </property> <!-- 配置隔离机制,即同一时刻只能有一台服务器对外响应 --> <property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <!-- 使用隔离机制时需要ssh无秘钥登录--> <property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/home/atguigu/.ssh/id_rsa</value> </property> <!-- 访问代理类:client用于确定哪个NameNode为Active --> <property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> </configuration>
11.3.3 启动HDFS-HA集群
0.重新配置hadoop的环境变量
sudo vim /etc/profile.d/my_env.sh
改HADOOP环境为
export HADOOP_HOME=/opt/module/ha/hadoop-3.1.3
并且source
source /etc/profile
1.删除所有集群/tmp下的所有文件
xcall sudo rm -rvf /tmp/*
2.在各个JournalNode节点上,输入以下命令启动journalnode服务
hdfs --daemon start journalnode
3.在[nn1]上就,对其进行格式化,并启动(注意格式化的时候一定没有要求按Y/N的请求)
hdfs namenode -format
hdfs --daemon start namenode
4.在[nn2] 和 [nn3]上,同步nn1的元数据信息
hdfs namenode -bootstrapStandby
5.启动[nn2]和[nn3]
hdfs --daemon start namenode
6.启动所有datanode
hdfs --daemon start datanode
7.将[nn1]切换为Active
hdfs haadmin -transitionToActive nn1
8 .是否Active
hdfs haadmin -getServiceState nn1
9.kill 掉Active 的NameNode,进行手动故障转移
hdfs haadmin -transitionToActive nn2
通过自动连接
hdfs haadmin -transitionToActive nn2
发现2020-12-14 22:05:19,957 INFO ipc.Client: Retrying connect to server: hadoop102/192.168.241.102:9000. Already tried 0 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=1, sleepTime=1000 MILLISECONDS) Unexpected error occurred Call From hadoop102/192.168.241.102 to hadoop102:9000 failed on connection exception: java.net.ConnectException: 拒绝连接; For more details see: http://wiki.apache.org/hadoop/ConnectionRefused Usage: haadmin [-ns ] [-transitionToActive [–forceactive] ]
错误
只能先把nn1抢救回来,发现在standby状态,然后才可以配置nn2为active状态
11.4 配置HDFS-HA自动故障转移
11.4.1 工作要点
前面学习了使用命令hdfs haadmin手动进行故障转移,在该模式下,即使现役NameNode已经失效,系统也不会自动从现役NameNode转移到待机NameNode,下面学习如何配置部署HA自动进行故障转移。自动故障转移为HDFS部署增加了两个新组件:ZooKeeper和ZKFailoverController(ZKFC)进程。ZooKeeper是维护少量协调数据,通知客户端这些数据的改变和监视客户端故障的高可用服务。HA的自动故障转移依赖于ZooKeeper的以下功能:
1)故障检测:集群中的每个NameNode在ZooKeeper中维护了一个持久会话,如果机器崩溃,ZooKeeper中的会话将终止,ZooKeeper通知另一个NameNode需要触发故障转移。
2)现役NameNode选择:ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。如果目前现役NameNode崩溃,另一个节点可能从ZooKeeper获得特殊的排外锁以表明它应该成为现役NameNode。
ZKFC是自动故障转移中的另一个新组件,是ZooKeeper的客户端,也监视和管理NameNode的状态。每个运行NameNode的主机也运行了一个ZKFC进程,ZKFC负责:
1)健康监测:ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode,只要该NameNode及时地回复健康状态,ZKFC认为该节点是健康的。如果该节点崩溃,冻结或进入不健康状态,健康监测器标识该节点为非健康的。
2)ZooKeeper会话管理:当本地NameNode是健康的,ZKFC保持一个在ZooKeeper中打开的会话。如果本地NameNode处于active状态,ZKFC也保持一个特殊的znode锁,该锁使用了ZooKeeper对短暂节点的支持,如果会话终止,锁节点将自动删除。
3)基于ZooKeeper的选择:如果本地NameNode是健康的,且ZKFC发现没有其它的节点当前持有znode锁,它将为自己获取该锁。如果成功,则它已经赢得了选择,并负责运行故障转移进程以使它的本地NameNode为Active。故障转移进程与前面描述的手动故障转移相似,首先如果必要保护之前的现役NameNode,然后本地NameNode转换为Active状态。
11.4.2 规划集群
hadoop102 | hadoop103 | hadoop104 |
---|---|---|
NameNode | NameNode | NameNode |
ZKFC | ZKFC | ZKFC |
JournalNode | JournalNode | JournalNode |
DataNode | DataNode | DataNode |
ZK | ZK | ZK |
ResourceManager | ||
NodeManager | NodeManager | NodeManager |
11.4.3 配置Zookeeper集
1 .集群规划
在hadoop102、hadoop102 和 hadoop104三个节点上部署Zookeeper
2.分别启动zookeeper
[atguigu@hadoop102 zookeeper-3.5.7]$ xcall bin/zkServer.sh start
3.查看状态
[atguigu@hadoop102 zookeeper-3.5.7]$ xcall bin/zkServer.sh status
11.4.4 配置HDFS-HA自动故障转移
1.具体配置
(1)在hdfs-site.xml中添加
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>
(2)在core-site.xml文件中添加
<property>
<name>ha.zookeeper.quorum</name>
<value>hadoop102:2181,hadoop103:2181,hadoop104:2181</value>
</property>
注意配置好后记得分发
2.启动
(1)关闭所有HDFS服务:
stop-dfs.sh
(2)启动Zookeeper集群,在每个Zookeeper节点执行
zk start
(3)初始化HA在Zookeeper中状态:
hdfs zkfc -formatZK
(4)启动HDFS服务:
start-dfs.sh
启动成功情况
———————-hadoop102——————— 7184 QuorumPeerMain 11523 Jps 10501 JournalNode 10726 DFSZKFailoverController 10248 DataNode 10106 NameNode ———————-hadoop103——————— 7298 NameNode 7396 DataNode 8006 Jps 7511 JournalNode 5592 QuorumPeerMain 7658 DFSZKFailoverController ———————-hadoop104——————— 7632 DFSZKFailoverController 7492 JournalNode 5574 QuorumPeerMain 7275 NameNode 7375 DataNode 7999 Jps
3.web端查看,一个Active的NameNode,两个Standby的NameNode
4.验证
(1)将Active NameNode进程kill,实现自动故障转移
kill -9 namenode的进程id
11.5 YARN-HA配置
11.5.1 YARN-HA工作机制
11.5.2 配置YARN-HA集群
1.规划集群
hadoop102 | hadoop103 | hadoop104 |
---|---|---|
NameNode | NameNode | NameNode |
JournalNode | JournalNode | JournalNode |
ZKFC | ZKFC | ZKFC |
DataNode | DataNode | DataNode |
ZK | ZK | ZK |
ResourceManager | ResourceManager | |
NodeManager | NodeManager | NodeManager |
2.具体配置
(1)yarn-site.xml
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<!--启用resourcemanager ha-->
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<!--声明两台resourcemanager的地址-->
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>cluster-yarn1</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>hadoop102</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>hadoop103</value>
</property>
<!--指定zookeeper集群的地址-->
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>hadoop102:2181,hadoop103:2181,hadoop104:2181</value>
</property>
<!--启用自动恢复-->
<property>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<!--指定resourcemanager的状态信息存储在zookeeper集群-->
<property>
<name>yarn.resourcemanager.store.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
</property>
</configuration>
记得分发
3.启动HDFS
start-dfs.sh
4.启动YARN
start-yarn.sh
查看服务状态
yarn rmadmin -getServiceState rm1
web端查看YARN的状态
11.6 HDFS Federation架构设计
\1. NameNode架构的局限性
(1)Namespace(命名空间)的限制
由于NameNode在内存中存储所有的元数据(metadata),因此单个NameNode所能存储的对象(文件+块)数目受到NameNode所在JVM的heap size的限制。50G的heap能够存储20亿(200million)个对象,这20亿个对象支持4000个DataNode,12PB的存储(假设文件平均大小为40MB)。随着数据的飞速增长,存储的需求也随之增长。单个DataNode从4T增长到36T,集群的尺寸增长到8000个DataNode。存储的需求从12PB增长到大于100PB。
(2)隔离问题
由于HDFS仅有一个NameNode,无法隔离各个程序,因此HDFS上的一个实验程序就很有可能影响整个HDFS上运行的程序。
(3)性能的瓶颈
由于是单个NameNode的HDFS架构,因此整个HDFS文件系统的吞吐量受限于单个NameNode的吞吐量。
\2. HDFS Federation架构设计,如图1-3所示
能不能有多个NameNode
表1-3
NameNode | NameNode | NameNode |
---|---|---|
元数据 | 元数据 | 元数据 |
Log | machine | 电商数据/话单数据 |
图1-3 HDFS Federation架构设计
\3. HDFS Federation应用思考
不同应用可以使用不同NameNode进行数据管理
图片业务、爬虫业务、日志审计业务
Hadoop生态系统中,不同的框架使用不同的NameNode进行管理NameSpace。(隔离性)