《Hadoop 2.X HDFS源码剖析》读书笔记(Client)

来源:互联网 发布:java方法的定义 编辑:程序博客网 时间:2024/05/17 09:31

1. DSFClient实现

对于管理文件/目录以及管理与配置HDFS系统这两个功能,DFSClient并不需要与Datanode交互,而是直接通过远程接口ClientProtocol调用Namenode提供的服务即可。而对于文件读写功能,DFSClient除了需要调用ClientProtocol与Namenode交互外,还需要通过流式接口DataTransferProtocol与Datanode交互传输数据。

1.1 构造方法

主要完成两个功能:

  1. 读入配置文件,并初始化以下成员变量。
变量 说明 conf HDFS配置信息。 stats Client状态统计信息,包括Client读、写字节数等。 dtpReplaceDatanodeOnFailure 当Client读写数据时,如果Datanode出现故障,是否进行Datanode替换的策略 localInterfaceAddrs 本地接口地址 readahead 预读取字节数 readDropBehind 读取数据后,是否立即从操作系统缓冲区中删除 writeDropBehind 写数据后,是否立即从操作系统缓冲区中删除 hedgedReadThresholdMillis 保存了触发“hedgedread”机制的时长。当Client发现一个数据块读取操作太慢时,Client会启动另一个并发操作读取数据块的另一个副本,之后Client会返回先完成读取副本的数据。

2. 获取Namenode的RPCProxy引用,供DFSClient远程调用Namenode RPC方法。

1.2 关闭方法

  1. 首先调用closeAllFilesBeingWritten()关闭所有正在进行写操作的IO流。
  2. 将clientRunning标志位置为false,停止DFSClient对外服务。
  3. 停止租约管理器
  4. 最后关闭与Namenode的RPC连接。

1.3 文件系统管理与配置方法

HDFS管理员通过DFSAdmin工具管理与配置HDFS,DFSAdmin也通过持有DistributedFileSystem对象的引用,然后进一步调用DFSClient类提供的方法执行管理与配置操作。

DFSAdmin类直接调用DFSClient对应的方法,再由DFSClient调用ClientProtocol对应的方法。

DFSAdmin参数 DFSClient
ClientProtocol 作用 -report datanodeReport()
getDatanodeReport() 获取当前集群中所有Datanode的信息 -safemode setSafeMode() 进入、离开或者查询安全模式。处于安全模式中的HDFS,命名空间是只读的。 -allowSnapshot allowSnapshot() 开启指定目录的快照(snapshot)功能 -disallowSnapshot disallowSnapshot() 关闭指定目录的快照功能 -saveNamespace saveNamespace() 保存当前Namenode元数据到fsimage -rollEdits rollEdits() 提交当前的edits_inprogress文件 -restoreFailedStorage restoreFailedStorage() 开启或关闭失败存储的恢复 -refreshNodes refreshNodes() 刷新hosts以及exclude文件 -finalizeUpgrade finalizeUpgrade() 提交升级 -rollingUpgrade rollingUpgrade() 进行升级 -clrQuota setQuota() 重置命名空间(Namespace)配额,和setQuota()使用同样的ClientProtocol接口 -setQuota setQuota() 设置命名空间配额 -clrSpaceQuota setQuota() 重置磁盘空间配额,和setSpaceQuota()使用同样的ClientProtocol接口 -setSpaceQuota setQuota() 设置磁盘空间配额 -setBalancerBandwidth setBalancerBandwidth() 设置平衡操作时的带宽

1.4 HDFS文件与目录操作方法

DFSClient的另一个重要功能就是操作HDFS文件与目录。这些操作首先调用checkOpen()检查DFSClient的运行情况,然后调用ClientProtocol对应的RPC方法,触发Namenode更改文件系统目录树。

2. 文件读操作和输入流

2.1 打开文件

Open方法流程图

2.2 读操作(DFSInputStream)

HDFS目前实现的读操作有三个层次,分别是网络读、短路读(short circuit read)以及零拷贝读(zero copy read),它们的读取效率依次递增。

  1. 网络读:网络读是最基本的一种HDFS读,DFSClient和Datanode通过建立Socket连接传输数据。
  2. 短路读:当DFSClient和保存目标数据块的Datanode在同一个物理节点上时,DFSClient可以直接打开数据块副本文件读取数据,而不需要Datanode进程的转发。
  3. 零拷贝读:当DFSClient和缓存目标数据块的Datanode在同一个物理节点上时,DFSClient可以通过零拷贝的方式读取数据块,大大提高了效率。

HdfsDataInputStream.read()方法首先调用HasEnhancedByteBufferAccess.read()方法尝试进行零拷贝读取,如果当前配置不支持零拷贝读取模式,则抛出异常,然后调用ByteBufferUtil.fallbackRead()静态方法退化成短路读或网络读。

3. 文件短路读操作

UNIX提供了一种UNIX Domain Socket进程间通信方式,它使得同一台机器上的两个进程能以Socket的方式通信,并且还可以在进程间传递文件描述符。

