为了阅读分析org.apache.hadoop.hdfs.DFSClient类的实现,我们还需要对Hadoop定义的通信协议簇来进行了解,因为DFSClient所执行的操作都是基于协议的规定来实现的,了解协议的内容可能会对DFSClient实例连接到HDFS执行的任务更好理解一点。
首先,了解一下Hadoop定义的通信双方需要遵循的一组协议,下面是协议接口的继承层次关系,并作简单介绍:
- 。org.apache.hadoop.ipc.VersionedProtocol
- 。org.apache.hadoop.hdfs.protocol.ClientProtocol
- 。org.apache.hadoop.hdfs.protocol.ClientDatanodeProtocol
- 。org.apache.hadoop.hdfs.server.protocol.NamenodeProtocol
- 。org.apache.hadoop.hdfs.server.protocol.DatanodeProtocol
- 。org.apache.hadoop.hdfs.server.protocol.InterDatanodeProtocol
- 。org.apache.hadoop.ipc.VersionedProtocol 。org.apache.hadoop.hdfs.protocol.ClientProtocol 。org.apache.hadoop.hdfs.protocol.ClientDatanodeProtocol 。org.apache.hadoop.hdfs.server.protocol.NamenodeProtocol 。org.apache.hadoop.hdfs.server.protocol.DatanodeProtocol 。org.apache.hadoop.hdfs.server.protocol.InterDatanodeProtocol
上述协议接口的基本含义简述如下:
VersionedProtocol协议是Hadoop的最顶层协议接口的抽象;
ClientProtocol协议是用户进程(包括客户端进程与Datanode进程)与Namenode进程之间进行通信所使用的协议,例如,(1)客户端进程需要向Datanode数据结点复制数据块,需要与Namenode进程通信,获取Datanode结点列表;(2)Datanode进程向Namenode进程发送心跳状态报告和块状态报告需要与Namenode进程交互;
ClientDatanodeProtocol协议是客户端进程与Datanode进程之间进行通信所使用的协议;
DatanodeProtocol协议是当Datanode进程需要与NameNode进程进行通信是需要基于此协议,例如发送心跳报告和块状态报告;
InterDatanodeProtocol协议是Datanode进程之间进行通信的协议,例如客户端进程启动复制数据块,此时可能需要在Datanode结点之间进行块副本的流水线复制操作。
下面我们一个一个来看:
该接口的定义如下:
- package org.apache.hadoop.ipc;
-
- import java.io.IOException;
-
-
-
-
-
- public interface VersionedProtocol {
-
-
-
-
-
-
-
- public long getProtocolVersion(String protocol, long clientVersion) throws IOException;
- }
- package org.apache.hadoop.ipc;import java.io.IOException;public interface VersionedProtocol { public long getProtocolVersion(String protocol, long clientVersion) throws IOException;}
该接口是所有与Hadoop RPC调用相关的协议的最高层抽象。
当客户端进程想要与Namenode进程进行通信的时候,需要通过org.apache.hadoop.hdfs.DistributedFileSystem类,基于ClientProtocol协议来实现交互过程。用户代码通过ClientProtocol协议,可以操纵HDFS的目录命名空间、打开与关闭文件流等。
该接口中定义了一个static final的versionID字段,如下所示:
-
-
-
-
- public static final long versionID = 41L;
- public static final long versionID = 41L;
下面介绍,该接口协议中定义的与文件内容相关的操作:
1、获取块位置信息
定义的方法为getBlockLocations,该方法的参数如下:
-
-
-
-
-
- public LocatedBlocks getBlockLocations(String src, long offset, long length) throws IOException;
- public LocatedBlocks getBlockLocations(String src, long offset, long length) throws IOException;
getBlockLocations方法的主要功能说明如下:
获取到指定范围内,指定文件的所有块的位置信息。 其中,调用该方法返回的org.apache.hadoop.hdfs.protocol.LocatedBlocks对象包含的内容为:文件长度、组成文件的块及其存储位置(所在的Datanode数据结点)。对于每个块所在DataNode结点的位置,市基于“到文件系统客户端地址的距离最近”的原则进行了排序。文件系统客户端必须与制定的Datanode进行交互,才能获取到所需要的实际的块据块。
2、在命名空间中创建一个文件入口(entry)
定义的方法为create,该方法的参数如下所示:
-
-
-
-
-
-
-
-
- public void create(
- String src,
- FsPermission masked,
- String clientName,
- boolean overwrite,
- short replication,
- long blockSize) throws IOException;
- public void create( String src, FsPermission masked, String clientName, boolean overwrite, short replication, long blockSize) throws IOException;
create方法的主要功能说明如下:
在命名空间中创建一个文件入口。该方法将创建一个由src路径指定的空文件,该路径src应该反映了从root目录开始的一个完整路径名称。从客户端的角度,Namenode并没有“当前”目录的概念。一旦文件创建成功,该文件就是可见的,并可以被其它客户端来执行读操作。但是,其它客户端不能够对该文件进行删除、重命名、重写,而这些操作只有在该文件被完全或明确指定为租约到期,才可以执行。
每个块都具有最大长度限制,如果客户端想要创建多个块,可以调用addBlock(String, String)方法来实现。
3、追加写文件操作
定义的方法为append,该方法 的参数如下所示:
-
-
-
-
- public LocatedBlock append(String src, String clientName) throws IOException;
- public LocatedBlock append(String src, String clientName) throws IOException;
append方法的主要功能说明如下:
向文件src中追加写入内容,返回一个org.apache.hadoop.hdfs.protocol.LocatedBlock对象,该对象封装了Block(Hadoop基本文件块)和DatanodeInfo[](Datanode的状态信息),通过追加写操作后的返回信息,可以定位到追加写入最后部分块的信息。
4、设置副本因子
定义的方法为setReplication,该方法 的参数如下所示:
-
-
-
-
-
-
-
-
-
-
-
- public boolean setReplication(String src, short replication) throws IOException;
- public boolean setReplication(String src, short replication) throws IOException;
setReplication方法的主要功能说明如下:
该方法为一个指定的文件修改块副本因子。Namenode会为指定文件设置副本因子,但是,不期望在调用该方法的过程修改实际块的副本因子,而是由后台块维护进程来执行:如果当前副本因子小于设置的新副本因子,需要增加一些块副本,如果当前副本因子大于设置的新副本因子,就会删除一些块副本。
另外,基于文件操作的方法,还有如下一些:
-
-
-
- public void setPermission(String src, FsPermission permission) throws IOException;
-
-
-
-
- public void setOwner(String src, String username, String groupname) throws IOException;
-
-
-
-
- public void abandonBlock(Block b, String src, String holder) throws IOException;
-
-
-
-
- public LocatedBlock addBlock(String src, String clientName) throws IOException;
-
-
-
-
- public boolean complete(String src, String clientName) throws IOException;
-
-
-
-
-
- public void reportBadBlocks(LocatedBlock[] blocks) throws IOException;
-
-
-
-
- public boolean rename(String src, String dst) throws IOException;
-
-
-
-
- public boolean delete(String src) throws IOException;
-
-
-
-
- public boolean delete(String src, boolean recursive) throws IOException;
-
-
-
-
- public boolean mkdirs(String src, FsPermission masked) throws IOException;
-
-
-
-
- public FileStatus[] getListing(String src) throws IOException;
- public void setPermission(String src, FsPermission permission) throws IOException; public void setOwner(String src, String username, String groupname) throws IOException; public void abandonBlock(Block b, String src, String holder) throws IOException; public LocatedBlock addBlock(String src, String clientName) throws IOException; public boolean complete(String src, String clientName) throws IOException; public void reportBadBlocks(LocatedBlock[] blocks) throws IOException; public boolean rename(String src, String dst) throws IOException; public boolean delete(String src) throws IOException; public boolean delete(String src, boolean recursive) throws IOException; public boolean mkdirs(String src, FsPermission masked) throws IOException; public FileStatus[] getListing(String src) throws IOException;
下面是接口ClientProtocol定义的与系统的管理相关的方法的定义:
1、监听客户端
方法renewLease定义为:
- public void renewLease(String clientName) throws IOException;
- public void renewLease(String clientName) throws IOException;
该方法主要是Namenode监听到某个客户端发送的心跳状态,如果再一段时间内无法获取到某个客户端的心跳状态,很可能是该客户端因为某些异常崩溃掉了,被加上了不能继续正常工作的状态锁,Namenode进程会周期地调用该方法来确定指定的客户端clientName是否确实挂掉了,如果又重新接收到该客户端发送的心跳报告,则为该客户端进行解锁操作,恢复其正常的工作。
2、获取文件系统的状态统计数据
定义的方法为getStats,如下所示:
-
-
-
-
-
-
-
-
-
-
-
- public long[] getStats() throws IOException;
- public long[] getStats() throws IOException;
上面返回的long[]对应定义的如下几个常量:
- public int GET_STATS_CAPACITY_IDX = 0;
- public int GET_STATS_USED_IDX = 1;
- public int GET_STATS_REMAINING_IDX = 2;
- public int GET_STATS_UNDER_REPLICATED_IDX = 3;
- public int GET_STATS_CORRUPT_BLOCKS_IDX = 4;
- public int GET_STATS_MISSING_BLOCKS_IDX = 5;
- public int GET_STATS_CAPACITY_IDX = 0; public int GET_STATS_USED_IDX = 1; public int GET_STATS_REMAINING_IDX = 2; public int GET_STATS_UNDER_REPLICATED_IDX = 3; public int GET_STATS_CORRUPT_BLOCKS_IDX = 4; public int GET_STATS_MISSING_BLOCKS_IDX = 5;
3、安全模式开关操作
定义的方法为setSafeMode,如下所示:
- public boolean setSafeMode(FSConstants.SafeModeAction action) throws IOException;
- public boolean setSafeMode(FSConstants.SafeModeAction action) throws IOException;
通过调用该方法可以执行如下操作:进入安全模式、退出安全模式、获取安全模式。
当一个Namenode启动时候,首先会进入到安全模式这种特殊状态,在该状态下不能够进行数据块的复制操作。Namenode接收HDFS集群中所有的Datanode结点的心跳状态报告和数据块状态报告,根据状态报告来决定是否开始进入工作状态。如果某些Datanode结点发送的心跳状态报告不正常或者根本无从接收到,Namenode会将这些Datanode视为故障结点,在进入工作状态的时候,将这些故障结点排除在工作集群之外。如果某些Datanode结点上的数据块状态报告存在问题,会根据要求进行处理,比如某Datanode据结点上数据块的块副本未达到副本因子,则会在退出安全模式之后,进行块复制操作,满足副本要求。
4、保存命名空间映像
定义的方法为saveNamespace,如下所示:
-
-
-
-
-
-
- public void saveNamespace() throws IOException;
- public void saveNamespace() throws IOException;
5、持久化文件系统元数据
定义的方法为metaSave,如下所示:
-
-
-
- public void metaSave(String filename) throws IOException;
- public void metaSave(String filename) throws IOException;
这里,引用Hadoop架构设计要点中的一段文字来描述一下,文件系统元数据的持久化:
“Namenode上保存着HDFS的名字空间。对于任何对文件系统元数据产生修改的操作,Namenode都会使用一种称为EditLog的事务日志记录下来。例如,在HDFS中创建一个文件,Namenode就会在Editlog中插入一条记录来表示;同样地,修改文件的副本系数也将往Editlog插入一条记录。Namenode在本地操作系统的文件系统中存储这个Editlog。整个文件系统的名字空间,包括数据块到文件的映射、文件的属性等,都存储在一个称为FsImage的文件中,这个文件也是放在Namenode所在的本地文件系统上。
Namenode在内存中保存着整个文件系统的名字空间和文件数据块映射(Blockmap)的映像。这个关键的元数据结构设计得很紧凑,因而一个有4G内存的Namenode足够支撑大量的文件和目录。当Namenode启动时,它从硬盘中读取Editlog和FsImage,将所有Editlog中的事务作用在内存中的FsImage上,并将这个新版本的FsImage从内存中保存到本地磁盘上,然后删除旧的Editlog,因为这个旧的Editlog的事务都已经作用在FsImage上了。这个过程称为一个检查点(checkpoint)。在当前实现中,检查点只发生在Namenode启动时,在不久的将来将实现支持周期性的检查点。
Datanode将HDFS数据以文件的形式存储在本地的文件系统中,它并不知道有关HDFS文件的信息。它把每个HDFS数据块存储在本地文件系统的一个单独的文件中。Datanode并不在同一个目录创建所有的文件,实际上,它用试探的方法来确定每个目录的最佳文件数目,并且在适当的时候创建子目录。在同一个目录中创建所有的本地文件并不是最优的选择,这是因为本地文件系统可能无法高效地在单个目录中支持大量的文件。当一个Datanode启动时,它会扫描本地文件系统,产生一个这些本地文件对应的所有HDFS数据块的列表,然后作为报告发送到Namenode,这个报告就是块状态报告。”
另外,ClientProtocol协议接口还定义了其它一些方法,如下所示:
-
-
-
-
- public DatanodeInfo[] getDatanodeReport(FSConstants.DatanodeReportType type) throws IOException;
-
-
-
-
- public long getPreferredBlockSize(String filename) throws IOException;
-
-
-
-
- public void refreshNodes() throws IOException;
-
-
-
-
-
- public void finalizeUpgrade() throws IOException;
-
-
-
-
- public UpgradeStatusReport distributedUpgradeProgress(UpgradeAction action) throws IOException;
-
-
-
-
- public FileStatus getFileInfo(String src) throws IOException;
-
-
-
-
- public ContentSummary getContentSummary(String path) throws IOException;
-
-
-
-
-
- public void fsync(String src, String client) throws IOException;
-
-
-
-
- public void setTimes(String src, long mtime, long atime) throws IOException;
-
-
-
-
- public void setQuota(String path, long namespaceQuota, long diskspaceQuota) throws IOException;
- public DatanodeInfo[] getDatanodeReport(FSConstants.DatanodeReportType type) throws IOException; public long getPreferredBlockSize(String filename) throws IOException; public void refreshNodes() throws IOException; public void finalizeUpgrade() throws IOException; public UpgradeStatusReport distributedUpgradeProgress(UpgradeAction action) throws IOException; public FileStatus getFileInfo(String src) throws IOException; public ContentSummary getContentSummary(String path) throws IOException; public void fsync(String src, String client) throws IOException; public void setTimes(String src, long mtime, long atime) throws IOException; public void setQuota(String path, long namespaceQuota, long diskspaceQuota) throws IOException;
该协议接口定义了次级Namenode(Secondary NameNode)与Namenode进行通信所需进行的操作。其中,Secondary NameNode是一个用来辅助Namenode的服务器端进程,主要是对映像文件执行特定的操作,另外还包括获取指定Datanode上块的操作。
该接口定义的操作如下所示:
-
-
-
- public BlocksWithLocations getBlocks(DatanodeInfo datanode, long size) throws IOException;
-
-
-
-
- public long getEditLogSize() throws IOException;
-
-
-
-
-
- public CheckpointSignature rollEditLog() throws IOException;
-
-
-
-
- public void rollFsImage() throws IOException;
- public BlocksWithLocations getBlocks(DatanodeInfo datanode, long size) throws IOException; public long getEditLogSize() throws IOException; public CheckpointSignature rollEditLog() throws IOException; public void rollFsImage() throws IOException;
当客户端进程需要与Datanode进程进行通信的时候,需要基于该协议。该协议接口定义数据块恢复的方法,如下所示:
- public interface ClientDatanodeProtocol extends VersionedProtocol {
- public static final Log LOG = LogFactory.getLog(ClientDatanodeProtocol.class);
-
-
-
-
- public static final long versionID = 3L;
-
-
-
-
-
-
-
- LocatedBlock recoverBlock(Block block, boolean keepLength, DatanodeInfo[] targets) throws IOException;
- }
- public interface ClientDatanodeProtocol extends VersionedProtocol { public static final Log LOG = LogFactory.getLog(ClientDatanodeProtocol.class); public static final long versionID = 3L; LocatedBlock recoverBlock(Block block, boolean keepLength, DatanodeInfo[] targets) throws IOException;}
该协议用于一个DFS Datanode用户与Namenode进行通信的协议。该接口定义如下所示:
- package org.apache.hadoop.hdfs.server.protocol;
-
- import java.io.*;
-
- import org.apache.hadoop.hdfs.protocol.Block;
- import org.apache.hadoop.hdfs.protocol.DatanodeID;
- import org.apache.hadoop.hdfs.protocol.LocatedBlock;
- import org.apache.hadoop.ipc.VersionedProtocol;
-
- public interface DatanodeProtocol extends VersionedProtocol {
-
-
-
- public static final long versionID = 19L;
-
-
- final static int NOTIFY = 0;
- final static int DISK_ERROR = 1;
- final static int INVALID_BLOCK = 2;
-
-
-
-
- final static int DNA_UNKNOWN = 0;
- final static int DNA_TRANSFER = 1;
- final static int DNA_INVALIDATE = 2;
- final static int DNA_SHUTDOWN = 3;
- final static int DNA_REGISTER = 4;
- final static int DNA_FINALIZE = 5;
- final static int DNA_RECOVERBLOCK = 6;
-
-
-
-
-
-
- public DatanodeRegistration register(DatanodeRegistration registration) throws IOException;
-
-
-
- public DatanodeCommand[] sendHeartbeat(DatanodeRegistration registration, long capacity, long dfsUsed, long remaining, int xmitsInProgress, int xceiverCount) throws IOException;
-
-
-
-
- public DatanodeCommand blockReport(DatanodeRegistration registration, long[] blocks) throws IOException;
-
-
-
-
- public void blockReceived(DatanodeRegistration registration, Block blocks[], String[] delHints) throws IOException;
-
-
-
-
- public void errorReport(DatanodeRegistration registration, int errorCode, String msg) throws IOException;
-
- public NamespaceInfo versionRequest() throws IOException;
-
-
-
-
- UpgradeCommand processUpgradeCommand(UpgradeCommand comm) throws IOException;
-
-
-
-
- public void reportBadBlocks(LocatedBlock[] blocks) throws IOException;
-
-
-
-
- public long nextGenerationStamp(Block block) throws IOException;
-
-
-
-
- public void commitBlockSynchronization(Block block, long newgenerationstamp, long newlength, boolean closeFile, boolean deleteblock, DatanodeID[] newtargets) throws IOException;
- }
- package org.apache.hadoop.hdfs.server.protocol;import java.io.*;import org.apache.hadoop.hdfs.protocol.Block;import org.apache.hadoop.hdfs.protocol.DatanodeID;import org.apache.hadoop.hdfs.protocol.LocatedBlock;import org.apache.hadoop.ipc.VersionedProtocol;public interface DatanodeProtocol extends VersionedProtocol { public static final long versionID = 19L;
一般来说Namenode不直接对Datanode进行RPC调用,如果一个Namenode需要与Datanode通信,唯一的方式就通过调用该协议接口定义的方法。
该协议用于Datanode进程之间进行通信。
该接口的定义如下所示:
- package org.apache.hadoop.hdfs.server.protocol;
-
- import java.io.IOException;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.apache.hadoop.hdfs.protocol.Block;
- import org.apache.hadoop.ipc.VersionedProtocol;
-
- public interface InterDatanodeProtocol extends VersionedProtocol {
- public static final Log LOG = LogFactory.getLog(InterDatanodeProtocol.class);
-
-
-
-
- public static final long versionID = 3L;
-
-
-
-
- BlockMetaDataInfo getBlockMetaDataInfo(Block block) throws IOException;
-
-
-
-
- void updateBlock(Block oldblock, Block newblock, boolean finalize) throws IOException;
- }
- package org.apache.hadoop.hdfs.server.protocol;import java.io.IOException;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.hadoop.hdfs.protocol.Block;import org.apache.hadoop.ipc.VersionedProtocol;public interface InterDatanodeProtocol extends VersionedProtocol { public static final Log LOG = LogFactory.getLog(InterDatanodeProtocol.class); public static final long versionID = 3L; BlockMetaDataInfo getBlockMetaDataInfo(Block block) throws IOException; void updateBlock(Block oldblock, Block newblock, boolean finalize) throws IOException;}
上面,对不同进程之间通信所使用的协议的接口进行了阅读分析,应该能够了解每种协议应用的场景,如果想要基于某种场景实现端端通信,可以选择合适的协议接口来实现它。