# ######################### 关于 HDFS Append ####################
(1) 背景
早期的HDFS版本不支持HDFS append功能. 当一个文件被关闭时, 这个文件就不能再被修改了. 如果要修改的话, 就只能重读此文件并将数据写入一个新的文件. 虽然这种方式很简单, 但和map/reduce的需求却是非常match的. map/reduce jobs会向HDFS写入多个结果文件, 这种方式比修改已经存在的输入文件效率要高很多. 而且map/reduce模型通常是不会修改输入文件的.
在早期的HDFS(0.15之前), 如果一个文件没有被成功的关闭(通过FSDataOutputStream.close())之前,这个文件在NameSpace中是不可见的. 如果client在close文件之前失效或
者是调用close()方法时抛出了异常, 这个文件就永远是不可见的. 恢复文件的唯一方法就是从头重写. 实事上map/reduce是非常适合这种文件读写风格的. 例如: map/reduce job>的某个Task失败, 只需重新运行一次Task即可. 因为map/reduce jobs在启动之前将作业切成了很多细小的Task, 各个Task之间是没有任何关联, 而且一个task失败后重新运行的开销也非常的低.
(2) Append的第一次尝试
当然map/reduce只是架构在HDFS之上的一个应用, 其它应用并不是完全没有Append需求的. 直到发布了hadoop-0.15.0这个版本, 打开的文件在namespace中才变得可见了.也就>是说在文件没有close之前, 其它client是可以看到这个文件的. 这就是说在没有关闭文件之前client挂掉, 这个文件也还是会被保留在namespace中的. 当然这个版本没有去解决让正在写入的数据尽快变得可见的问题.参考: https://issues.apache.org/jira/browse/HADOOP-1708
与此同时, HADOOP-89这个ISSUE还解决了让正在写入文件也可以被其它clients读到,(但是只有最后一个被全部成功写入的块是可见的). 也就是说在文件写入过程中有部分是可见的. 这使得外部可以探知一个文件写入的进度. 当然也可以使用hadoop fs -tail 的方式依次读取己成功写入的block.
参考: https://issues.apache.org/jira/browse/HADOOP-89
(3) 更进一步的需求
对于某些应用来说, HDFS提供的API还不是足够强大. 例如, 对于某些数据库(HBase)希望将Transaction Log以非常可靠的方式写入到HDFS, 但是HDFS并没有提供. 对于HBase这样的应用, 需要有类似于sync()这样的API来保证所有的Transactoin Log记录被成功的持久化到HDFS中(类似于 POSIX's fsync), 如果应用挂掉了, 它仍然可以恢复已经flush的Transaction Log记录以便回放日志文件, 并且还能重新以 append 模式打开Transaction
Log文件追加新的数据.
同样, HDFS没有办法满足以上持久化日志文件写入的这种需求. 因为类似于HBase这样的应用无法容忍因为client失效而导致最后一个block中的所有日志记录全部丢失.
终于在2007年8月HDFS Append功能的实现重新提上了日程(https://issues.apache.org/jira/browse/HADOOP-1700), 并且在2008年7月HDFS Append功能终于随hadoop-0.19.0这个版本发布了.
为了实现HDFS Append功能免不了要修改很多HDFS的核心代码. 例如, 在准备进行append之前, HDFS Blocks是不可以修改的. 然后当开始对一个文件进行Append时, 最后一个block就需要变得可以被修改, 在修改的过程中需要一些方法来更新block的GenerationStamp, 以保证在修改最后一个块的的过程中, 如果在某个Datanode挂掉, 这个Block就必须被认为是拥有一个过期的版本号(GenerationStamp),不然就会出现不一致的情况.细节可以关注:HADOOP-1700.
(4) Append的终极版本
在HADOOP-1700被committed后, 2008年10月有人提出了Append中的有一个功能并没有生效: Reader Client不能读到已经被writer flushed的数据(HDFS-200).并且还出现了一些其它相关的问题: 例如: 当用户从0.17这个版本升级到0.19这个版本时,Datanode应该删掉tmp目录中的文件; Namenode在块复制/删除坏块是限入了死循环; FSNameSystem#addStoredBlock方法没有正确处理块长度不一致的问题; 还有BlockReport时应该对比GenerationStamp.......因为这些问题的存在,
在发布HADOOP-0.19.1版本时append功能又被disabled掉了. 紧接着在0.20.0这个版本中 dfs.support.append 配置项默认都被设置为了false.(HADOOP-5332), 所以在线上生产环境尽量不能打开 dfs.support.append功能, 因为在这个版本中append功能是非常不稳定的. 除非是在测试环境中你才可以打开这个功能.
因为这一系列的问题, 社区的developers不得不重新看待Append这个功能了, Append功能实际上是非常复杂的. 与此同事社区重新开了一个ISSUE: HDFS-265 "Revisit append". 并且提交了新的设计文档和测试计划.
另外部分hadoop committer在Y!的办公室专门举办了一个会议来讨论appends相关的需求. 并且重新给sync定义了一个语义上更加精确的名字: hflush. hflush会保证数据被flush到所有的datanodes, 但是不保证数据会被flush到操作系统的buffers或持久化到硬盘. 注: hflush应该是代表hadoop flush.
# ############################################################################################################################################
(1) 实现HDFS Append挑战有:
a) 一致性: 在同一时间在进行写操作的文件的最后一个Block被写入了不同数量的字节, 那么HDFS怎么保证读取一致性? 甚至在出现异时一致性问题又怎么保证?
b) 错误恢复: 当一个错误发生的时候, 恢复工作不仅仅是简单的丢掉最后一个Block, 而是至少需要保证已经hflushed的字节还可以被正确的读到.
c) 实现append功能需要重新考虑BlockReport, blockWrite, block状态改变, Replication等方面, 需要改动很多HDFS核心代码;
d) 既然是要实现Append, 那就肯定要保证Append数据是可靠的, 即保证数据写入是成功的, 所以提供Append功能的文件系统必须要提供sync功能.
(2) HDFS Append的最实质的需求:
a) 支持一个wirter和多个readers访问同一个文件;
b) 在一个文件关闭之前,writer新写入的数据可以被其它reader读取;
c) writer通过调用flush可以保证数据安全的持久化到磁盘;
d) writer可以重复频繁的调用flush写入少量的数据而不会给HDFS带来一些不当的压力;
e) 保证flush后的数据能被readers读到, 当然readers要在writer flush之后打开文件才行;
f) 保证HDFS永远不会丢失数据(silently).
(3) HDFS Append设计不包含以下目标:
a) 在数据未持久化之前, 其它readers可以读取到writer写入的数据; 当然HDFS会尽最大的努力快速的进行数据持久化;
b) 一个flush调用不保证那些既存的readers可以读到刚刚flush的数据;
# ####################### Stale Replica Deletion(陈旧/过期副本删除) ###############
当Datanode失效时, 此Datanode上的Block副本可能会丢失一些更新, 从而导致副本过期. 因而检测陈旧的副本并阻止这些副本继续提供数据读服务变得非常紧迫.
Namenode通过维护一个全局的block version(GenerationStamp)来解决这个问题, 每次在创更新Block时, 会重新生成GenerationStamp. GenerationStamp是由8字节组成, 并且是全局递增的.如果write Client在flushing数据到Datanode(s)时碰到了某些错误,也必需要生成一个新的GenerationStamp.
# ####################### GenerationStamp ###############################
(1) GenerationStamp存在的两个原因
a) 检测过期副本;
b) 当Dead Datanode在过了很长一段时间后又重新加入集群时,可以通过GenerationStamp检测pre-historic副本;
(2) 在以下情形下需要生成GenerationStamp
a) 创建一个新文件;
b) 当client append 或 truncate 一个已经存在的文件;
c) 当client在写入数据到Datanode(s)时碰到错误或异常时需要申请一个新的GenerationStamp;
d) Namenode开始对一个文件进行lease recovery时;
# ####################### (Failure Modes and Recovery)故障情况及恢复 ###############################
(1) 本次设计将会处理以下故障情况:
# client在写block时挂掉;
# client写入数据时, pipeline中的Datanode挂掉;
# Client写入数据时, Namenode挂掉;
# The Namenode may encounter multiple attempts to restart before a successful restart occurs;
(2) append的数据写入流程:
a) The Writer Client 请求Namenode创建一个新的文件或者打开一个已经存在的文件进行appending. Namenode为新写入的Block生成一个新的blockId和一个新的GenerationStamp. 新的BlockGenerationStamp是根据一个Global GenerationStamp+1生成的, 同时还会将Global GenerationStamp存储到Transaction log中.同时在BlocksMap中还记录blockId, block
locations和block generationstamp. 以上事情完成后,Namenode返回blockGenerationStamp和block locations给client.
b) Client发送blockId和BlockGenerationStamp给pipeline中的所有Datanodes. Datanodes接到请求后将会为给定的blockId创建一个block(if necessary), 同时将BlockGenerationStamp持久化到和block关联的meta-file中.要注意的是, block是直接在数据存储目录创建的, 而不是在临时目录下创建的.[如果在这个阶段出现error, 请看c_1].做完这些>时情后, Client将会通知Namenode持久化block
list和blockGenerationStamp.
c) Client开始向pipeline写入数据流. 在pipeline中的每一个Datanode向前一个Datanode汇报是否写入成功, 并且还会将汇报的情况转发给pipeline中的下一个Datanode. 如
果pipeline中的任意一个Datanode出现error, Client将会收到通知并进行如下处理:
c_1) Client在pipeline中移除掉bad Datanode.
c_2) Client向Namenode请求一个新的BlockGenerationStamp. Client还会向Namenode汇报bad Datanode.
c_3) Namenode更新BlocksMap: 在valid block location中移除掉bad Datanode. 然后生成一个新的BlockGenerationStamp存储到in-memory OpenFile结构体中, 然后返
回给Client. (这个OpenFile应该是指的INodeUnderContruction)
c_4) Client收到BlockGenerationStamp后,发送给pipeline宫的所有good datanods.
c_5) 每一个good datanode收到新的BlockGenerationStamp后, 持久化存储到block对应的meta-file.做完这些时情后, Client将会通知Namenode持久化block list和blockGenerationStamp(OpenFile).
c_6) 现在一切又准备ok了, Client可以跳到[c]开始继续写入数据.
d) 当block全部接收成功时, Datanode会发送一个block received信息(包含blockId和BlockGenerationStamp)给Namenode.
e) Namenode将会从Datanode收到block receive的确认.
f) 如果Namenode从Datanode收到一个block receive的确认, 但是如果BlockGenerationStamp小于Namenode存储的BlockGenerationStamp, Namenode将会认为这是一个无效的block, 然后发送一条block delete的消息给datanode.
g) 当写入一个文件完成时The Writer会发送一个close命令给Namenode. Namenode收到命令后向Transaction log写入一个CloseFile的Transaction. CloseFile Transaction>记录包含了被关闭文件的全路径. 最后Namenode验证文件的每一个block的所有副本是否是含有相同的BlockGenerationStamp.
(3) append的数据读取流程:
Client在每次向Datanode发读请求时都会带上GenerationStamp, 如果Datanode上Block的GenerationStamp与Client发送的GenerationStamp不一致时, Datanode将会拒绝提供数据读
服务. 在这种情况下, Client将会转而去其它Datanodes上读取数据. 在重试前Client会重新向Namenode获取block locations和BlockGenerationStamp.
以下将列出Reader Client将会碰到的一些异常:
a) 不存在Writers: 在这种情况下Reader可以直接访问所有文件的内容, 在这种情况下不存在一致性问题题.
b) 有Writer正在写文件:
b_1) The Reader从Namenode获取所有blocks的list. list中的每一个block都包含有与它关联的block locations和Block GenerationStamp. 如果请求的文件正在进行写>入, Namenode只会返回离client最近的Datanode做为block location, 这主要是减少Client读到不一致数据的概率.(在读的过程中切换Datanodes会增加读取不致的概率)
c) 一个Reader已经打开一个文件并且cache住了block locations和BlockGenerationStamps.这时有一个Writer请求打开文件appending..
c_1) writer更新了文件最后一个Block的BlockGenerationStamp, 这时Reader如果尝试去读取最后一个Block将会因为过期的GenerationStamp而读取失败. Client不得不>重新向Namenode获取block locations和BlockGenerationStamp. 和Case_b一样, Namenode仅仅返回primary datanode做为唯一的block locatoin.
d) 一个Reader已经打开一个文件并且cache住了block locations和BlockGenerationStamps.这时有一个Writer请求打开文件truncate..
d_1) 这种情况和Case_c一样.
# ####################### Lease recovery ###############################
当一个文件被打开进行write(or append)时, Namenode会创建一个in-memory lease-record. lease record包含了正在进行写操作的文件名. 如果一个文件的lease过期(过期时间通常是1小时, lease过期一般是由于Client挂掉造成的), Namenode会对这个lease进行lease recovery. Namenode会选择Block size最小的一个块就行恢复(这样设计仅仅是为了减集群的压力), 然后把size更大的block截短;
# ####################### BlockId allocations and Client flushes ###############################
Writer为正在写的文件向Namenode申请一个新的Block. Namenode分配一个新的blockId和一个新的BlockGenerationStamp, 并且插入到BlocksMap中. 并且还会写入一个StoreGenerationStamp和OpenFile transaction到transaction log中. transation log并不需要fluse到磁盘上, 因为万一Namenode crash掉, 正在写入的block应该要被丢掉, 这是可以接受的.而且这个优化是必须的,因为如果每次block
allocation都刷一次磁盘会给namenode带来性能瓶颈. 此外, 我们写入到内存的transaction log很快就可以被写入到磁盘, 因为其它>同步的transaction会引起buffer的log都被写到磁盘.
# ####################### Durable Edits in HBase ###############################
(1) HBase需要HDFS支持Durable
为了提供durability edits保证数据不会丢失, HBase要求HDFS支持sync调用. sync调用会使DFSClient中的pending data全部写入到HDFS pipeline中的所有Datanodes中, 并且还要接收到所有Datanodes的确认信息.(注: sync调用会保证数据已经成功写入到Datanodes中, 但不保证Datanodes中的数据已经写入到操作系统的buffers或者是说已经成功的写入到磁盘)
虽然hadoop-0.19.0 release版本就开始支持append功能了, 但是发布之后由于append功能使HDFS变得非常的不稳定, 出现了很多bug. 所以hadoop-0.19.1又将append功能disable掉了. 并且之后的版本虽然都支持append, 但默认append都是被关闭的, 只有在测试的时候才会打开. 目前只有hadoop-0.20-append/CDH3这两个版本支持append功能. 也就是说如>果不使用这两个版本中的一个HBase都是有数据丢失风险的(是否存在数据丢失风险和hbase版本无关,
不管是使用hbase-0.20或者hadoop-0.90都存在数据丢失风险).
https://issues.apache.org/jira/browse/HDFS-200
http://www.cloudera.com/blog/2009/07/file-appends-in-hdfs/
分享到:
相关推荐
### Apache Hadoop HDFS Append Design #### 设计挑战与解决方案 **1.1 设计挑战** 随着`hflush`功能的引入,Hadoop HDFS(HDFS)面临着一个全新的挑战:如何使未关闭文件的最后一个块对读者可见。这一需求带来了...
### 使用Java API访问HDFS文件的关键知识点 #### 一、HDFS概述 ...以上是关于如何使用Java API操作HDFS文件的主要知识点和示例代码。通过这些基本操作,你可以根据实际需求构建更复杂的文件处理逻辑。
7. 文件追加:HDFS最初设计不支持文件追加,但后来引入了Append API,允许在文件末尾添加数据,尽管这在某些情况下可能不如预想的那样高效。 8. Checksums:为了检测数据错误,HDFS为每个数据块生成校验和,确保...
对于HDFS的写入器,`defaultFS`是HDFS的默认文件系统地址,`path`指定数据保存的目录,`fileName`是生成文件的名称,`fileType`定义文件类型,如文本或二进制,`writeMode`可以是“append”、“overwrite”等。...
例如,`create()`用于新建文件,`append()`用于追加数据,`open()`用于读取文件,`rename()`用于重命名文件或目录,`delete()`用于删除文件或目录。 2. **Java客户端实现**: "webhdfs-java-client-master"中的...
2. **追加内容**:为了向HDFS中的文件追加内容,我们需要打开已存在的文件并使用`FSDataOutputStream`的`append`方法。这将创建一个输出流,所有写入的数据都会追加到文件的末尾。这里我们使用`FileInputStream`读取...
SliveTest是另一种专门用于测试Namenode性能的工具,它通过大量map任务模拟各种RPC操作,如ls(列出文件和目录)、append(追加写文件)、create(创建文件)、delete(删除文件)、mkdir(创建目录)、rename(重命名文件)和...
- HDFS倾向于大文件和批处理操作,写入是append-only模式,不支持文件的随机修改。读取时,数据从最近的DataNode读取,如果需要,会自动处理数据块的复制。 - KFS在设计上也更适合大数据批量处理,但提供了更多的...
### Python 存储数据到 HDFS 的方法及实践 #### 一、HDFS简介与Python接口使用 Hadoop分布式文件系统(HDFS)是Apache Hadoop项目的核心组件之一,主要用于存储大量的数据集,并且能够提供高吞吐量的数据访问能力...
3. 文件追加:可以使用 `hdfs dfs -append` 命令追加文件内容。 4. 文件删除:可以使用 `hdfs dfs -rm` 命令删除文件。 5. 文件夹操作:可以使用 `hdfs dfs -mkdir` 命令创建文件夹,使用 `hdfs dfs -rmdir` 命令...
文件写入或追加内容则涉及`create()`或`append()`方法,使用`DFSOutputStream`进行数据流操作。 6. **安全认证**:在安全模式下,初始化`FileSystem`时需要进行Kerberos认证,这是HDFS确保数据安全的重要机制。...
FSDataOutputStream out = fs.append(filePath); out.writeBytes("Hello, Hadoop!"); out.close(); // 读取数据 FSDataInputStream in = fs.open(filePath); byte[] buffer = new byte[10]; in.read(buffer...
3. **特殊操作兼容**:针对append和随机读取等操作,设计专门的机制,如索引文件(Indexfile),以保持数据的完整性和访问效率。 #### 如何规避风险 为了确保数据安全和系统稳定性,透明压缩技术采取了以下几项...
首先,Ozone不支持如Append、truncate和hflush等操作,这意味着它在处理动态增长的数据流时可能不如HDFS灵活。其次,Ozone的键值对在未完全写入之前不可见,这可能影响到实时数据处理的应用场景。此外,Ozone的RPC...
- 使用`FileSystem`类的`write`和`append`方法进行文件的创建和追加。 - `FileStatus`类获取文件的元数据,如权限、大小、时间戳和路径。 - 对于目录操作,可以使用`mkdirs`创建目录,`delete`配合`recursive`...
例如,使用FileSystem类的open()方法打开文件,create()方法创建文件,append()方法追加内容,以及delete()方法删除文件等。 总的来说,HDFS文件系统通过其独特的读写机制、副本策略和丰富的访问接口,实现了大数据...
【HDFS基础知识】 Hadoop分布式文件系统(HDFS)是Apache Hadoop项目的核心组件,它是一种高度容错性的分布式文件系统,设计用于运行在廉价硬件上。HDFS在Hadoop架构中扮演着存储大量数据的角色,它将大文件分割成...
Sqoop 是一个强大的工具,它在大数据领域中起到了桥梁的作用,允许用户在关系型数据库(如 Oracle)和 Hadoop 分布式文件系统(HDFS)之间进行数据迁移。Hadoop 是一个开源的分布式计算框架,它以其高可靠性、高扩展...
- `writeMode`:写入策略,如"append"表示追加写入,保证文件不冲突。 - `fieldDelimiter`:字段分隔符,需与Hive表一致,以确保数据可被正确读取。 4. **运行JSON文件**:通过DataX的Python脚本`datax.py`执行...
- `writeMode`: 写入模式,设置为"append",表示追加写入。 - `fieldDelimiter`: 字段分隔符,这里使用制表符`\t`。 5. 应用与拓展 了解了MySQL到HDFS的增量同步后,可以进一步研究其他数据库与数据存储的同步,如...