客户端向Datanode请求数据时,Datanode会打开块文件和校验和文件,将这两个文件的文件描述符直接传给客户端,而不是将路径传给客户端。客户端接收到这两个文件的文件描述符之后,就可以直接打开文件读取数据了,也就绕开了Datanode进程的转发。因为文件描述符是只读的,所以客户端不能修改文件。同时,由于客户端自身无法访问数据块文件所在的目录,所以它也就不能访问其他不该访问的数据了,保证了读取的安全性。

3.1 短路读共享内存

在DFSClient中,使用ShortCircuitReplica类封装可以进行短路读取的副本。ShortCircuitReplica对象中包含了短路读取副本的数据块文件输入流、校验文件输入流、短路读取副本的共享内存中的槽位(slot)以及副本的引用次数等信息。DFSClient会持有一个ShortCircuitCache对象缓存并管理所有的ShortCircuitReplica对象,DFSClient从ShortCircuitCache获得了ShortCircuitReplica的引用之后,就可以构造BlockReaderLocal对象进行本地读取操作了。

3.2 DataTransferProtocol

DataTransferProtocol底层是基于Socke流的,而当DFSClient和Datanode在同一台物理机器上时,DataTransferProtocol底层的Socke将会是DomainSocket,使用DomainSocket的DataTransferProtocol可以在Socket流中传递文件描述符。

3.3 DFSClient短路读操作流程

客户端首先会调用ShortCircuitCache.fetchAndCreate()方法尝试从缓存中获取ShortCircuitReplica对象,如果缓存中没有这个对象,fetchAndCreate()方法会调用create()方法创建一个新的ShortCircuitReplica对象,并在创建成功之后将这个对象缓存。

create()方法首先会调用DFSClientShmManager.allocSlot()方法尝试在已有共享内存中获取一个槽位,如果在DFSClientShmManager管理的共享内存中都没有槽位了,allocSlot()方法会调用DataTransferProtocol.requestShortCircuitShm()方法申请一段新的共享内存,并构造一个DfsClientShm对象管理这段共享内存,然后allocSlot()方法会在这段共享内存中获取一个存放当前副本状态的槽位并返回。处理好槽位后,create()方法会调用DataTransferProtocol.requestShortCircuitFds()方法获取副本的文件描述符并构建IO流,然后构建ShortCircuitReplica对象并返回。

3.4 Datanode短路读操作流程

Datanode响应短路读请求的相关类主要有两个:

  1. RegisteredShm:ShortCircuitShm的子类,ShortCircuitRegistry的内部类,用来描述Datanode侧的一段共享内存。
  2. ShortCircuitRegistry:管理Datanode侧的所有共享内存。客户端使用DataTransferProtocol申请新的共享内存段以及释放已有的共享内存段时,是由ShortCircuitRegistry类来执行对应的操作的。

4. 文件写操作与输出流

4.1 创建文件

DFSClient.create()方法调用流程图

4.2 写操作(DSFOutputStream)

DFSOutputStream中使用Packet类来封装一个数据包。每个数据包中都包含若干个校验块,以及校验块对应的校验和。

数据包发送流程图如下:

数据包发送流程图

4.3 追加写操作

DistributedFileSystem.append()方法就是用于打开一个已有的HDFS文件,并获取追加写操作的HdfsDataOutputStream对象。

DistributedFileSystem.append()方法调用了DFSClient.callAppend()方法获取输出流对象。callAppend()方法首先通过ClientProtocol.append()方法获取文件最后一个数据块的位置信息,如果文件的最后一个数据块已经写满则返回null。然后callAppend()方法会调用DFSOutputStream.newStreamForAppend()方法创建到文件最后一个数据块的输出流对象。获取文件租约,并将新构建的DFSOutputStream包装为HdfsDataOutputStream对象,然后返回。

4.4 租约相关

无论是DFSClient.create()还是DFSClient.append()方法创建DFSOutputStream对象时,都会调用beginFileLease()方法获取HDFS文件租约,并开始执行租约更新操作。beginFileLease()方法会调用LeaseRenewer.put()方法将打开的HDFS文件以及输出流作为一个记录,放入DFSClient的租约管理器LeaseRenewer对象中。

4.5 关闭输出流

DFSOutputStream.close()方法首先调用flushBuffer()将输出流中缓存的数据写入数据包,然后将数据流中没有发送的数据包放入dataQueue队列中,最后构造一个新的空数据包用于标识数据块已经全部写完。

close()方法调用flushInternal()方法确认所有数据包已经成功地写入数据流管道后,就可以调用closeThreads()关闭DataStreamer线程了。之后close()方法会调用completeFile()方法向Namenode提交这个文件,completeFile()方法底层调用了ClientProtocol.complete()方法。最后close()方法会释放当前文件的租约。

5. HDFS常用工具

5.1 FsShell实现

FsShell类是HDFS中用于执行文件系统Shell命令的类,这个类的入口方法是main(),是典型的基于ToolRunner实现的应用。FsShell.run()会调用CommandFactory.getInstance()从参数中解析出命令对应的Command对象,然后在Command对象上调用run()方法执行对应的操作。

5.2 DFSAdmin实现

DFSAdmin类就是用于dfsadmin工具的类,它继承自FsShell,也通过ToolRunner.run()执行DFSAdmin.run()方法,不同的是DFSAdmin.run()会直接在方法体内判断命令并调用相应的处理方法。

0 0