HDFS和MR主要针对大数据文件来设计,在小文件处理上效率低.解决方法是选择一个容器,将这些小文件包装起来,将整个文件作为一条记录,可以获取更高效率的储存和处理,避免多次打开关闭流耗费计算资源.hdfs提供了两种类型的容器 SequenceFile和MapFile
一、SequenceFile
SequenceFile的存储类似于Log文件,所不同的是Log File的每条记录的是纯文本数据,而SequenceFile的每条记录是可序列化的字符数组。
SequenceFile可通过如下API来完成新记录的添加操作:
fileWriter.append(key,value)
可以看到,每条记录以键值对的方式进行组织,但前提是Key和Value需具备序列化和反序列化的功能
hadoop预定义了一些Key Class和Value Class,他们直接或间接实现了Writable接口,满足了该功能,包括:
Text 等同于Java中的String
IntWritable 等同于Java中的Int
BooleanWritable 等同于Java中的Boolean
.
.
在存储结构上,SequenceFile主要由一个Header后跟多条Record组成,如图所示:
Header主要包含了Key classname,Value classname,存储压缩算法,用户自定义元数据等信息,此外,还包含了一些同步标识,用于快速定位到记录的边界。
每条Record以键值对的方式进行存储,用来表示它的字符数组可依次解析成:记录的长度、Key的长度、Key值和Value值,并且Value值的结构取决于该记录是否被压缩。
数据压缩有利于节省磁盘空间和加快网络传输,SeqeunceFile支持两种格式的数据压缩,分别是:record compression和block compression。
record compression如上图所示,是对每条记录的value进行压缩
block compression是将一连串的record组织到一起,统一压缩成一个block,如图所示:
block信息主要存储了:块所包含的记录数、每条记录Key长度的集合、每条记录Key值的集合、每条记录Value长度的集合和每条记录Value值的集合
注:每个block的大小是可通过io.seqfile.compress.blocksize属性来指定的
二、MapFile
MapFile是排序后的SequenceFile,通过观察其目录结构可以看到MapFile由两部分组成,分别是data和index。
index作为文件的数据索引,主要记录了每个Record的key值,以及该Record在文件中的偏移位置。在MapFile被访问的时候,索引文件会被加载到内存,通过索引映射关系可迅速定位到指定Record所在文件位置,因此,相对SequenceFile而言,MapFile的检索效率是高效的,缺点是会消耗一部分内存来存储index数据。
需注意的是,MapFile并不会把所有Record都记录到index中去,默认情况下每隔128条记录存储一个索引映射。当然,记录间隔可人为修改,通过MapFIle.Writer的setIndexInterval()方法,或修改io.map.index.interval属性;
另外,与SequenceFile不同的是,MapFile的KeyClass一定要实现WritableComparable接口,即Key值是可比较的。
总结:
SequenceFile文件是用来存储key-value数据的,但它并不保证这些存储的key-value是有序的,
而MapFile文件则可以看做是存储有序key-value的SequenceFile文件。
MapFile文件保证key-value的有序(基于key)是通过每一次写入key-value时的检查机制,这种检查机制其实很简单,就是保证当前正要写入的key-value与上一个刚写入的key-value符合设定的顺序,
但是,这种有序是由用户来保证的,一旦写入的key-value不符合key的非递减顺序,则会直接报错而不是自动的去对输入的key-value排序
Hadoop HDFS JAVA API
org.apache.hadoop.fs.FileSystem
是在分布式环境中访问和管理HDFS中的文件/目录的通用类,文件内容以多个大尺寸块(64M)的形式存储在datanode中,namenode中记录了这些块的信息和文件的元信息。FileSystem可以读或者按块的顺序流式的访问。FileSystem首先从NameNode中得到块的信息,然后一个接一个的读。它打开第一个块,它将读完关闭后才访问下一块。HDFS的复制块带来了更高的可靠性和可扩展性。如果client是数据节点中的一个,它将先访问本地,如果失败的话,它才会到集群的其他节点去访问。
FileSystem
使用FSDataOutputStream
及FSDataInputStream
来读写流的内容。Hadoop提供了多种FileSystem的实现:
- DistributedFileSystem(DFS):在分布式的环境中,访问HDFS文件
- LocalFileSystem:在本地系统中访问HDFS文件
- FTPFileSystem:访问HDFS文件的FTP客户端
- WebHdfsFileSystem:通过web接口访问HDFS文件。
URI及Path:
Hadoop的URI指定的HDFS的文件地址,FileSystem使用hdfs://host:port/location
的形式来访问文件。
hdfs://localhost:9000/user/joe/TestFile.txt
URI uri=URI.create (“hdfs://host: port/path”);
其中uri中的host及port是在core-site.xml文件中定义的:
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
Path应当如下的方法来产生:
Path path=new Path (uri); //It constitute URI
Configuration
Configuration类可以把Hadoop的配置信息传递给FileSystem.他默认通过类引导器引导core-site及core-default.xml,保存Hadoop配置信息 fs.defaultFS
,fs.default.name
等:
你可以用以下的方式来产生配置信息:
Configuration conf = new Configuration ();
你也可以明确的设置配置参数信息:
conf.set("fs.default.name", “hdfs://localhost:9000”);
FileSystem
下面的代码描述了怎样产生Hadoop的文件系统:
public static FileSystem get(Configuration conf)
public static FileSystem get(URI uri, Configuration conf)
public static FileSystem get(URI uri, Configuration conf, String user)
FileSystem使用NameNode来定位数据在DataNode的存放,并直接访问DataNode块,并按顺序读文件。FileSystem 使用Java IO FileSystem接口,主要是用DataInputStream 及 DataOutputStream的IO操作。
访问本地的文件系统,你可以直接使用getLocal
方法:
public static LocalFileSystem getLocal(Configuration conf)
FSDataInputStream
FSDataInputStream封装了DataInputStream,实现了Seekable, PositionedReadable接口,用于提供getPos(), seek(),以提供对HDFS文件的随机访问方法。FileSystem的open()方法返回FSDataInputStream
URI uri = URI.create (“hdfs://host: port/file path”);
Configuration conf = new Configuration ();
FileSystem file = FileSystem.get (uri, conf);
FSDataInputStream in = file.open(new Path(uri));
上面的方法的FSDataInputStream缺省缓冲大小为4096byte,可以在产生输入流的时候定义缓冲的大小
public abstract FSDataInputStream open(Path path, int sizeBuffer)
public interface Seekable {
void seek(long pos) throws IOException;
long getPos() throws IOException;
boolean seekToNewSource(long targetPos) throws IOException;
}
seek()方法找到从文件头到指定的长度的位置,read()从指定的位置以数据流的方式读数据,getPos()返回当前的InputStream流的位置
FileSystem file = FileSystem.get (uri, conf);
FSDataInputStream in = file.open(new Path(uri));
byte[] btbuffer = new byte[5];
in.seek(5); // sent to 5th position
Assert.assertEquals(5, in.getPos());
in.read(btbuffer, 0, 5);//read 5 byte in byte array from offset 0
System.out.println(new String(btbuffer));// &amp;amp;quot; print 5 character from 5th position
in.read(10,btbuffer, 0, 5);// print 5 character staring from 10th position
FSDataInputStream
也实现了 PositionedReadable
,提供了read, readFully,或者读指定位置固定长度的值
read(long position, byte[] buffer, int offset, int length)
FSDataOutputStream
Filesystem的create()
方法返回FSDataOutputStream
,用于产生新的HDFS文件或写内容到EOF
它没有提供seek,因为HDFS限制了内容只能写到EOF。它包装了Java IO的DataOutputStream,增加了方法getPos()得到文件的位置,以及write()将内容写在最后的位置。
public FSDataOutputStream create(Path f) /*create empty file.*/
public FSDataOutputStream append(Path f) /* will append existing file */
create方法传送Progressable接口,用于跟踪文件产生的状态。
public FSDataOutputStream create(Path f, Progressable progress)