第七章:小朱笔记hadoop之源码分析-hdfs分析
第四节:namenode分析
4.1 namenode启动过程分析
org.apache.hadoop.hdfs.server.namenode.main 方法是系统的入口,它会调用 createNameNode 创建 NameNode 实例。 createNameNode 分析命令行参数,如果是 FORMAT 戒 FINALIZE,调用对应的方法后退出,如果是其他的参数,将创建NameNode 对象。NameNode的构造函数会调initialize,初始化NameNode的成员发量,包括创建 RPC 服务器,初始化FSNamesystem,初始化RPC服务器和回收站线程。
创建的服务如下:
服务 类
server ipc.RPC.Server
serviceRpcServer ipc.RPC.Server
HttpServer http.HttpServer
Trash Emptier fs.Trash.Trash.Emptier
hbthread hdfs.server.namenode.FSNamesystem.HeartbeatMonitor
lmthread hdfs.server.namenode.LeaseManager.Monitor
replthread hdfs.server.namenode.FSNamesystem.ReplicationMonitor
dnthread hdfs.server.namenode.DecommissionManager.Monitor
初始化 name-node 入口:
/**
* Initialize name-node.
*
* @param conf the configuration
*/
private void initialize(Configuration conf) throws IOException {
InetSocketAddress socAddr = NameNode.getAddress(conf);
//从配置conf中获取到Namenode服务器所使用的Socket地址 读取fs.default.name的值,获取hdfs集群的地址
UserGroupInformation.setConfiguration(conf);
//设置用户权限信息
//如果dfs.namenode.keytab.file存在,并且kerberos已经开启,则调用UserGroupInformation.loginUserFromKeytab进行登陆
//登陆使用dfs.namenode.kerberos.principal作为用户名,否则使用当前linux的user作为用户。
SecurityUtil.login(conf, DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY,
DFSConfigKeys.DFS_NAMENODE_USER_NAME_KEY, socAddr.getHostName());
//获取namenode同时处理请求的数量配置,默认为10 服务器上处理器Handler线程的数量
int handlerCount = conf.getInt("dfs.namenode.handler.count", 10);
// set service-level authorization security policy
//如果授权配置(hadoop.security.authorization)开启了,则刷新授权策略
if (serviceAuthEnabled = conf.getBoolean(ServiceAuthorizationManager.SERVICE_AUTHORIZATION_CONFIG, false)) {
//默认会重新加载hadoop-policy.xml中的acl配置,加载完成后acl配置保存在
ServiceAuthorizationManager.refresh(conf, new HDFSPolicyProvider());
}
//创建服务指标,用于观察namenode服务状态
myMetrics = NameNodeInstrumentation.create(conf);// 初始化NameNodeMetrics
//启动FSNamesystem,启动FSNamesystem时,会启动各种thread执行namenode职责
//1.init FSNamesystem
this.namesystem = new FSNamesystem(this, conf);
//如果安全机制开启
if (UserGroupInformation.isSecurityEnabled()) {
//启动守护线程每5秒执行一次ExpiredTokenRemover
namesystem.activateSecretManager();
}
//2.init namenode-datanode,namenode-snn RPC Server
//创建rpc服务器,如果dfs.namenode.servicerpc-address配置项存在,则用来作为服务器地址
InetSocketAddress dnSocketAddr = getServiceRpcServerAddress(conf);
if (dnSocketAddr != null) {
int serviceHandlerCount =
conf.getInt(DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_KEY,
DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_DEFAULT);
this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(),
dnSocketAddr.getPort(), serviceHandlerCount,
false, conf, namesystem.getDelegationTokenSecretManager());
this.serviceRPCAddress = this.serviceRpcServer.getListenerAddress();
setRpcServiceServerAddress(conf);
}
//3.init namenode-client RPC Server
this.server = RPC.getServer(this, socAddr.getHostName(),socAddr.getPort(), handlerCount, false, conf, namesystem.getDelegationTokenSecretManager());
// The rpc-server port can be ephemeral... ensure we have the correct info
this.serverAddress = this.server.getListenerAddress();
FileSystem.setDefaultUri(conf, getUri(serverAddress));
LOG.info("Namenode up at: " + this.serverAddress);
//4. start Http Server
startHttpServer(conf);
//5. start namenode-client RPC Server
this.server.start();
if (serviceRpcServer != null) {
serviceRpcServer.start();
//6. start namenode-datanode,namenode-snn RPC Server
}
//7.start TrashEmptier
//启动垃圾清理守护线程,读取fs.trash.interval的值作为两次清理的时间间隔。默认每60分钟清理一次
startTrashEmptier(conf);
}
(1)初始化FSNamesystem
启动FSNamesystem,启动FSNamesystem时,会启动各种thread执行namenode职责。
FSNamesystem
的构造函数会调用 initialize
方法,
去初始化上面我们分析过的一堆成员发量。几个重要的步骤包括加载 FSImage
editlog
设置系统为安全模式,
初始化各个工作线程和 HTTP
服务器。
FSNamesystem重要属性:
// Default initial capacity and load factor of map
public static final int DEFAULT_INITIAL_MAP_CAPACITY = 16;
public static final float DEFAULT_MAP_LOAD_FACTOR = 0.75f;
private boolean isPermissionEnabled;//是否打开权限检查,可以通过配置项dfs.permissions来设置。
//本地文件的用户文件属主和文件组,可以通过hadoop.job.ugi设置,如果没有设置,那么将使用启动HDFS的用户(通过whoami获得)和该用户所在的组(通过groups获得)作为值
private UserGroupInformation fsOwner;
private String supergroup;//对应配置项dfs.permissions.supergroup,应用在defaultPermission中,是系统的超级组。
//缺省权限,缺省用户为fsOwner,缺省用户组为supergroup,缺省权限为0777,可以通过dfs.upgrade.permission修改。
private PermissionStatus defaultPermission;
// FSNamesystemMetrics counter variables
//系统总容量/已使用容量/剩余容量
private long capacityTotal = 0L, capacityUsed = 0L, capacityRemaining = 0L;
//系统总连接数,根据DataNode心跳信息跟新
private int totalLoad = 0;
boolean isAccessTokenEnabled;
BlockTokenSecretManager accessTokenHandler;
private long accessKeyUpdateInterval;
private long accessTokenLifetime;
// Scan interval is not configurable.
private static final long DELEGATION_TOKEN_REMOVER_SCAN_INTERVAL =
TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS);
private DelegationTokenSecretManager dtSecretManager;
//分别是成员变量pendingReplications(正在复制的数据块),
//neededReplications(需要复制的数据块)的大小,
//scheduledReplicationBlocksCount是当前正在处理的复制工作数目
volatile long pendingReplicationBlocksCount = 0L;
volatile long corruptReplicaBlocksCount = 0L;
volatile long underReplicatedBlocksCount = 0L;
volatile long scheduledReplicationBlocksCount = 0L;
volatile long excessBlocksCount = 0L;
volatile long pendingDeletionBlocksCount = 0L;
//
// Stores the correct file name hierarchy
//指向系统使用的FSDirectory对象。
public FSDirectory dir;
//
// Mapping: Block -> { INode, datanodes, self ref }
// Updated only in response to client-sent information.
//
final BlocksMap blocksMap = new BlocksMap(DEFAULT_INITIAL_MAP_CAPACITY,
DEFAULT_MAP_LOAD_FACTOR);
//
// Store blocks-->datanodedescriptor(s) map of corrupt replicas
//
//保存损坏(如:校验没通过)的数据块到对应DataNode的关系,CorruptReplicasMap类图如下,类只有一个成员变量,
//保存Block到一个DatanodeDescriptor的集合的映射和这个映射上的一系列操作
//
public CorruptReplicasMap corruptReplicas = new CorruptReplicasMap();
/**
* Stores the datanode -> block map.
* <p>
* Done by storing a set of {@link DatanodeDescriptor} objects, sorted by
* storage id. In order to keep the storage map consistent it tracks
* all storages ever registered with the namenode.
* A descriptor corresponding to a specific storage id can be
* <ul>
* <li>added to the map if it is a new storage id;</li>
* <li>updated with a new datanode started as a replacement for the old one
* with the same storage id; and </li>
* <li>removed if and only if an existing datanode is restarted to serve a
* different storage id.</li>
* </ul> <br>
* The list of the {@link DatanodeDescriptor}s in the map is checkpointed
* in the namespace image file. Only the {@link DatanodeInfo} part is
* persistent, the list of blocks is restored from the datanode block
* reports.
* <p>
* Mapping: StorageID -> DatanodeDescriptor
*
* 保存了StorageID >> DatanodeDescriptor的映射,用于保证DataNode使用的Storage的一致性。
*/
NavigableMap<String, DatanodeDescriptor> datanodeMap = new TreeMap<String, DatanodeDescriptor>();
//
// Keeps a Collection for every named machine containing
// blocks that have recently been invalidated and are thought to live
// on the machine in question.
// Mapping: StorageID -> ArrayList<Block>
//
// 保存了每个DataNode上无效但还存在的数据块(StorageID >> ArrayList<Block>)。
// 保存了每个DataNode上有效,但需要删除的数据块(StorageID >> TreeSet<Block>),这种情况可能发生在一个DataNode故障后恢复后,
// 上面的数据块在系统中副本数太多,需要删除一些数据块。
private Map<String, Collection<Block>> recentInvalidateSets = new TreeMap<String, Collection<Block>>();
//
// Keeps a TreeSet for every named node. Each treeset contains
// a list of the blocks that are "extra" at that location. We'll
// eventually remove these extras.
// Mapping: StorageID -> TreeSet<Block>
//保存Datanode上有效但需要删除的数据块(StorageID -> TreeSet<Block>)比如一个Datanode故障恢复后,上面的数据块在系统中副本数太多,需要删除一些数据块。
//
Map<String, Collection<Block>> excessReplicateMap = new TreeMap<String, Collection<Block>>();
Random r = new Random();
/**
* Stores a set of DatanodeDescriptor objects.
* This is a subset of {@link #datanodeMap}, containing nodes that are
* considered alive.
* The {@link HeartbeatMonitor} periodically checks for outdated entries,
* and removes them from the list.
* 所有目前活着的DataNode,线程HeartbeatMonitor会定期检查
*/
ArrayList<DatanodeDescriptor> heartbeats = new ArrayList<DatanodeDescriptor>();
/**
* Store set of Blocks that need to be replicated 1 or more times.
* Set of: Block
*
* 需要进行复制的数据块。UnderReplicatedBlocks的类图如下,它其实是一个数组,数组的下标是优先级(0的优先级最高,如果数据块只有一个副本,它的优先级是0),
* 数组的内容是一个Block集合。UnderReplicatedBlocks提供一些方法,对Block进行增加,修改,查找和删除。
*
*/
private UnderReplicatedBlocks neededReplications = new UnderReplicatedBlocks();
// We also store pending replication-orders.保存正在复制的数据块的相关信息
private PendingReplicationBlocks pendingReplications;
public LeaseManager leaseManager = new LeaseManager(this);
//
// Threaded object that checks to see if we have been
// getting heartbeats from all clients.
//
Daemon hbthread = null; // HeartbeatMonitor thread 对应DataNode心跳检查
public Daemon lmthread = null; // LeaseMonitor thread 租约检查
Daemon smmthread = null; // SafeModeMonitor thread 安全模式检查
public Daemon replthread = null; // Replication thread 数据块复制
private ReplicationMonitor replmon = null; // Replication metrics
private volatile boolean fsRunning = true; //系统运行标志
long systemStart = 0;//系统启动时间
// The maximum number of replicates we should allow for a single block
private int maxReplication;
// How many outgoing replication streams a given node should have at one time
private int maxReplicationStreams;
// MIN_REPLICATION is how many copies we need in place or else we disallow the write
private int minReplication;
// Default replication
private int defaultReplication;
// Variable to stall new replication checks for testing purposes
private volatile boolean stallReplicationWork = false;
// heartbeatRecheckInterval is how often namenode checks for expired datanodes
private long heartbeatRecheckInterval;
// heartbeatExpireInterval is how long namenode waits for datanode to report
// heartbeat
private long heartbeatExpireInterval;
//replicationRecheckInterval is how often namenode checks for new replication work
private long replicationRecheckInterval;
// default block size of a file
private long defaultBlockSize = 0;
// allow appending to hdfs files
private boolean supportAppends = true;
/**
* Last block index used for replication work.
*/
private int replIndex = 0; ///和neededReplications配合,记录下一个进行复制的数据块位置。
private long missingBlocksInCurIter = 0;
private long missingBlocksInPrevIter = 0;
public static FSNamesystem fsNamesystemObject;
/** NameNode RPC address */
private InetSocketAddress nameNodeAddress = null; // TODO: name-node has this field, it should be removed here
//安全模式是这样一种状态,系统处于这个状态时,不接受任何对名字空间的修改,同时也不会对数据块进行复制或删除数据块。
//NameNode启动的时候会自动进入安全模式,同时也可以手工进入(不会自动离开)。系统启动以后,DataNode会报告目前它拥有的数据块的信息,
//当系统接收到的Block信息到达一定门槛,同时每个Block都有dfs.replication.min个副本后,系统等待一段时间后就离开安全模式。这个门槛定义的参数包括:
//dfs.safemode.threshold.pct:接受到的Block的比例,缺省为95%,就是说,必须DataNode报告的数据块数目占总数的95%,才到达门槛;
//dfs.replication.min:缺省为1,即每个副本都存在系统中;
//dfs.replication.min:等待时间,缺省为0,单位秒。
private SafeModeInfo safeMode; // safe mode information
//保存了主机名(String)到DatanodeDescriptor数组的映射(Host2NodesMap唯一的成员变量为HashMap<String,DatanodeDescriptor[]> map,它的方法都是对这个map进行操作)。
private Host2NodesMap host2DataNodeMap = new Host2NodesMap();
// datanode networktoplogy
// 定义了HDFS的网络拓扑,网络拓扑对应选择数据块副本的位置很重要。如在一个层次型的网络中,接到同一个交换机的两个节点间的网络速度,
// 会比跨越多个交换机的两个节点间的速度快,但是,如果某交换机故障,那么它对接到它上面的两个节点会同时有影响,但跨越多个交换机的两个节点,这种影响会小得多
NetworkTopology clusterMap = new NetworkTopology();
private DNSToSwitchMapping dnsToSwitchMapping;
// for block replicas placement
//用于为数据块备份选择目标,例如,用户写文件时,需要选择一些DataNode,作为数据块的存放位置,这时候就利用它来选择目标地址。
//chooseTarget是ReplicationTargetChooser中最重要的方法,
//它通过内部的一个NetworkTopology对象,计算出一个DatanodeDescriptor数组,该数组就是选定的DataNode,同时,顺序就是最佳的数据流顺序
ReplicationTargetChooser replicator;
//保存了系统中允许/不允许连接到NameNode的机器列表
private HostsFileReader hostsReader;
// 线程句柄,该线程用于检测DataNode上的Decommission进程。例如,某节点被列入到不允许连接到NameNode的机器列表中(HostsFileReader)
// 那么,该节点会进入Decommission状态,它上面的数据块会被复制到其它节点,复制结束后机器进入DatanodeInfo.AdminStates.DECOMMISSIONED,这台机器就可以从HDFS中撤掉。
private Daemon dnthread = null;
//系统能拥有的INode最大数(配置项dfs.max.objects,0为无限制)。
private long maxFsObjects = 0; // maximum number of fs objects
/**
* The global generation stamp for this file system.
*/
private final GenerationStamp generationStamp = new GenerationStamp();
// Ask Datanode only up to this many blocks to delete.
// 发送给DataNode删除数据块消息中,能包含的最大数据块数。比方说,如果某DataNode上有250个Block需要被删除,而这个参数是100,
// 那么一共会有3条删除数据块消息消息,前面两条包含了100个数据块,最后一条是50个。
int blockInvalidateLimit = DFSConfigKeys.DFS_BLOCK_INVALIDATE_LIMIT_DEFAULT;
// precision of access times.
//用于控制文件的access时间的精度,也就是说,小于这个精度的两次对文件访问,后面的那次就不做记录了。
private long accessTimePrecision = 0;
private String nameNodeHostName;
FSNamesystem初始化方法:
private void initialize(NameNode nn, Configuration conf) throws IOException {
this.systemStart = now();
setConfigurationParameters(conf);
//读取配置文件
dtSecretManager = createDelegationTokenSecretManager(conf);
//读取dfs.namenode.delegation.token的相关配置
this.nameNodeAddress = nn.getNameNodeAddress();
this.registerMBean(conf); // register the MBean for the FSNamesystemStutus
this.dir = new FSDirectory(this, conf);
StartupOption startOpt = NameNode.getStartupOption(conf);
this.dir.loadFSImage(getNamespaceDirs(conf),
getNamespaceEditsDirs(conf), startOpt);
//加载namenode持久化在硬盘的信息
long timeTakenToLoadFSImage = now() - systemStart;
LOG.info("Finished loading FSImage in " + timeTakenToLoadFSImage + " msecs");
NameNode.getNameNodeMetrics().setFsImageLoadTime(timeTakenToLoadFSImage);
//加载安全模式信息,进入安全模式
this.safeMode = new SafeModeInfo(conf);
//将block的总数设置给safemode
setBlockTotal();
pendingReplications = new PendingReplicationBlocks(
conf.getInt("dfs.replication.pending.timeout.sec",
-1) * 1000L);
if (isAccessTokenEnabled) {
accessTokenHandler = new BlockTokenSecretManager(true,
accessKeyUpdateInterval, accessTokenLifetime);
}
//启动心跳监控的线程
this.hbthread = new Daemon(new HeartbeatMonitor());
//启动文件租约管理监控的线程
this.lmthread = new Daemon(leaseManager.new Monitor());
//启动副本监控的线程
this.replmon = new ReplicationMonitor();
this.replthread = new Daemon(replmon);
hbthread.start();
lmthread.start();
replthread.start();
//读取主机信息黑白名单
this.hostsReader = new HostsFileReader(conf.get("dfs.hosts",""),
conf.get("dfs.hosts.exclude",""));
//启动退役节点监控的线程
this.dnthread = new Daemon(new DecommissionManager(this).new Monitor(
conf.getInt("dfs.namenode.decommission.interval", 30),
conf.getInt("dfs.namenode.decommission.nodes.per.interval", 5)));
dnthread.start();
this.dnsToSwitchMapping = ReflectionUtils.newInstance(
conf.getClass("topology.node.switch.mapping.impl", ScriptBasedMapping.class,
DNSToSwitchMapping.class), conf);
/* If the dns to swith mapping supports cache, resolve network
* locations of those hosts in the include list,
* and store the mapping in the cache; so future calls to resolve
* will be fast.
*/
if (dnsToSwitchMapping instanceof CachedDNSToSwitchMapping) {
dnsToSwitchMapping.resolve(new ArrayList<String>(hostsReader.getHosts()));
}
InetSocketAddress socAddr = NameNode.getAddress(conf);
this.nameNodeHostName = socAddr.getHostName();
//将这个类注册到监控系统
registerWith(DefaultMetricsSystem.INSTANCE);
}
从代码中可以看到,FSNamesystem的initialize方法主要的工作是:通过读取配置文件设置成员变量;创建FSDirectory并 loadFSImage;设置系统安全模式;启动一系列的后台线程monitorDaemon。其中对 loadFSImage非常重要。
Namenode会将HDFS的文件和目录元数据存储在一个叫fsimage的二进制文 件中,每次保存fsimage之后到下次保存之间的所有hdfs操作,将会记录在editlog文件中,当editlog达到一定的大小(bytes,由 fs.checkpoint.size参数定义)或从上次保存过后一定时间段过后(sec,由fs.checkpoint.period参数定 义),namenode会重新将内存中对整个HDFS的目录树和文件元数据刷到fsimage文件中。Namenode就是通过这种方式来保证HDFS中 元数据信息的安全性。当namenode重启加载fsimage时,就是按照如下格式协议从文件流中加载元数据信息。
从fsimag的存储格式可以看出,fsimage保存有如下信息:
写道
1. image head,其中包含:
a) imgVersion(int):当前image的版本信息
b) namespaceID(int):用来确保别的HDFS instance中的datanode不会误连上当前NN。
c) numFiles(long):整个文件系统中包含有多少文件和目录
d) genStamp(long):生成该image时的时间戳信息。
2.文件或目录的源数据信息,如果是目录,则包含以下信息:
a)path(String):该目录的路径,如”/user/zhuhui/data”
b)replications(short):副本数(目录虽然没有副本,但这里记录的目录副本数也为3)
c)mtime(long):该目录的修改时间的时间戳信息
d)atime(long):该目录的访问时间的时间戳信息
e)blocksize(long):目录的blocksize都为0
f)numBlocks(int):实际有多少个文件块,目录的该值都为-1,表示该item为目录
g)nsQuota(long):namespace Quota值,若没加Quota限制则为-1
h)dsQuota(long):disk Quota值,若没加限制则也为-1
i)username(String):该目录的所属用户名
j)group(String):该目录的所属组
k)permission(short):该目录的permission信息,如644等,有一个short来记录。
3.如果是文件,则还会额外包含如下信息:
a)blockid(long):属于该文件的block的blockid,
b)numBytes(long):该block的大小
c)genStamp(long):该block的时间戳
a) imgVersion(int):当前image的版本信息
b) namespaceID(int):用来确保别的HDFS instance中的datanode不会误连上当前NN。
c) numFiles(long):整个文件系统中包含有多少文件和目录
d) genStamp(long):生成该image时的时间戳信息。
2.文件或目录的源数据信息,如果是目录,则包含以下信息:
a)path(String):该目录的路径,如”/user/zhuhui/data”
b)replications(short):副本数(目录虽然没有副本,但这里记录的目录副本数也为3)
c)mtime(long):该目录的修改时间的时间戳信息
d)atime(long):该目录的访问时间的时间戳信息
e)blocksize(long):目录的blocksize都为0
f)numBlocks(int):实际有多少个文件块,目录的该值都为-1,表示该item为目录
g)nsQuota(long):namespace Quota值,若没加Quota限制则为-1
h)dsQuota(long):disk Quota值,若没加限制则也为-1
i)username(String):该目录的所属用户名
j)group(String):该目录的所属组
k)permission(short):该目录的permission信息,如644等,有一个short来记录。
3.如果是文件,则还会额外包含如下信息:
a)blockid(long):属于该文件的block的blockid,
b)numBytes(long):该block的大小
c)genStamp(long):该block的时间戳
当该文件对应的numBlocks数不为1,而是大于1时,表示该文件对应有多个block信息,此时紧接在该fsimage之后的就会有多个 blockid,numBytes和genStamp信息。因此,在namenode启动时,就需要对fsimage按照如下格式进行顺序的加载,以将 fsimage中记录的HDFS元数据信息加载到内存中。
namenode在加载fsimage过程其实非常简单,就是从fsimage中不停的顺序读取文件和目录的元数据信息,并在内存中构建整个 namespace,此时BlocksMap中每个block对应的datanodes 列表暂时为空。当fsimage加载完毕后,整个HDFS的目录结构在内存中就已经初始化完毕,所缺的就是每个文件对应的block对应的 datanode列表信息。这些信息需要从datanode的blockReport中获取,所以加载fsimage完毕后,namenode进程进入 rpc等待状态,等待所有的datanodes发送blockReports。
HDFS将fsimage和edits文件内容读入内存,进行合并,填充与INode相关的元数据结构,并将新的元数据内存镜像导出,在磁盘上形成新的 fsimage和edits文件。HDFS同时还接收DataNode的心跳信息,填充与Block和Data Node相关的元数据结构。
saveNameSpace将元数据写入到磁盘,具体操作步骤:首先将current目录重命名为lastcheckpoint.tmp;然后在创建新的 current目录,并保存文件;最后将lastcheckpoint.tmp重命名为privios.checkpoint。
namenode在加载fsimage过程其实非常简单,就是从fsimage中不停的顺序读取文件和目录的元数据信息,并在内存中构建整个 namespace,此时BlocksMap中每个block对应的datanodes 列表暂时为空。当fsimage加载完毕后,整个HDFS的目录结构在内存中就已经初始化完毕,所缺的就是每个文件对应的block对应的 datanode列表信息。这些信息需要从datanode的blockReport中获取,所以加载fsimage完毕后,namenode进程进入 rpc等待状态,等待所有的datanodes发送blockReports。
HDFS将fsimage和edits文件内容读入内存,进行合并,填充与INode相关的元数据结构,并将新的元数据内存镜像导出,在磁盘上形成新的 fsimage和edits文件。HDFS同时还接收DataNode的心跳信息,填充与Block和Data Node相关的元数据结构。
saveNameSpace将元数据写入到磁盘,具体操作步骤:首先将current目录重命名为lastcheckpoint.tmp;然后在创建新的 current目录,并保存文件;最后将lastcheckpoint.tmp重命名为privios.checkpoint。
在加载完成时,初始化了一下四个守护线程:
//启动心跳监控的线程
this.hbthread = new Daemon(new HeartbeatMonitor());
//启动文件租约管理监控的线程
this.lmthread = new Daemon(leaseManager.new Monitor());
//启动副本监控的线程
this.replmon = new ReplicationMonitor();
this.replthread = new Daemon(replmon);
hbthread.start();
lmthread.start();
replthread.start();
//启动退役节点监控的线程
this.dnthread = new Daemon(new DecommissionManager(this).new Monitor(
conf.getInt("dfs.namenode.decommission.interval", 30),
conf.getInt("dfs.namenode.decommission.nodes.per.interval", 5)));
dnthread.start();
(2)初始化RPC服务器
//init namenode-client RPC Server
this.server = RPC.getServer(this, socAddr.getHostName(),socAddr.getPort(), handlerCount, false, conf, namesystem.getDelegationTokenSecretManager());
public synchronized void start() {
responder.start();
listener.start();
handlers = new Handler[handlerCount];
for (int i = 0; i < handlerCount; i++) {
handlers[i] = new Handler(i);
handlers[i].start();
}
}
server启动了三个(假设handlercount为1)线程,这三个线程分别为listener,responder,handler ,这三个线程之间是有职责关系的:Hadoop的Server采用了Java的NIO,这样的话就不需要为每一个socket连接建立一个线程,读取 socket上的数据。在Server中,只需要一个线程,就可以accept新的连接请求和读取socket上的数据,这个线程,就是 Listener。请求处理线程一般有多个,它们都是Server.Handle类的实例。它们的run方法循环地取出一个Server.Call,调用 Server.call方法,搜集结果并串行化,然后将结果放入Responder队列中。对于处理完的请求,需要将结果写回去,同样,利用NIO,只需 要一个线程,相关的逻辑在Responder里。
(3)启动Trash Emptier 线程
//启动垃圾清理守护线程,读取fs.trash.interval的值作为两次清理的时间间隔。默认每60分钟清理一次
startTrashEmptier(conf);
private void startTrashEmptier(Configuration conf) throws IOException {
this.emptier = new Thread(new Trash(conf).getEmptier(), "Trash Emptier");
this.emptier.setDaemon(true);
this.emptier.start();
}