Hadoop HDFS 初探
1. 数据块的存取
HDFS集群上的从节点都会驻留一个数据节点的守护进程,来执行分布式文件系统中最忙碌的部分:将HDFS数据块写到Linux本地文件系统的实际文件中,或者从这些实际的文件读取数据块。
2. 客户端访问HDFS的过程
客户端进行文件内容操作时,先由名字节点告知客户端每个数据块驻留在哪个数据节点,然后客户端直接与数据节点守护进程进行通信,处理与数据块对应的本地文件。同时,数据节点会和其他数据节点进行通信,复制数据块,保证数据的冗余性。
HDFS是一种分布式文件系统。
设计分布式文件系统要考虑的一些问题:
访问透明性、性能透明性、伸缩透明性、复制透明性、并发透明性、故障透明性等
选取一些比较有特点的设计摘录如下:
设计一:FileStatus
文件信息对象,路径、长度、
副本数,块大小、所有者等。
block replication和block size专门为HDFS准备。
实现Writable接口,一次性将文件的所有属性读出返回到客户端,减少在分布式系统中进行网络传输的次数,针对性设计。
设计二:FSDataInputStream
它实现了Seekable、PositionedReadable接口。
利用前者的
seekToNewSource方法可以重新选择一个副本,用于HDFS中。
利用后者实现从流中某个位置开始读数据,并
不改变流的当前位置,且是
线程安全。(线程安全是如何实现的?实现方法时?)
注意,FSInputStream抽象类实现了PositionedReadable接口的read方法,通过sychronized关键字保证线程安全。通过Seekable中的方法保证流的当前位置不变。
设计该类的目的就是将FSInputStream包装进DataInputStream中,使之具备读取某种原生类型的功能。同时,构造FSDataInputStream的对象必须是实现了Seekable、PositionedReadable接口的类对象,比如FSInputStream。这样利用多态,每个方法的实现就依赖于构造器参数中流对象的方法实现了。构造器如下:
public FSDataInputStream(
InputStream in)
throws IOException {
super(in);
if( !(in instanceof Seekable) || !(in instanceof PositionedReadable) ) {
throw new IllegalArgumentException(
"In is not an instance of Seekable or PositionedReadable");
}
}
类似于一种装饰器。这样,原来只有任意位置读取功能的流,如FSInputStream,同时具有了读取某种类型数据的能力。
方法实现举例:
public int read(long position, byte[] buffer, int offset, int length)
throws IOException {
return ((PositionedReadable)in).read(position, buffer, offset, length);
}
read方法就是利用in的read实现。in可以使FSInputStream等类型。
设计三:FileDataOutputStream
不实现Seekable接口,Hadoop文件系统
不支持随机写,
但提供getPos的功能,当前写位置通过一个内部类PositionCache获得,这个类是个
过滤流。
过滤流的作用就是在底层流基础上封装一些新的功能,是装饰器模式的基础。
构造器:
public FSDataOutputStream(OutputStream out, FileSystem.Statistics stats,
long startPosition) throws IOException {
super(new PositionCache(out, stats, startPosition));
wrappedStream = out;
}
插入话题:装饰模式
动机:
透明地给一个对象增加功能,并实现功能的动态组合。所谓透明,就是给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象。继承的缺点在于只能增加功能,不能删除或修改功能。
思想:在面向对象设计中,有一条基本的规则就是“尽量使用对象组合,而不是对象继承”来扩展和复用功能。装饰器模式思考起点就是这个规则。
装饰模式的一些对象:
1. Component 组件对象接口
2. ConcreteComponent 具体组件对象 被装饰器装饰的原始对象
3. Decorator:所有装饰器的抽象类
接口与组件接口一致(
通常继承于Component,目的是可以连续装饰) 要持有一个Component对象 即被修饰的对象
4. ConcreteDecorator:实现具体要向被装饰对象添加的功能
这里类比一下Java流的类
Component对应OutputStream
ConcreteComponent对应FSDataOutputStream
Decorator相当于FilterOutputStream
ConcreteDecorator相当于PositionCache
具体地,FilterOutputStream继承于OutputStream保持接口一致,并持有一个OutputStream对象,用于装饰,构造时要传入一个OutputStream对象。
public void write(int b) throws IOException {
out.write(b);
}
其中方法转发给组件对象,如上述的out.write
举例:PositionCache类是一个装饰器,目的是给输出流增加记录当前流写位置和一些统计功能。
FSDataOutputStream是一个装饰器类,它持有一个组件对象wrappedStream(先忽略这个对象)
这个类从父类那继承了一个OuputStream对象out,其实它的父类DataOutputStream类也是一个装饰器类,其增加了记录当前输出的字节数的功能。
这个类即在父类的基础上使用PositionCache装饰器进一步增加功能。
加入客户端有如下代码:
FSDataOutputStream fsdos = new FSDataOutputStream(new FileOutputStream("test.txt"), stats, 1000);
调用下面构造器:
public FSDataOutputStream(OutputStream out, FileSystem.Statistics stats,
long startPosition) throws IOException {
super(new PositionCache(out, stats, startPosition));
wrappedStream = out;
}
构造PositionCache对象:
public PositionCache(OutputStream out,
FileSystem.Statistics stats,
long pos) throws IOException {
super(out);
statistics = stats;
position = pos;
}
此时那个文件输出流对象被赋值给装饰器基类中的OutputStream对象out
回到上一层,刚构造的PositionCache对象被赋值给装饰器基类中的OutputStream对象out
当调用fsdos.write时,调用父类的write方法如下:
public synchronized void write(int b) throws IOException {
out.write(b);
incCount(1);
}
这里的out继承自DataOutputStream的父类,即FilterOutputStream,而在FSDataOutputStream对象初始化时,这个out对象已经被初始化为一个PositionCache对象。
因此调用PositionCache的write方法:
public void write(int b) throws IOException {
out.write(b);
position++;
if (statistics != null) {
statistics.incrementBytesWritten(1);
}
}
而这里的out对象是在PositionCache对象初始化时,被赋值为那个文件输出流对象,因此调用的是FileOutputStream.write方法。
这样便完成了从组件类(FileOutputStream)到装饰器类(FSDataOutputStream、DataOutputStream和PositionCache)的层层调用的递归过程。在每一层功能都有所增加。
其实装饰过程相当于
new FSDataOutputStream(new PointCache(new FileOutputStream(""),dd,dd),dd,dd);
每层装饰器的父类对象都使用下一层的装饰器对象初始化,一直到组件对象位置。调用方法时,则递归调用到底层组件对象,再逐层返回,每层都是先调用父类(也就是下层装饰器)方法,再添加装饰功能。
参考资料:
蔡斌 陈湘萍 《Hadoop技术内幕--深入解析Hadoop HDFS和Common架构设计与实现原理》