文件写入剖析
接下来我们要看文件是如何写入HDFS的。尽管比较详细,但对于理解数据流还是很有用的,因为它清楚地说明了HDFS的连贯模型(coherency model)。
我们考虑的情况是创建一个新的文件,向其写入数据后关闭该文件。参见图3-3。
|
(点击查看大图)图3-3:客户端对HDFS写入数据 |
客户端通过在DistributedFilesystem中调用create()来创建文件(图3-3的步骤1)。DistributedFilesystem一个RPC去调用名称节点,在文件系统的命名空间中创建一个新的文件,没有块与之相联系(步骤2)。名称节点执行各种不同的检查以确保这个文件不会已经存在,并且客户端有可以创建文件的适当的许可。如果这些检查通过,名称节点就会生成一个新文件的记录;否则,文件创建失败并向客户端抛出一个IOException异常。分布式文件系统返回一个文件系统数据输出流,让客户端开始写入数据。就像读取事件一样,文件系统数据输出流控制一个DFSoutPutstream,负责处理数据节点和名称节点之间的通信。
在客户端写入数据时(步骤3),DFSoutPutstream将它分成一个个的包,写入内部的队列,称为数据队列。数据队列随数据流流动,数据流的责任是根据适合的数据节点的列表来要求这些节点为副本分配新的块。这个数据节点的列表形成一个管 线-- 我们假设这个副本数是3,所以有3个节点在管线中。数据流将包分流给管线中第一个的数据节点,这个节点会存储包并且发送给管线中的第二个数据节点。同样地,第二个数据节点存储包并且传给管线中第三个(也是最后一个)数据节点(步骤4)。
DFSoutputStream也有一个内部的包队列来等待数据节点收到确认,称为确认队列。一个包只有在被管线中所有节点确认后才会被移出确认队列(步骤5)。
如果在有数据写入期间,数据节点发生故障,则会执行下面的操作,当然这对写入数据的客户端而言,是透明的。首先管线被关闭,确认队列中的任何包都会被添加回数据队列的前面,以确保数据节点从失败的节点处是顺流的,不会漏掉任意一个包。当前的块在正常工作的数据节点中被给予一个新的身份并联系名称节点,以便能在故障数据节点后期恢复时其中的部分数据块会被删除。故障数据节点会从管线中删除并且余下块的数据会被写入管线中的两个好的数据节点。名称节点注意到块副本不足时,会在另一个节点上安排创建一个副本。随后,后续的块会继续正常处理。
在一个块被写入期间多个数据节点发生故障的可能性虽然有但很少见。只要dfs.replication.min的副本(默认为1)被写入,写操作就是成功的,并且这个块会在集群中被异步复制,直到满足其目标副本数(dfs.replication的默认设置为3)。
客户端完成数据的写入后,就会在流中调用close()(步骤6)。在向名称节点发送完信息之前,此方法会将余下的所有包放入数据节点管线并等待确认(步骤7)。名称节点已经知道文件由哪些块组成(通过Data streamer询问块分配),所以它只需在返回成功前等待块进行最小量的复制。
副本的放置
名称节点如何选择哪个数据节点来保存副本?我们需要在可靠性与写入带宽和读取带宽之间进行权衡。例如,因为副本管线都在单独一个节点上运行,所以把所有副本都放在一个节点基本上不会损失写入带宽,但这并没有实现真的冗余(如果节点发生故障,那么该块中的数据会丢失)。同样,离架读取的带宽是很高的。另一个极端,把副本放在不同的数据中心会最大限度地增大冗余,但会以带宽为代价。即使在相同的数据中心(所有的Hadoop集群到目前为止都运行在同一个数据中心),也有许多不同的放置策略。其实,Hadoop在发布的0.17.0版中改变了放置策略来帮助保持块在集群间有相对平均的分布(第10章详细说明了如何保持集群的平衡)。
Hadoop的策略是在与客户端相同的节点上放置第一个副本(若客户端运行在
集群之外,就可以随机选择节点,不过系统会避免挑选那些太满或太忙的节点)。
第二个副本被放置在与第一个不同的随机选择的机架上(离架)。第三个副本被放置在与第二个相同的机架上,但放在不同的节点。更多的副本被放置在集群中的随机节点上,不过系统会尽量避免在相同的机架上放置太多的副本。
一旦选定副本放置的位置,就会生成一个管线,会考虑到网络拓扑。副本数为3的管道看起来如图3-4所示。
|
图3-4:一个典型的副本管线 |
总的来说,这样的方法在稳定性(块存储在两个机架中)、写入带宽(写入操作只需要做一个单一网络转换)、读取性能(选择从两个机架中进行读取)和集群中块的分布(客户端只在本地机架写入一个块)之间,进行了较好的平衡。
|