------------------------------------hdfs详解------------------------------------------
1.hdfs详解的设计思想
1.1 hdfs特性:
超大文件,通常有几百M甚至几百TB的大小。
hdfs的构建思路是:一次写入、多次读取是最高的访问模式。数据由数据源然后长时间
在此集群上进行各类分析。读取整个数据集的延迟比读取一条记录延迟更重要
低延迟:要求低时间延迟,hdfs是为了高数据吞吐量应用优化的。
写入操作:文件数据一次写入,不再修改
1.2 HDFS为什么要使用块(block)
hdfs块:hdfs块比磁盘块大,目的是为了最小化寻址的开销,一次map任务处理一个块
抽取块优势:一个文件的大小可以大于磁盘容量,简化存储子系统的设计
1.3 namenode和datanode
namenode:a)管理文件系统的命名空间,维护者文件系统的所有文件和目录。这些信息存储
在命名空间镜像文件和编辑日志文件。b)namenode同时记录着每个块所在的
数据节点信息,这些信息在系统启动时由数据节点重建
datanode:是系统的工作节点,根据客户端需要存储并检索数据块,向namenode发送心跳
1.4 hadoop容错机制
如果namenode宕机,整个文件系统将无法使用,datanode块无法重建文件。
第一种:备份文件元数据 第二种:secondaryNameNode,namenode宕机数据难免丢失
因此,备份文件很有必要
2.命令行接口
2.1 默认文件系统:
a)fs.defaut.name ,设置为 hdfs://localhost/
文件系统由URI指定,hdfs的守护程序通过该属性确定hdfs nanmenode的主机及端口。
locahost上8020运行namenode,客户端就可以知道在哪里可以连接到它
b)dfs.replication,设置为1的话就不会按默认复制为3块。为分布式不会给出警告
2.2 文件系统的操作命令
fs -help 获取帮助
fs -copyFromLocal input/doc/data.txt hdfs://local/user/tom/data.txt 复制文件
fs -mkdir books 创建目录
fs -ls 查看文件列表
3.hadoop文件系统
3.1 hadoop.fs.FileSystem 是hadoop文件系统的接口,实现类有:
Local fs.LocalFileSystem 使用了客户端和本地磁盘文件系统
HDFS hdfs.DistributeFileSystem hadoop文件系统
HFTP hdfs.HsftpFileSyste http上访问hdfs
FTP fs.ftp.FtpFileSystem ftp服务器支持的文件系统
S3 fs.s3native.NativeS3FileSystem Amazon支持的文件系统
3.2 从Hadoop URL中读取数据
1)使用java.net.URL访问
InputStream in = new URL("hdfs://host/path").openStream();
IOUtils.copyBytes(in,System.out,4096,false);
IOUtils.closeStream(in);
2)通过 FileSystem API 读取数据
FileSystem的两种静态工厂方法:
public static FileSystem get(Configuration conf) throws IOException
public static FileSystem get(URI uri,Configuration conf) throws IOException
调用open()获取文本输入流:
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri),conf);
in = fs.open(new Path(uri));
IOUtils.copyBytes(in,System.out,4096,false);
IOUtils.closeStream(in);
FSDataInputStream 中 openI()返回FSDataInputStram,可从任意位置读取数据
public class FSDataInputStream extends DataInputStream
implements Seelable,PositionReadable{
//implements method
}
3.3 通过FileSystem写入数据
最简单普通的方法
public FSDataOutputStream create(Path f) throws IOException
追加至文件末尾
public FSDataOutputStream append(path f) throws IOException
示例:将本地文件复制到Hadoo文件系统
String localSrc=args[0];
String dst= args[1];
InputStream in = new BufferedInputStream(new FileInputStream(localSrc));
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URL.create(dst),conf);
OutputStream out = fs.create(new Path(dst),new Progressable(){
public void progress(){
System.out.print(".");
}
});
IOUtils.copyBytes(in,out,4096,true);
3.4 查询文件系统
FileSystem.getFileStatus()获取文件或目录的FileStatus对象
列出文件:
public FileStatus[] listStatus(Path f) throws IOException
使用FileUtil获取文件路径
Path[] listedPaths = FileUtil.stat2Paths(status);
3.5 文件通配符
/* /2007/2008
/*/* /2007/12/2008/01
/*/12/* /2006/12/30/2007/12/31
/200? /2007/2008
/200[78] /2007/2008
/2007[7-8] /2007/2008
/200[^01234569] /2007/2008
/*/*{31,01} /2007/12/31/2008/01/01
/*/{12/31,01/01} /2007/12/31/2008/01/01
使用方法:implements PathFileter
3.6 FileSystem删除数据
public boolean delete(Path f,boolean recursive) ,为true文件才会被删除
3.7 文件的读取
客户端读取hdfs中的数据图
1)DistributedFileSystem:通过使用RPC来调用namenode,以确定起始块的位置。客户端首先
读取最近的datanode节点。该类返回一个FSDataInputStream对象给客户端读取。
FSDataInputStream转而封装DFSInputStream对象,它管理着datanode和namenode的I/O
2)客户端对这个流调用read()方法,到达块的末端时,DFSInputStream会关闭与该该datanode连接
如果DFSInputStream与datanode通信时遇到错误,便会尝试从最近的datanode读取数据。同时
记录该datanode不再访问。还会校验确认从datanode发来的数据是否完整,如果损坏,则
在从其他datanode读取块之前通知namenode。
3.8 文件写入分析
客户端写入hdfs数据图
1)客户端通过对DistributedFileSystem对象调用create()创建文件。
2)dfs对象对namenode创建一个RPC调用,在文件系统中创建一个新文件。此时还没有相应数据块
3)namenode执行各种检查以确保该文件不存在(防止文件覆盖),且客户端有创建该文件权限
如果检查均通过,namenode创建信文件记录一条记录,否则返回IOException异常
dfs对象向客户端返回FSDataOutputStream对象,客户端就可以写入。它封装了DFSOutputStream
4)DFSOutputStream 将写入的数据分成一个个数据包,并写入内部序列。副本存放策略:
第一个:随机的不太忙的节点,第二个:与第一个不同且与第一个同一机架,第三个:不同机架
客户端完成数据的写入后,会调用close()方法。该操作将剩余数据包写入datanode管线中
3.9 一致模型
文件系统的一致性模型描述了对文件读写的数据可见性,如果创建文件后希望在命名空间中可见:
Path p = new Paht("p");
OutputStream out = fs.create(p);
out.write("content".getBytes("utf-8"));
out.flush();
assertThat(fs.getFileStatus(p).getLen(),is(0L));
写入的数据超过一个块后,新的reader才能看见。其他块无法看见当前正在写入的块
最后可调用out.sync(),成功返回后对所有新reader而已,hdfs能保证写入的均数据可见
重要性:如果不调用sync(),系统故障时可能会丢失一个数据块。尽管系统优化,但该方法仍然会
带来许多额外开销。这需要根据系统设计的需要进行权衡,设置调用sync()频率
3.10 hadoop存档
1)每个文件均按照块的方式存储,每个块的元数据在namenode内存中,因此hadoop存储小文件会非常
低效。大量的小文件会耗尽namenode中大部分内存
2)使用archive工具
hadoop存档通过archive工具根据一组文件创建而来。该存档工具运行一个MapReduce作业来并行
处理所有的出入文件。因此需要一个MapReduce集群运行和使用
hadoop archive -archiveName files.har /my/files /my
--------------------------------------hadoop I/O -------------------------------------
1.数据的完整性
1.1 数据量大到hadoop能够处理的极限时,数被损坏的概率还是很高的。
检查数据损坏:在数据第一次引入系统时,计算验证和(checksum)并在数据通过一个不可靠的
通道进行传世再次计算验证和。这样就能发现数据是否损坏
1.2 hdfs的数据完整性
1)hdfs会针对写入的所有数据计算验证和,并在读取数据时验证校验和。针对每个由io.bytes.per.checksum
指定字节的数据计算验证和。默认情况下为512个字节
2)客户端在读取datanode数据时也会验证校验和。每个datanode会保持验证和日志,所以指定
每个数据块的最后一次验证时间。客户端成功验证数据块后会告诉datanode,datanode会更新日志
3)客户端检测到块损坏后,就向namenode报告已损坏的数据块及其正在读取操作的datanode,
最后才抛出ChecksumException。namenode标记损坏的块,然后复制数据块到期望值,再删除损坏块
2.LocalFileSystem
这个类继承自FileSystem,可以向其他文件系统加入检验和。
FIleSystem rawFs = ...
FileSystem checkSummedFs = new ChecksumFileSystem(rawFs);
底层文件系统称为源(raw)文件系统,可以使用ChecksumFileSystem实例getRawFileSystem()获取它
getCheckSumFile()可以获取任意一个文件的校验和文件路径
3.压缩
3.1 文件压缩好处:可以减少存储文件所需的磁盘空间;可以加速数据在网络和磁盘上的传输
压缩格式 工具 算法 文件扩展名 是否包含多个文件 是否可切分
deflate n/a deflate .deflate 否 否
Gzip gzip deflate .gz 否 否
bzip2 bzip2 bzip2 .bz2 否 是
LZO Lzop LZOP .lzo 否 否
3.2 创建一个.gz压缩文件 :gzip -l file
bzip2压缩率比gzip高,但压缩速度慢一些
3.3 codec
codec实现了一种压缩-解压缩算法。hadoop中,一个对CompressionCodec接口的实现代表一个codec
例如,GzipCodec包装了gzip的压缩和解压缩算法
createOutputStream(OutputStream out)对输出文件压缩
createInputStream(InutStream in) 读取解压后的数据
示例-压缩从表中输入读取的数据,然后将其写到标准输出:
public void static main(String args[]) throws Exception{
String codecClassname = args[0];
Class<?> codecClass = Class.forName(codecClassname);
Configuration conf = new Configuration();
CompressionCodec codec = (CompressionCodec)
ReflectionUtils.newInstance(codecClass,conf);
CompressionOutputStream out = codec.createOutputStream(System.out);
IOUtils.copyBytes(System.in,out,4096,false);
out.flush();
}
通过CompressionCodecFactory推断CompressionCodec
public staitc void main(String[] args) throws Exception {
String uri = args[0];
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri),conf);
Path inputPath = new Path(uri);
CompressionCodecFactory factory = new CompressionCodecFactory(conf);
CompressionCodec codec = factory.getCodec(inputPath);
if(codec == null){
System.err.println("no codec found for " + uri);
System.exit(1);
}
String outputUri =
CompressionCodecFactory.removeSuffix(Uri,codec.getDefaultExtension());
InputStream in = null;
OutputStream out = null;
try{
in = codec.createInputStream(fs.open(inputPath));
IOUtils.copyBytes(in,out,conf);
} finally {
IOUtils.closeStream(in);
IOUtils.closeStream(out);
}
}
一旦找到对应的codec,便去除文件扩展名形成的输出文件名,这是通过CompressionCodecFactory
对象的静态方法removeSuffix()来实现的。一个文件可以通过下面的程序压缩为名为file的文件:
hadoop FileDecompressor file.gz
为了性能,最好使用“原生”(native)类库实现压缩和解压。使用原生的gzip类库可以减少
大约一半的解压缩时间和大约10%的压缩时间
压缩代码库的实现
压缩格式 java实现 原生实现
DEFLATE 是 是
gzip 是 是
bzip2 是 否
LZO 否 是
java系统的java.library.path 属性知道原生代码库。bin文件中的hadoop脚本可以帮助设置
hadoop会根据自身的运行平淡搜索原生代码库,如果找到就会自动加载
3.4 CodecPool
如果使用的是原生代码库并且需要在应用中执行大量压缩和解压缩操作,可以使用CodecPool
它允许你反复使用压缩和解压缩,以分摊创建这些对象所涉及的开销
示例-使用压缩池对读取自标准输入的数据压缩,然后输出
public static void main(String[] args) throws Exception {
String codecClassname = args[0];
Class<?> codecClass = Class.forName(codecClassname);
Configuration conf = new Configuration();
CompressionCodec codec = (CompressionCodec)
ReflectionUtils.newInstance(codecClass,conf);
Compressor compressor = null;
try{
compressor = CodecPool.getCompressor(codec);
CompressionOutputStream out = codec.createOutputStream(System.out,compressor)
IOUtils.copyBytes(System.in,out,4096,false);
}finally {
CodecPool.returnCompressor(compressor);
}
}
4. 压缩和输入分片
4.1 输入分片的问题分析
1)如果输入的文件类型是gzip,mapreduce不会尝试切分gzip压缩文件,因为他知道输入的是gzip
文件是不支持切分的。这样就导致map任务书很少,作业的粒度就会很大,运行效率就会很低
如果使用的是LZO压缩,这个格式不支持数据读取和数据流同步。但是bzip2文件提供不同数据
块之间的同步标志,因而支持切分。
2)应该使用哪种压缩格式与具体应用相关,目的是为了让应用运行速度最快,尽可能降低数据存储
开销。通常需要为应用尝试不同策略,为应用构建一套测试基准,找到最佳压缩格式
4.2 在MapReduce中使用压缩
MapReduce会在读取文件时自动解压缩文件。要想对MapReduce作业的输出进行压缩操作,应在
作业配置过程中将mapred.output.compress属性设为true 和 mapred.output.compression.codec
属性设置为打算使用的压缩codec类名,
示例:最高气温作业所产生的输出进行压缩
public static void main(String[] args[]){
if(args.length!=2){
System.,println("Usage :MaxTempertureWithCompression <input path> <output path>")
System.exit(-1);
}
JobConf conf = new JobConf(MaxTempertureWithCompression.class);
conf.setJobName("MaxTempertureWithCompression");
FileInputFormat.addInputPath(conf,new Path(args[0]));
FileOutputFormat.setOutputPath(conf,new Path(args[1]));
conf.setOutputkeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setBoolean("mapred.output.compress.class");
conf.setClass("mapred.open.compression.codec",GzipCodec.class,CompressionCodec.class);
conf.setMapperClass(MaxTemptureMapper.class);
conf.setCombinerClass(MaxTemptureReducer.class);
Conf.setReducerClass(MaxTemptureReducer.class);
JobClient.runjob(conf);
}
5. 序列化
5.1 为什么要序列化
1)序列化是指将结构化对象转化为字节流,以便在网络上传输或写到磁盘进行永久存储。
反序列化是指将字节流转回结果话对象的逆过程。
2)hadoop中,多个节点上进程间的通信是通过RPC(远程过程调用)实现的。
RPC协议将消息序列化成二进制流后发送到远程节点,远程节点再反序列化为原始消息。
紧凑:紧凑的格式能够充分利用网络带宽
快速:进程通信形成了分布式的系统骨架,所以需要尽量减少序列化和反序列化的性能开销
可扩展:协议为了满足新的需求而不断变化,所以在控制客户端和服务器过程中,需要直接
引进相应的协议。例如需要新增参数,新服务器依然能接受客户端老格式消息
互操作:这种特定格式能支持不同语言学的客户端与服务器交互
6. Writable 接口
6.1 Writeable接口定义了两个方法:
void write(DataOutput out) throws IOException;//序列化
void readFields(DataInput in) throws IOException;//反序列化
6.2 WritableComparable和comparabtor
1)IntWriteable:实现了WirteableComparable接口,该接口继承自Writable和Compareable接口
2)Comparator:对MapReduce来说,类型的比较是非常重要的,因为中间有个基于键的排序阶段
hadoop提供了一个优化接口Raw,它继承了Comparator接口,该接口允许直接比较数据流中的记录,
无须反序列化为对象,避免了额外开销。
3)writableComparator是对继承自WriteabeComparable类的RawComparator通用实现
功能:第一,提供了对原始compare()方法的一个默认实现,可以在流中比较对象,并调用对象
compare()方法;第二,它充当的是RawCompare实例的工厂
COmparator.compare(w1,w2);
6.3 writable 类
java类型 Writab实现 序列化大小
boolean BooleanWritable 1
bytes ByteWritable 1
int IntWritable 4
VintWritable
float FloatWritable 4
long LongWritable 8
VlongWirtable
double DoubleWritable 8
String Text
null NullWritable
定长格式适合值域范围分布非常均匀的数值进行编码。,变长比较省空间
2)Text是针对UTF-8序列化的writable类。Text中find()相当于String中indexOf()
区别:String的长度是其所含char编码单元的个数,Text是其UTF-8编码的字节数
调用text.toString()转化为String,进行字符串操作
3)BytesWritable,对二进制数组的封装
BytesWritable b = new BytesWritable(new byte[]{3,5})
它的值可以通过set()方法进行修改,getBytes()返回字节数值长度
4)ObjectWritable和GenericWritable
例如SequenceFile中的值包含多个类型,就可以将值类型声明为ObjectWritable
如果封装的类型输了比较少并且能够提前知道,就可以使用静态类型数组,这是
GenericWritable类采取的方法,且可以在自己的子类中指定需要支持的类型
6.4 Writable集合类
1)hadoop.io中4个集合类ArrayWritable、TwoDArrayWritable、MapWritable和sortedWritable
ArrayWritable、TwoDArrayWritable是对数组和二维数组的实现,其集合中所有元素类型
必须是同一类型,如:
ArrayWritable writable = newArrayWritable(Text.class);
2)mapWritable和sortWritable分别实现了java.util.Map<Writable,writale>和
java.util/SortedMap<WritableCompable,Wirtable>。
数组经常与标准类型结合使用,而定制的Wriable类型也通常结合使用,但对非标准类型
则需要在包头中指明所使用的数组类型,以上两个结合类正用于此。
使用不同键和值类型的MapWriable:
MapWritable src = newMapWriable();
src.put(new IntWritable(1),new Text("cat"));
src.put(new VintWritable(2),new LongWritable(163));
MapWritable dest = new MapWritable();
WritableUtils.cloneInto(dest,src);
assertThat((Text) desc.get(newVInWritable(1),is(new Text("cat"))));
assertThat((LongWritable) dest.get(new VIntWritabe(2)),is(new LongWritale(163)));
还可以使用与MapWritable相似的观点构造一通用的ListWritable
7. 实现定制的Writable类型
7.1 有了定制的Writable类型就可以完全控制二进制表示和顺序排序。如果希望将结构调整的更好
,做法往往是新建一个Writable类,示例:p117