Hadoop IO 特性详解(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**************************************************************** * Abstract Checksumed FileSystem. * It provide a basic implementation of a Checksumed FileSystem, * which creates a checksum file for each raw file. * It generates & verifies checksums at the client side. * *****************************************************************/ @InterfaceAudience.Public @InterfaceStability.Stable public abstract class ChecksumFileSystem extends FilterFileSystem { private static final byte[] CHECKSUM_VERSION = new byte[] { 'c' , 'r' , 'c' , 0}; private int bytesPerChecksum = 512; private boolean verifyChecksum = true ; private boolean writeChecksum = true ; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
public abstract class FileSystem extends Configured implements Closeable { /** *作用是将本地文件拷贝到目标文件,如果目标还是在本地就不执行任何操作,如果是远程就执行 * @param fsOutputFile path of output file * @param tmpLocalFile path to local tmp file */ public void completeLocalOutput(Path fsOutputFile, Path tmpLocalFile) throws IOException { moveFromLocalFile(tmpLocalFile, fsOutputFile); } /** * 将本地文件src拷贝到远程中去,也就是增加到FS中,操作之后本地文件还是原封不动,保持完整。 * @param src path * @param dst path */ public void copyFromLocalFile(Path src, Path dst) throws IOException { copyFromLocalFile( false , src, dst); } /** * 在指定的地点利用给定的校验和选项创建一个FSDataOutputStream * @param f the file name to open * @param permission访问权限 * @param flags {@link CreateFlag}s to use for this stream. * @param bufferSize the size of the buffer to be used. * @param replication required block replication for the file.副本 * @param blockSize * @param progress * @param checksumOpt checksum parameter. If null, the values * found in conf will be used. * @throws IOException * @see #setPermission(Path, FsPermission) */ public FSDataOutputStream create(Path f, FsPermission permission, EnumSet<CreateFlag> flags, int bufferSize, short replication, long blockSize, Progressable progress, ChecksumOpt checksumOpt) throws IOException { // Checksum options are ignored by default. The file systems that // implement checksum need to override this method. The full // support is currently only available in DFS. return create(f, permission, flags.contains(CreateFlag.OVERWRITE), bufferSize, replication, blockSize, progress); } /** Return true iff file is a checksum file name.是不是校验和文件呢。.crc结尾嘛,上一篇文章已经讲过这个点了*/ public static boolean isChecksumFile(Path file) { String name = file.getName(); return name.startsWith( "." ) && name.endsWith( ".crc" ); } /** Return the name of the checksum file associated with a file.*/ public Path getChecksumFile(Path file) { return new Path(file.getParent(), "." + file.getName() + ".crc" ); } /** Return the length of the checksum file given the size of the * actual file. **/ public long getChecksumFileLength(Path file, long fileSize) { return getChecksumLength(fileSize, getBytesPerSum()); } /** * Set whether to verify checksum. */ @Override public void setVerifyChecksum(boolean verifyChecksum) { this .verifyChecksum = verifyChecksum; } @Override public void setWriteChecksum(boolean writeChecksum) { this .writeChecksum = writeChecksum; } /** get the raw file system */ @Override public FileSystem getRawFileSystem() { return fs; } public boolean reportChecksumFailure(Path f, FSDataInputStream in , long inPos, FSDataInputStream sums, long sumsPos) { return false ; } } |
FileStatus几乎包含了文件/目录的所有属性,这样设计的好处可以减少在分布式系统中进行网络传输的次数。
FSDataInputStream/FSDataOutputStream
Hadoop基于流机制进行文件读写。通过FileSystem.open()可创建FSDataInputStream;通过FileSystem.create()/append()可创建FSDataOutputStream。
FSDataInputStream实现了Seekable接口和PositionedReadable接口 FSDataInputStream是装饰器模式的典型运用,实现Seekable接口和PositionedReadable接口借助其装饰的InputStream对象。
public class FSDataInputStream extends DataInputStream
implements Seekable, PositionedReadable, Closeable, HasFileDescriptor {
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");
}
}
public synchronized void seek(long desired) throws IOException {
((Seekable)in).seek(desired);
}
public void readFully(long position, byte[] buffer)
throws IOException {
((PositionedReadable)in).readFully(position, buffer, 0, buffer.length);
}
...
}
Seekable接口提供了在流中进行随机存取的方法,可在流中随机定位位置,然后读取输入流。 seekToNewSource()重新选择一个副本。
public interface Seekable {
// Seek to the given offset from the start of the file.
void seek(long pos) throws IOException;
// Return the current offset from the start of the file
long getPos() throws IOException;
// Seeks a different copy of the data. Returns true if found a new source, false otherwise.
boolean seekToNewSource(long targetPos) throws IOException;
}
PositionedReadable接口提供了从输入流中某个位置读取数据的方法,这些方法读取数据后并不改变流的当前位置。 read()和readFully()方法都是线程安全的,区别在于:前者试图读取指定长度的数据,后者读取制定长度的数据,直到读满缓冲区或者流结束。
public interface PositionedReadable {
public int read(long position, byte[] buffer, int offset, int length) throws IOException;
public void readFully(long position, byte[] buffer, int offset, int length) throws IOException;
public void readFully(long position, byte[] buffer) throws IOException;
}
FSInputStream抽象类继承InputStream,并实现PositionedReadable接口。FSInputStream拥有多个子类,具体的文件系统实现相应的输入流。
FSDataOutputStream继承DataOutputStream,Hadoop文件系统不支持随机写,因而没有实现Seekable接口。 FSDataOutputStream实现了Syncable接口,Syncable.sync()将流中的数据同步至设备中。
public class FSDataOutputStream extends DataOutputStream implements Syncable {...}
Hadoop 具体文件系统
Hadoop提供大量具体的文件系统实现,以满足用户访问各种数据需求。 这些文件系统直接或者间接的继承org.apache.hadoop.fs.FileSystem。
其中FilterFileSystem类似于java.io.FilterInputStream,用于在已有的文件系统之上提供新的功能,同样是包装器设计模式的运用。 ChecksumFileSystem用于在原始文件系统之上提供校验功能。
继承关系为:
FileSystem <-- FilterFileSystem <-- ChecksumFileSystem <-- LocalFileSystem
<-- ChecksumDistributeFileSystem
ChecksumFileSystem
ChecksumFileSystem继承FilterFileSystem,基于CRC-32提供对文件系统的数据校验。 与其他文件系统一样,ChecksumFileSystem需要提供处理文件/目录相关事务和文件读写服务。
文件/目录相关事务
这部分逻辑主要保持数据文件和CRC-32校验信息文件的一致性,如数据文件重命名,则校验文件也需要重命名。 如果数据文件为:foo.txt,则校验文件为:.foo.txt.crc
以ChecksumFileSystem.delete()方法删除文件文件为例。若文件为目录则递归删除(recursive=true);若为普通文件,则删除对应的校验文件(若存在)。
public boolean delete(Path f, boolean recursive) throws IOException{
FileStatus fstatus = null;
try {
fstatus = fs.getFileStatus(f);
} catch(FileNotFoundException e) {
return false;
}
if(fstatus.isDir()) {
return fs.delete(f, recursive);
} else {
Path checkFile = getChecksumFile(f);
if (fs.exists(checkFile)) {
fs.delete(checkFile, true);
}
return fs.delete(f, true);
}
}
读文件
Hadoop读文件时,需要从数据文件和校验文件中分别读出内容,并根据校验信息对读入的数据文件内容进行校验,以判断文件的完整性。 注:若校验事变,ChecksumFileSystem无法确定是数据文件出错还是校验文件出错。
读数据流程与ChecksumFSInputChecker和其父类FSInputChecker相关。 FSInputChecker的成员变量包含数据缓冲区、校验和缓冲区和读取位置等变量。
abstract public class FSInputChecker extends FSInputStream {
protected Path file; // The file name from which data is read from
private Checksum sum;
private boolean verifyChecksum = true;
private byte[] buf; // 数据缓冲区
private byte[] checksum; // 校验和缓冲区
private int pos;
private int count;
private int numOfRetries; // 出错重试次数
private long chunkPos = 0; // cached file position
...
}
ChecksumFSInputChecker构造方法对基类FSInputChecker的成员进行初始化,基于CRC-32校验,校验和大小为4字节。 对校验文件首先要进行版本校验,即文件头部是否匹配魔数"crc\0"
public ChecksumFSInputChecker(ChecksumFileSystem fs, Path file, int bufferSize)
throws IOException {
super( file, fs.getFileStatus(file).getReplication() );
...
try {
...
if (!Arrays.equals(version, CHECKSUM_VERSION))
throw new IOException("Not a checksum file: "+sumFile);
this.bytesPerSum = sums.readInt();
set(fs.verifyChecksum, new PureJavaCrc32(), bytesPerSum, 4);
} catch (...) { // ignore
set(fs.verifyChecksum, null, 1, 0);
}
}
FSInputChecker.read()循环调用read1()方法直到读取len个字节或者没有数据可读,返回读取的字节数。
public synchronized int read(byte[] b, int off, int len) throws IOException {
... // 参数校验
int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
}
}
FSInputChecker.read1()方法为了提高效率,减少内存复制的次数,若当前FSInputChecker.buf没有数据可读且要读取的len字节数大于或等于数据块大小(buf.length,默认512字节),则通过readchecksumChunk()方法将数据直接读取目标数组中,而不需经过FSInputChecker.buf的中转。 若buf没有数据可读且读取的len字节数小于数据块大小,则通过fill()方法从数据流中一次读取一个数据块。
private int read1(byte b[], int off, int len) throws IOException {
int avail = count-pos;
if( avail <= 0 ) {
if(len>=buf.length) {
int nread = readChecksumChunk(b, off, len); // read a chunk to user buffer directly; avoid one copy
return nread;
} else {
fill(); // read a chunk into the local buffer
if( count <= 0 ) {
return -1;
} else {
avail = count;
}
}
}
// copy content of the local buffer to the user buffer
int cnt = (avail < len) ? avail : len;
System.arraycopy(buf, pos, b, off, cnt);
pos += cnt;
return cnt;
}
FSInputChecker.readChecksumChunk()方法通常需要对读取的字节序列进行校验(默认为true),若校验不通过,可选择新的副本进行重读,如果进行了retriesLeft次重读仍然不能校验通过,则抛出异常。 readChunk()方法是一个抽象方法,FSInputChecker的子类实现它,以定义实际读取数据的逻辑。
private int readChecksumChunk(byte b[], int off, int len) throws IOException {
// invalidate buffer
count = pos = 0;
int read = 0;
boolean retry = true;
int retriesLeft = numOfRetries;
do {
retriesLeft--;
try {
read = readChunk(chunkPos, b, off, len, checksum);
if( read > 0 ) {
if( needChecksum() ) {
sum.update(b, off, read);
verifySum(chunkPos);
}
chunkPos += read;
}
retry = false;
} catch (ChecksumException ce) {
if (retriesLeft == 0) {
throw ce;
}
if (seekToNewSource(chunkPos)) { // 重试一个新的数据副本
seek(chunkPos);
} else {
throw ce;
}
}
} while (retry);
return read;
}
ChecksumFileSystem.ChecksumFSInputChecker实现了readChunk()的逻辑。 readChunk()它读取数据块和校验数据和,不进行两者的校验。 getChecksumFilePos()方法定位到校验和文件中pos位置对应块的边界,以便读取一个数据块对应的完整校验和。
// ChecksumFSInputChecker.readChunk()
protected int readChunk(long pos, byte[] buf, int offset, int len,
byte[] checksum) throws IOException {
boolean eof = false;
if(needChecksum()) {
try {
long checksumPos = getChecksumFilePos(pos);
if(checksumPos != sums.getPos()) {
sums.seek(checksumPos);
}
sums.readFully(checksum);
} catch (EOFException e) {
eof = true;
}
len = bytesPerSum;
}
if(pos != datas.getPos()) {
datas.seek(pos);
}
int nread = readFully(datas, buf, offset, len);
if( eof && nread > 0) {
throw new ChecksumException("Checksum error: "+file+" at "+pos, pos);
}
return nread;
}
写文件
与文件/目录元数据信息的维护和读文件相比,写文件相对起来比较复杂,ChecksumFileSystem需要维护字节流上的数据读写和基于块的校验和关系。 一般而言,每{io.bytes.per.checksum}(默认512)个数据字节对应一个单独的校验和,CRC-32校验和的输出为4个字节。因此校验数据所带来的存储开销小于1%。
ChecksumFSOutputSummer继承FSOutputSummer,在基本的具体文件系统的输出流上,添加数据文件和校验文件流的输出。 继承关系:OutputStream <-- FSOutputSummer <-- ChecksumFSOutputSummer
FSOutputSummer是一个生成校验和的通用输出流,包含4个成员变量。
abstract public class FSOutputSummer extends OutputStream {
private Checksum sum; // data checksum 计算校验和
private byte buf[]; // internal buffer for storing data before it is checksumed 输出数据缓冲区
private byte checksum[]; // internal buffer for storing checksum 校验和缓冲区
private int count; // The number of valid bytes in the buffer. 已使用空间计数
...
}
FSOutputSummer逻辑非常清晰,根据提供的字节数组,每{io.bytes.per.checksum}求出一个校验和,并根据子类所实现的writeChunk()方法写出到响应的输出流中,在ChecksumFSOutputSummer中,则分别写入文件数据流和校验文件数据流。
// ChecksumFileSystem.CheckSumFSOutputSummer
private static class ChecksumFSOutputSummer extends FSOutputSummer {
private FSDataOutputStream datas;
private FSDataOutputStream sums;
...
@Override
protected void writeChunk(byte[] b, int offset, int len, byte[] checksum) throws IOException {
datas.write(b, offset, len);
sums.write(checksum);
}
}
FSOutputSummer.write()方法循环调用write1()方法进行校验和计算和数据流输出。当buf的count数等于buf.length,则将数据和校验和输出到对应的流中。
public synchronized void write(byte b[], int off, int len) throws IOException {
... //参数校验
for (int n=0;n<len;n+=write1(b, off+n, len-n)) { }
}
private int write1(byte b[], int off, int len) throws IOException {
if(count==0 && len>=buf.length) {
final int length = buf.length;
sum.update(b, off, length);
writeChecksumChunk(b, off, length, false);
return length;
}
// copy user data to local buffer
int bytesToCopy = buf.length-count;
bytesToCopy = (len<bytesToCopy) ? len : bytesToCopy;
sum.update(b, off, bytesToCopy);
System.arraycopy(b, off, buf, count, bytesToCopy);
count += bytesToCopy;
if (count == buf.length) { // local buffer is full
flushBuffer();
}
return bytesToCopy;
}
private void writeChecksumChunk(byte b[], int off, int len, boolean keep) throws IOException {
int tempChecksum = (int)sum.getValue();
if (!keep) {
sum.reset();
}
int2byte(tempChecksum, checksum); // 整数转字节数组
writeChunk(b, off, len, checksum);
}
write1()方法是用了一个实用的技巧,若当前缓冲区的写入字节数为0(count=0)且需要写入的字节数据长度大于或等于块(buf.length)的长度,则直接进行校验和计算,避免将数据拷贝到缓冲区,然后再计算校验和,减少内存拷贝的次数。 write1()方法尽可能的写入多的数据,但一次最多写入一个块。
ChecksumFileSystem.CheckSumFSOutputSummer提供了构造FSOutputSummer所需要的参数。 校验和采用PureJavaCrc32,校验和长度4字节,缓冲大小为512字节(默认)。
public ChecksumFSOutputSummer(ChecksumFileSystem fs, Path file, boolean overwrite, int bufferSize,
short replication, long blockSize, Progressable progress) throws IOException {
super(new PureJavaCrc32(), fs.getBytesPerSum(), 4);
int bytesPerSum = fs.getBytesPerSum();
this.datas = fs.getRawFileSystem().create(file, overwrite, bufferSize, replication, blockSize, progress);
int sumBufferSize = fs.getSumBufferSize(bytesPerSum, bufferSize);
this.sums = fs.getRawFileSystem().create(fs.getChecksumFile(file), true, sumBufferSize, replication, blockSize);
sums.write(CHECKSUM_VERSION, 0, CHECKSUM_VERSION.length);
sums.writeInt(bytesPerSum);
}
构造ChecksumFSOutputSummer时,就往校验和文件流中写入魔数CHECKSUM_VERSION("crc\0")和校验块长度。 FSOutputSummer抽象了大部分和数据分块、计算校验和的相关功能,ChecksumFSOutputSummer在此基础上提供了具体的文件流输出。
java的crc校验,性能还提高了下,如果是几百兆就做一个crc校验,那么jni调用导致的上下文切换少些,那么jni就还有优势,但是在hadoop这个应用场景明显不合适。 后来淘宝的针对hadoop的crc场景,定制了jvm,将crc指令优化为调用硬件指令,性能测试报告证明提高了hdfs性能的20%-30%。
Hadoop IO 特性详解(2)的更多相关文章
- Hadoop IO 特性详解(2)【文件校验】
(本文引用了microheart,ggjucheng的一些资料,在此感谢.charles觉得知识无价,开源共享无价) 这一次我们接着分析文件IO校验的相关代码,看看最底层是如何实现这种大数据集的文件校 ...
- Hadoop IO 特性详解(1)
本文结合hadoop : the definitive guide精心而作,包含作者的心血,希望可以帮助大家理解一点hdfs的皮毛,足矣.(charles@xingbod.cn) hadoop本身自带 ...
- Hadoop IO 特性详解(1)【数据完整性】
本文结合hadoop : the definitive guide精心而作,包含作者的心血,希望可以帮助大家理解一点hdfs的皮毛,足矣.(charles@xingbod.cn) hadoop本身自带 ...
- java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET
java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了! 社区福利快来领取免费参加MDCC大会机会哦 Tag功能介绍—我们 ...
- ES6,ES2105核心功能一览,js新特性详解
ES6,ES2105核心功能一览,js新特性详解 过去几年 JavaScript 发生了很大的变化.ES6(ECMAScript 6.ES2105)是 JavaScript 语言的新标准,2015 年 ...
- 点击--》java9 新特性 详解
引言: 点击-->java9 新特性 详解 点击-->java8 新特性 详解 正题: 1.局部变量var 将前端思想var关键字引入java后段,自动检测所属于类型,一种情况除外,不能为 ...
- java10 新特性 详解
引言: 点击-->java9 新特性 详解 点击-->java8 新特性 详解 正题: 1.局部变量var 将前端思想var关键字引入java后段,自动检测所属于类型,一种情况除外,不能为 ...
- hadoop基础-SequenceFile详解
hadoop基础-SequenceFile详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.SequenceFile简介 1>.什么是SequenceFile 序列文件 ...
- Hadoop RPC机制详解
网络通信模块是分布式系统中最底层的模块,他直接支撑了上层分布式环境下复杂的进程间通信逻辑,是所有分布式系统的基础.远程过程调用(RPC)是一种常用的分布式网络通信协议,他允许运行于一台计算机的程序调用 ...
随机推荐
- python django框架(一)
s4day63内容回顾: 1. 安装 2. 创建用户 + 授权 3. 连接 - 数据库 终端创建数据库(字符编码) - 数据表 终端 ORM pymysql create ...)engine=inn ...
- Android性能调优实例
本文主要分享自己在appstore项目中的性能调优点,包括同步改异步.缓存.Layout优化.数据库优化.算法优化.延迟执行等. 目前性能优化专题已完成以下部分: 性能优化总纲——性能问题及性能调优方 ...
- BloomFilter布隆过滤器使用
从上一篇可以得知,BloomFilter的关键在于hash算法的设定和bit数组的大小确定,通过权衡得到一个错误概率可以接受的结果. 算法比较复杂,也不是我们研究的范畴,我们直接使用已有的实现. go ...
- SQL中合并两个表的JOIN语句
SQL里有四种JOIN语句用于根据某条件合并两个表: (INNER) JOIN: 交集 LEFT (OUTER) JOIN: 左表数据全包括,右表对应的如果没有就是NULL RIGHT (OUTER) ...
- TCP的保活定时器 转
http://blog.csdn.net/zhangskd/article/details/44177475 TCP的Keepalive,目的在于看看对方有没有发生异常,如果有异常就及时关闭连接. 当 ...
- ORM版,学生管理系统03
关于老师信息管理 建立多对多关系 第一种(通过外键建立) 自己写类,自己使其建立关系 缺点: 不能用Django ORM 多对多操作的语法 class Teacher(models.Model): t ...
- 小程序连续点击bug解决
问题描述: 1)wxml片段 <view bindtap="loadMulti"> <text>连续点击,加载多次</text> </vi ...
- (九)java位运算符
位运算符 &(与),|(或),^(异或),~(取反),<<(左移),>>(右移),>>>(无符号右移) 1:为true,0为false ...
- 四种线性相位FIR滤波器振幅谱统一形式
- oracle 删除当前用户下多个表
1.执行Sql语句: select 'drop table '||table_name||';' from cat where table_type='TABLE' 可查询到当前用户下所有的表,如图: ...