介绍
OutputStream是一个标准接口,文档介绍它如何在HFDS上实现。
HDFS增加实现了标准接口之外的Syncable和StreamCapabilities
数据写入文件系统
- 通常写入是通过FileSystem.create,FileSystem.append,FileDataOutputStreamBuilder.build()得到OutputStream。
- 通过write写入outputStream中,如果close方法调用了。需要其他客户端可见。
- 和写数据一起,Hadoop outputStream通过Sync接口提供了flush方法,让数据可以对其他的客户端可见。
- 接口:StreamCapabilities,允许探测Stream的能力
输出流模型
- 输出流可以被视为存储在客户机中的字节列表,hsync和hrush是操作。让数据传输到其他读者可以看到的地方,并持久化。
- open可以追踪输出流是否打开。
- 输出流可以通过一个三元组定位 (path,open,buffer)
刷新数据的可见性
Syncable操作之后,path中的数据需要和buffer一致。hsync和hrush区别点在于持久化的方式不同,而不是可见性的不同。
Stream状态
- FileSystem.create
返回一个空的缓存的stream
Stream = (path,true,[]) - FileSystem.append
返回 Stream = (path,true,data(Fs,path)) 默认填充缓冲区
持久化数据
当close时,当前buffer会替换file的内容,进行持久化。
close是幂等的,但是不能重复写。
FSDataOutputStream
OutputStream可以被Syncable,CanSetDropBehind和StreamCapabilities包装。
HDFS的FileSystem实现,DistributedFileSystem,返回了HdfsDataOutputStream.它实现了两个没有被Java显示声明的行为。
1.多个线程可以往同一个OutputStream里面写入,HBase依赖这一项。
咋实现的?
2.OutputStream.flush在文件关闭时禁止写入。
由于write是HDFS的很重要的API,所以write线程安全非常重要。
OutputStream
Java OutputStream允许应用写bytes到目的文件。
- write(Stream, data)
- write(Stream,byte[] data,int offset,int len)
- write(byte[] data)
- flush
- close
close时要释放锁
close需要阻塞到write数据完成
close只能一次,后续的close要异常
有些close非常慢,比如对象存储要在close时上传全部数据。可能阻塞线程很长时间。
close需要保证一致性,持久化,还要保证文件状态是closed。
- hdfs的实现上,没有保证close的时候已经完成数据持久化了,所以需要先调用sync来持久化数据。
Syncable
目的是保证数据写入到文件系统即可见,有持久。具有hflush和hsync接口。
Syncable除了被OutputStream的子类实现外,还被其他类比如说Sequence.Writer实现。
FSDataOutputStream实现Syncable,仅仅是看它包裹的OutStream是不是实现了Sync接口,如果没有的话,会降级hflush到flush。
hflush
hflush和flush区别?
flush是should,hflush是阻塞确保刷新的。
hflush可以调用hsync。
通常文件系统只提供hsync,那么时间可能更长。
除非WAL,否则不要每一行都hsync.
hsync
hsync提供持久性和可见性.要求存磁盘。
StreamCapabilities
目的是让调用者动态的决定Stream的行为,比如是否支持HSYNC,HFLUSH等,readAhead,unbuffer,readbytebuffer,preadbytebuffer等。
CanSetDropBehind
改变hdfs的策略,用来判断是否丢弃cache
Output Stream 的 持久性,并发性,一致性和可见性
系统行为的几个方面,文件系统模型没有直接设计,但是在生产中可以看到。
持久化
- write
可以同步或者异步的持久化数据 - flush
flush数据到目的地,没有持久化要求 - hflush
确保结束之后,所有读取新流的客户端可以看到,不保证持久性。 - hsync
语义和hflush一致,但是要求持久化存储 - close
语义和hflush一致,需要关闭流
并发性
- 多个线程写入同一个文件的结果未定义。
这里面不用锁吗?hbase是如何支持多个线程公用一个流的?
- 一个读一个写的时候,啥时候可以读取到新数据。
- DFSOutputStream具有强的线程安全模型。
DFSOutputStrem他所有的操作都是加锁的了。
一致性和可见性
- 没有要求数据立即可见,除非是使用了fush等操作。
- create(path,overwrite==true),那么路径会立刻不可见。
- 文件的元数据应该在flush和sync或者close以后和内容一致。
Hadoop Output Stream 模型的问题
问题点主要在于何时写入和持久化数据,以及何时同步元数据。HDFS和本地文件系统的实现在某些部分不遵从outputStream的设计规范。
HDFS
- HDFS: hsync只同步最后一个块。
WAL要求在提交标志刷新之前,前面字节必须被保存。因此用HDFS实现WAL时,要注意如果上次同步之后写入的数据很多,跨越了block边界,只会同步最后一个块。
如果一个大的写任务要求同步,那么需要定时同步。 - HDFS: 元数据通常是滞后的,比如说getFileStatus获取的文件长度通常是过时的。
需要注意的一点是getFileStatus(Fs,path).getLen()==0并不能说明文件是空的。
localFileSystem
localfilesystem是通过file.crc文件来确保块的完整性的。所以,只有完整块才可以hflush。
所以如果不禁用校验和,不能随时hsync。hdfs是通过一个DataBlockScanner周期性校验。
对象存储
对象存储可能一直到close才put输出。
对象存储不能保准outputStream创建后可见。
对象存储和POSIX文件系统也很不一样,对象存储不能保证close之后,数据可以持久的保存。比较有利的是,PUT操作可以保证原子性。
优化建议
1.实现Syncable接口,或者是抛出不支持。
2.元数据更新
3.close持久化数据。