`
QING____
  • 浏览: 2232615 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

HDFS读写路径详解

 
阅读更多

    【HDFS读写路径详解

    HDFS中我们通常使用FileSystem类来操作文件系统,提供了众多类POSIX接口调用风格的方法。一段典型的代码样例为:

public static void read(String path) throws Exception{
	Configuration conf = new Configuration();
	FileSystem fs = FileSystem.get(conf);
	BufferedReader reader = null;
	try{
		FSDataInputStream fsInput = fs.open(new Path(path));
		reader = new BufferedReader(new InputStreamReader(fsInput,"UTF-8"),256);
		String line = null;
		while (true) {
			line = reader.readLine();
			if(line == null) {
				break;
			}
			System.out.println(line);
		}
	}catch (Exception e) {
		e.printStackTrace();
	} finally {
		IOUtils.closeStream(reader);
	}
}

public static void create(String parent,String file) throws Exception {
	Configuration conf = new Configuration();
	FileSystem fs = FileSystem.get(conf);
	BufferedWriter writer = null;
	try {
		FSDataOutputStream fsOut = fs.create(new Path(parent,file),true);
		writer = new BufferedWriter(new OutputStreamWriter(fsOut,"UTF-8"));
		writer.write("Hello,hadoop!");
		writer.newLine();
		writer.write("Hello,HDFS!");
		writer.flush();
	}catch (Exception e) {
		e.printStackTrace();
	} finally {
	   writer.close();
	}
}

 

一、Configuration

    此类用来加载hadoop的配置文件,hadoop平台下的其他组件(HDFS,Mapreducer,yarn,hbase等)也可以通过它来加载。它类似于java中的Properties类,可以get/set相应的配置项值,不过提供了更多便捷的操作方法,Configuration实例非线程安全。默认情况下Configuration类只依次加载“core-default.xml”、“core-site.xml”配置文件。

 

    1、addDefaultResource(String name): 将指定的配置文件作为默认值加载,Confuguration类内部有一个static类型的CopyOnWriteArrayList,用来保存defaultResources列表,默认此列表中只有"core-default.xml"、"core-site.xml"两个文件,可以通过此方法添加默认resource文件,此后所有的Configuration实例都会加载这些defaultResources。

    每个Configuration实例,都会重新加载所有的配置文件(加载文件的过程是同步的),这在某些场景下显得有些“多余”,通常情况下Confuguration实例非线程安全,因为其set方法并没有同步,如果能够对set方法进行同步,我们仍然可以在多线程环境中使用Configuration实例。

 

    2、Configuration的子类有HBaseConfiguration、HdfsConfiguration、YarnConfiguration等;其中HBaseConfiguration额外加载了“hbase-default.xml”、“hbase-site.xml”;HdfsConfiguration额外加载了“hdfs-defaut.xml”、“hdfs-site.xml”;YarnConfiguration额外加载了“yarn-default.xml”、“yarn-site.xml”。

 

二、FileSystem

    这是HDFS的核心API,我们通常使用它来操作HDFS文件系统。FileSystem有多个子类,比如LocalFileSystem、FTPFileSystem、S3FileSystem,以及HDFS中最常用的DistributedFileSystem,和HBase中的HFileSystem等。

    1、FileSystem get(Configuration conf): 此方法将会导致,configuration额外的加载core、hdfs、mapred、yarn所有的配置,并根据“fs.defaultFS”配置项返回相应的文件系统:如果以“file://”开头,则返回LocalFileSystem实例,如果以“hdfs://”开头则返回DistributedFileSystem实例;其他的FileSystem类似。FileSystem将使用ServiceLoader类加载META-INF/services目录下“org.apache.hadoop.fs.FileSystem”文件中指定的服务列表,列表中即为HDFS支持的文件系统。

 

    此方法将会根据conf中“fs.defaultFS”配置项指定的文件系统uri,返回指定的FileSystem实例,同时还可以使用get(URI uri,Configuration conf)方法指定uri来覆盖conf中的配置项。

 

    FileSytem类内部有一个static CACHE,用来保存每种文件系统的实例集合,FileSystem类中可以通过"fs.%s.impl.disable.cache"来指定是否缓存FileSystem实例(其中%s替换为相应的scheme,比如hdfs、local、s3、s3n等),默认cache为开启,即一旦创建了相应的FileSystem实例,这个实例将会保存在缓存中,此后每次get都会获取同一个实例;当调用FileSystem#close()方法时,将会从CACHE中移除此实例;如果CACHE中没有匹配的实例,将会实例化。

 

    2、FSDataOutputStream append(Path path): 从文件的尾部写入数据。并非所有的FileSystem子类都支持。不过HDFS文件系统DistributedFileSystem支持。

    3、boolean deleteOnExit(Path path): 将delete操作添加到本地cache中,在文件系统关闭时(close方法),再执行删除文件操作。与之相关联的方法还有cancelDeleteOnExit(path),从本地cache中移除一个deleteOnExit删除操作。“deleteOnExit”删除操作,将会在close方法调用时,依次删除(调用delete方法)。

 

    4、void close(): 关闭文件系统,此操作通常被子文件系统重写;此法将会从CACHE中移除此文件系统的实例,执行“deleteOnExit”列表,子系统中还会处理相关的网络链接关闭等操作。通常情况下,我们不会调用close方法,除非确定FileSystem实例不在使用。

 

    5、void concat(Path target,Path[] srcs):  将多个src文件“追加”到目标文件中,需要注意的是这个过程仅仅是blocks的逻辑移动,要求参与concat的文件都必须有相同的block大小。

 

    6、void copyFromLocalFile()/copyToLocalFile():将文件在local与remote之间互传。

    7、FSDataOutputStream create(Path):创建一个文件,此方法有多个重载版本,可以在创建文件时指定permission、block大小等,以及overwrite现存的文件设置。

    8、Path createSnapshot(Path path,String snapshotName):手动触发snapshot创建。也可以使用deleteSnapshot方法删除相应的snapshot文件。

    9、boolean delete(Path):删除文件,如果是目录,将会删除目录下所有的文件。

    10、boolean exists(Path path):检测文件或者路径是否存在。

    11、FileStatus[] listStatus(Path path):获取指定目录下所有文件的状态,通常用来获取文件列表。

    12、void setOwner(Path path,String user,String group):设定指定目录的owner信息。superuser才能具有此权限。相关的方法还有setPermission、setReplication。

    13、void setVerifyChecksum(boolean checksum):在读取文件时,是否使用checksum校验。这是HDFS保证数据完整性的方式。默认为true。

 

    有些方法非常重要,我们会在稍后DistributedFileSystem中详细介绍,包括HDFS文件系统的“读路径”、“写路径”的原理。

 

三、DistributedFileSystem

    这个类是HDFS文件系统的操作类,继承自FileSystem。在Hadoop平台上,mapreduce、hbase等应用,都会使用此类操作HDFS文件系统。 DistributedFileSystem在初始化时除了加载“core-default.xml”、“core-site.xml”,还会实例化HdfsConfiguration并加载“hdfs-default.xml”、“hdfs-site.xml”配置文件。每个DistributedFileSystem内部,都会实例化一个DFSClient类,DFSClient高度封装了文件系统的所有接口调用,并负责底层与Datanode、Namenode的Socket通讯。

 

    1、DFSClient

    在DistributedFileSystem实例创建后,会执行一系列的初始化工作(initialize),其中包括初始化DFSClient实例。DistributedFileSystem底层会通过DFSClient与远端Hadoop建立链接、执行文件操作等。DFSClient实例化时根据configuration中有关的配置信息创建多项辅助操作类(参见下文),不过此时,并不会触发实际的socket操作。

 

    1) NameNodeProxies:用来创建与远程Namenode通信的代理类,最终代理类使用合适的address(或者转义之后的address)与远端通讯,并在链接异常时负责重连或者Failover,在HA和non-HA模式下有所不同。

    HA: 当集群中多个Namenode以HA模式架构时,我们通常在Client端使用HA Failover;通过使用“dfs.client.failover.proxy.provider.{nameserviceId}”指定proxy(参见HDFS HA相关文档),对于Failover模式下,可用的provider有“DefaultFailoverProxyProvider”和“ConfiguredFailoverProxyProvider”,当RetryProxy实例遇到error,重试一定次数后触发“failover”,有上述2个provider决定此时该如何处理;其中“DefaultFailoverProxyProvider”并不具备“fail-over”的能力,当failover发生时它不做任何事情,仍然重试当前的Namenode,这和non-HA没有区别;“ConfiguredFailoverProxyProvider”实现了“fail-over”能力,当nameservice中某个namenode失效,那么将会failover到下一个namenode,通常我们在HA模式下,使用此provider来实现Client端的failover特性。

 

<property>  
	<name>dfs.nameservices</name>  
	<value>default</value>  
</property> 
<property>  
	<name>dfs.ha.namenodes.default</name>  
	<value>com.namenode1,com.namenode2</value>  
</property> 
<property>  
	<name>dfs.client.failover.proxy.provider.default</name>  
	<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>  
</property> 	
<!-- 其他ha参数,请参见DFSConfigKeys中以“DFS_CLIENT_FAILOVER”开头的配置项 -->

 

    non-HA:当HDFS实例中只有一个Namenode时,即non-HA模式下,我们将无法使用Failover;当Namenode失效时Client将不可用,为了避免这种情况,我们需要开启retry机制(默认关闭)

<property>  
	  <name>dfs.client.retry.policy.enabled</name>  
	  <value>true</value>  
</property> 

  

    2) DomainSocket:通常情况下,Client读取HDFS文件的过程是:Datanode根据blockId从本地磁盘获取文件并通过TCP流发送给Client;不过,HDFS提供了“Short-Circuit Local Reads”特性(Client与server端都需要支持),即当Client与Block位于同一节点时,那么Client可以通过直接读取本地Block文件即可获取数据,无需通过Datanode TCP链接,这就是“Short-Circuit”(短路),在很多情况下,这种方式可以有效提升读取性能。[参见文档]

<property>
	<name>dfs.client.read.shortcircuit</name>
	<value>true</value>
</property>
<property>
	<name>dfs.domain.socket.path</name>
	<value>/var/lib/hadoop-hdfs/dn_socket</value>
</property>

 

    3) PeerCache:在Client读取block数据时,需要与Datanode建立Socket链接(NioInetPeer),或者在Shortcircuit场景下创建DoaminSocket(DomainPeer),毕竟创建这些IO操作句柄是耗时的,DFSClient使用PeerCache来缓存这些已经创建的Peer对象(所谓Peer,即内部封装点对点通信Socket对象),PeerCache内部为Map,key为DatanodeID,value为Peer。当DFSClient需要读取文件时,会创建BlockReader对象,此时也将会检测PeerCache中对应的目标DatanodeID下是否有已经创建了Peer对象,如果有则根据此Peer创建BlockReader,否则则实例化一个新的Peer并放入PeerCache中。PeerCache内部启动了一个守护线程,间歇性的检测每个Peer的空闲时间,如果空闲超时,则会从PeerCahce中移除并close。

 

    4) Read CachingStrategy:读缓存策略,readDropBehind和readahead两个参数控制读缓存策略,数据读取通常为磁盘操作,每次read将会读取一页数据(512b或者更大),这些数据加载到内存并传输给Client。readDropBehind表示读后即弃,即数据读取后立即丢弃cache数据,这可以在多用户并发文件读取时有效节约内存,不过会导致更频繁的磁盘操作,如果关闭此特性,read操作后数据会被cache在内存,对于同一个文件的多次读取可以有效的提升性能,但会消耗更多内存。readahead为预读,如果开启,那么Datanode将会在一次磁盘读取操作中向前额外的多读取一定字节的数据,在线性读取时,这可以有效降低IO操作延迟。这个特性需要在Datanode上开启Native libaries,否则不会生效。

 

<property>
	<name>dfs.client.cache.readahead</name>
	<value>true</value>
</property>
<property>
	<name>dfs.client.cache.drop.behind.reads</name>
	<value>true</value>
</property>
<property>
	<name>dfs.datanode.readahead.bytes</name>
	<value>4193404</value>
</property>

 

    2、读路径

    我们可以通过使用DistributedFileSystem#open(Path path)方法开打开一个文件,并获取其FSInputStream实例,此后我们即可以通过FSInputStream#read()方法依次读取文件数据。

    1) open():此方法接受一个参数bufferSize,并实例化一个DFSInputStream实例,DFSInputStream继承自FSInputStream,FSInputStream又继承自DataInputStream,它内部封装了实际的文件读取操作。

    在实例化DFSInputStream时,首先由DFSClient向Namenode发送RPC调用,获取指定file的一定数量的Block位置,“Block位置”由LocatedBlocks类描述,它持有一个LocatedBlock对象的列表,每个LocatedBlock持有Block信息(blockId,poolId,offset起始)以及此Block被放置的Datanode列表;DFSClient初次并不获取所有Blocks位置信息,而是获取10个,这个参数可以由“dfs.client.read.prefetch.size”控制,默认为“10*blockSize”,此值建议为blockSize的整数倍。

 

    在整个read过程中,从Namenode获取的LocatedBlocks都会被cache在DFSClient中,当read到某个offset时,则会从LocatedBlock列表中遍历并获取持有此offset数据的LocatedBlock,如果不存在,则向Namenode重新获取并并插入到LocatedBlock列表的合适位置(保证顺序是按照offset排列);每个LocatedBlock中Datanode列表的顺序有Namenode预先排列好的,排序的准则是根据Client与Datanode的逻辑“距离”:local > local rack > cross rack,然后再根据可靠性原则把“stale”的Datanode(陈旧的,非新鲜的,有故障风险的)调整到列表的尾部,最终此Datanode列表为为:距离近 + fresh > 距离远 + fresh > 距离近 + stale,stale节点之所以优先级较低,可以避免read hotspot问题以及较小的故障率,当Namenode在一定时间内没有收到(或延迟收到)Datanode的心跳信息,那么Namenode将会把此Datanode标记为“stale”,处于stale状态的Datanode并不意味着“失效”,只是它有失效的风险,此外stale也意味着此Datanode上已经存在了大量的旧数据,有较多的Client与其建立链接,通常可能请求压力较大,有成为“热点”的可能;对于Client端而言,读写操作总是与最优的Datanode通信。

 

<property>
	<name>dfs.namenode.avoid.read.stale.datanode</name>
	<value>false</value>
	<!-- 如果namenode在一定时间内没有收到datanode的心跳信息
	那么此datanode将会被标记为"stale",在给Client read返回
	datanode列表时,"stale" datanodes将会追加在列表的尾部,
	优先级最低
	-->
</property>

 

    DFSInputStream中read方法有多个重载版本,它们都是重写了父类DataInputStream。在read开始时,如果还没有到达当前Block的尾部,则继续读取;如果到达当前Block的尾部,那么就需要读取下一个Block,首先根据从LocatedBlocks中查到到此block对应的LocatedBlock,并获取最优的Datanode,此后则直接与Datanode建立Socket链接并创建一个辅助操作类BlockReader,BlockReader负责通过Socket流持续读取数据,一般一次性读取bufferSize字节个数据并保存在Client端的缓存中,如果缓存耗尽,则会再次触发Socket IO操作;Client缓存有两种方式,分别为ByteArrayStrategy和ByteBufferStrategy,其实现分别使用ByteArray和ByteBuffer,它们适用于read(byte[] buf)和read(ByteBuffer buf)两个重载方法。其中bufferSize可以由“io.file.buffer.size”配置项决定,默认为4096。

 

 

 

    当BlockReader读到Block的尾部,且Block的checksum校验正常,则会立即向Datanode额外的发送一个statusCode,以帮助Datanode判定Block的完整性,此时Datanode会继续维持Socket链接(Client端也会把Peer放入PeerCache,以重用);否则Datanode会认为block有异常,则会主动终端Socket链接。

 

    如果在读取数据时,发现Checksum校验失败,并不会立即抛出ChecksumException,而是继续重试其他Datanode,如果有正常的Datanode,则会与此Datanode建立链接并重新生成BlockReader实例(原BlockReader将会被关闭,而且Peer也会被关闭,不会cache);在向Client返回数据之前,首先向Namenode报告此Block损坏情况,Namenode将会在namespace中标记此Block所在的Datanode为“corrupted”,并确保在此后其他Client获取此Block的locations时不会包含此Datanode。只有当此Block的所有Datanode都损坏时,才会向Client抛出ChecksumException。

 

    因为网络原因(或者Block迁移)Client在读取Block数据时可能发生网络错误,此时Client端将会重试此Datanode,如果重试失败,则把此Datanode添加到“DeadDatanode”列表,继续尝试其他的Datanode,如果所有的Datanode都无法连接,则会重新从Namenode获取;重试多次后,仍然无法连接,则抛出IOException。

 

    2) 通常情况下,DistributedFileSystem实例在HDFS读操作中是线程安全的,我们不需要为每个file的读操作创建单独的DistributedFileSystem实例。不过在共享此实例时,在调用close方法之前,需要确定所有的文件读操作都已经结束,因为其内部将会关闭DFSClient实例。

 

 

 

 

    3、写路径

    1) create(Path path,boolean overwrite):创建文件,此方法将返回一个DFSOutputStream,其内部只做了一件事:向Namenode发送RPC请求,并创建path有关的元数据。我们可以在创建文件时,指定blockSize、replication和Checksum算法,其中blockSize、checksum一旦指定将不能修改,replication此后可以随意调整。

    create操作并不会触发实际的Block创建,Namenode也不会向Client返回与DataNodes Pipeline相关的信息;这些将会在有实际数据写入时延迟获取,请参见下文。

 

 

    2) append(Path path):在文件尾部追加数据,此方法最终返回一个DFSOutputStream;append是HDFS唯一支持的“update”操作,我们不能对HDFS文件随机写入,这由HDFS架构的设计要求所决定。append方法与create不同的是,它将首先从Namenode上获取lastBlock的位置信息--LocatedBlock实例:它包含此Block所在的Datanodes列表以及offset等,那么此后的写入操作,将会从此Block开始。

 

 

    3) DataStreamer:无论是create还是append,在创建DFSOutputStream实例时总会创建一个DataStreamer对象,它是一个守护线程,并在DFSOutputStream实例化后被start。在写入数据时,并不会触发实际的IO操作,DFSOuptStream首先将数据buffer在Client端本地的缓存中,这个缓存的单位是Packet(数据包),每个packet默认为64KB,当Packet负荷满时,将会把此packet添加到一个Queue中(底层实现为LinkedList,非阻塞);

DataStreamer线程将从Queue中移除Packet,如果Queue为空则阻塞(wait,直到write操作将其notify),并将packet写入到Pipeline中。

 

 

    此外,每个Packet又有多个chunk构成,每个chunk包含定长的bytes数据和定长的checksum值:

<property>
	<name>dfs.client-write-packet-size</name>
	<value>65536</value>
</property>
<property>
	<name>dfs.bytes-per-checksum</name>
	<value>512</value>
</property>

 

    Client将数据写入Pakcet之后将会立即返回,当Packet负荷满时将此packet加入队列,但Client并不等待DataStreamer的发送结果以及Datanodes的响应结果。由此可见,当Client端故障时,那些在本地Buffer的packet数据将会丢失。

 

 

    Queue中最多缓存80个Packet,如果Queue已满,那么Client的写入操作将会阻塞,直到其他Packet通过Pipeline发送且Ack成功为止。

 

 

    4) ResponseProcessor:DataStreamer负责将Packet发送到Pipeline中,那么ResponseProcessor则负责等待Pipeline中的ACK信息;ResponseProcessor也是一个守护线程,并在Pipeline初始化是启动。当Queue中的Packet被DataStreamer移除后,将会被添加到一个ackQueue中,ackQueue就是Pipeline响应队列;DataStreamer负责向Pipeline中write数据,那么ResponseProcessor则负责从Pipeline中read数据;如果它接收到了ACK且状态为成功,那么将会从ackQueue移除packet,说明这个packet已经被Pileline中的Datanodes正确存储了。

 

 

    5) Datanodes Pipeline:create方法将会导致pipeline的状态处于“PIPELINE_SETUP_CREAT”,append方法将会导致pipeline的状态为“PIPELINE_SETUP_APPEND”,DataStreamer线程启动后,将会根据此状态来初始化Datanodes pipeline。

 

 

    “PIPELINE_SETUP_CREAT”表明这是create方法创建,此时将会向Namenode发送addBlock请求并创建当前文件的首个Block,并返回此Block所在的Datanodes列表(有序),Block的创建涉及到HDFS block放置策略,如果Client部署在集群中的某个Datanodes上(例如Mapreduce程序),那么将会优先在当前Datanode上创建block,否则将会根据统计信息选择一个“磁盘利用率”最低的节点。Client将会与列表中第一个Datanode建立链接,并将Datanodes列表以及block信息通过TCP的方式发送出去,此过程为writeBlock;那么对于第一个Datanode而言(操作类为DataXceiver,Receiver),将会解析出Client传递的Datanodes列表,并与下一个Datanode建立TCP链接并将此数据原样传递下去(仍然是writeBlock),并且每个Datanode都会等待下一个节点反馈链接状态(ResponseStatus),最终将结果响应给Client,此时Pipeline构建成功。如果Pipeline构建过程中出现网络异常,那么Client将会abandonBlock(放弃block),并继续重试上述过程;如果仍然失败,将会抛出IOException。

 

    “PIPELINE_SETUP_CREAT”表明这是有append方法创建,它和“PIPELINE_SETUP_CREAT”的最大区别就是当前文件已经存在Block,因为它不能像create操作那样“addBlock”或者随意的“abandonBlock”,而是根据lastBlock的信息构建Pipeline。构建过程基本相同,只不过在构建时如果某个Datanode失效,将会把此Datannode从pipeline中移除,并重新构建。这里还涉及到pipeline中Datanodes的排除策略、recovery机制等。过程及其复杂,源码阅读起来苦不堪言,在此就不多介绍。

 

 

    最终我们达成结果为:

 

 

    那么Client的数据Packet将会在pipeline中传递,如果在发送数据过程中网络异常(第一个Datanode故障)或者ResponseProcessor从ACK中得到Pipeline中某个节点异常,将会导致Client从Namenode重新回去当前Block的位置信息,并重建Pipeline;Queue中的packet将会重新发送。

 

 

 

    6) HDFS为了严格控制每个block的尺寸必须符合设定,那么有可能在临近block的尾部时,Client发送的packet并不是完整的(65526个字节);Client在write数据时,每次都会比较block尾部剩余空间与Packet的大小,如果不足一个完整的packet,那么将创建一个和剩余空间一样大的Packet并写入,以确保block完整。因为内部设计的原因(其中包括checksum值为8字节数字),那么要求blockSize的大小以及chunk的大小必须是8的倍数。

如果一个Packet是当前Block的最后一个Packet,那么当此Packet发送完毕之后,DataStreamer将会阻塞,直到ResponseProcessor获取了此前已发送的所有Packet的ACK响应为止,接下来关闭ResponseProcessor,并继续为下一个新的Block构建Pipeline。

 

    7) 每个Packet在创建时都会制定一个自增的ID,用来标记Client发送的packet序列,它可以帮助ResponseProcessor来判定ACK属于哪个packet,如果出现乱序,则会导致Client直接异常退出。

 

 

    8) Pipeline中的每个Datanode在接收到数据后,保存并转发给下一个节点依次传递下去,每个Datanode都封装下一个节点返回的ACK状态并依次向上传递,只有Pipeline中所有的节点都保存成功(dfs.replication.min),那么才能认定此次写入是正常的。如果某个datanode发生故障,那么将会导致pipeline被关闭,

那些尚未发送成功的Packet将会添加到queue的顶端;正常的Datanodes将会对Block重新生成标识(重命名),并发送给Namenode,这个动作就回导致那些故障的Datanodes上不完整的blocks将会在数据恢复(或心跳报告)后被删除。Client端将会从Pipeline中移除那些故障的Datanodes,并重建Pipeline,并重发Packets。因为Datanodes故障的原因,可能导致Block的replication不足,Namenode将会在稍后分配其他replication的创建。

 

    9) 对于同一个文件而言,不支持集群中并发的写入,因为这会带来很多问题。

 

    4. hflush/hsync

    看起来很相似,它们的作用都是“flush”。不过需要提醒,在使用FSDataOutputStream或者其封装类时,原生的flush方法并不会触发实际的IO操作。HDFS文件的一致性模型决定,一个文件中正在被写入的block对于read操作而言是不可见的,即使数据已经刷新与存储,原因就是read操作在获取最后一个Block的信息时已经表明了它的offset和length,那么read操作将不能读取超出length部分的数据,即使block已经有了新的数据长度,除非重新读取。

 

    hflush方法将Client缓存的所有数据(packet)立即发送给Datanodes,并阻塞直到它们写入成功为止。hsync方法除了flush数据之外,还额外的包含“sync”指令,即强制Datanode执行一次“fsync”操作,将数据写入磁盘,它比hflush多了一次担保,hsync之后数据一定写入了磁盘中,而hflush只是担保数据已经从Client发送给了Datanodes,但有可能Datanode的缓存(或者磁盘IO缓存)导致这些数据仍没有真正的持久化。hflush之后,可以确保Client端故障不会导致数据丢失,但如果Datanodes失效仍有丢失数据的可能,当FSDataOutputStream关闭时也会额外的执行一次flush操作。

 

    我们通常在写入一定量的数据后,手动调用一次hsync,以减少数据丢失的风险,hsync会触发Namenode更新Block的length信息,此后reader就可以看到最新的数据。 

  • 大小: 162.1 KB
  • 大小: 17 KB
  • 大小: 35.4 KB
  • 大小: 83.6 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics