核心类:
- DataNode (主进程类)
- BlockPoolManager (块池管理类,管理者多个BPOfferService,因为一台DN上可以存储多个NameNode(联邦)的数据)
- BPOfferService (对应着一个NameService,管理一对BPServerActor)
- BPServiceActor (连接NameNode和StandByNameNode,负责发送心跳,汇报数据,接收指令等)
- DataXSeverServer (对外提供独写Block服务)
- TcpPeerServer (DataXCeiverServer内部持有的一个socket服务类)
- DataStorage (每个DN上只持有一个,负责创建和管理DN上的磁盘空间,升级回滚等,内部持有BlockPollSliceStorage列表,使用该对象来进行操作)
- BlockPollSliceStorage (管理单个块池)
- FsDataset (所有对数据块的操作都在这)
- DataBlockScanner (独立线程,定时扫描数据块,检查校验和)
- DirectoryScanner (独立线程,定时扫描数据块,对比内存和磁盘的数据库差异,保持内存和磁盘的同步)
- StorageReceivedDeletedBlocks (用于汇报增量数据块)
- ReceivedDeletedBlockInfo (对Block进行封装,包含了数据块的三个状态:正在接收,已接受,删除)
DataNode逻辑结构:
DataNode:名称节点的进程类,入口就是main方法
org.apache.hadoop.hdfs.server.datanode.DataNode
//入口是main方法,调用链如下:
//main->secureMain->createDataNode->instantiateDataNode->makeInstance->new DataNode->startDataNode
void startDataNode(Configuration conf,
List<StorageLocation> dataDirs,
SecureResources resources
) throws IOException {
//每一台的DN都可以指定多个磁盘当作存储目录
synchronized (this) {
this.dataDirs = dataDirs;
}
//管理整个DN的所有块池的存储空间
storage = new DataStorage();
//初始化DataXCeiverServer,负责接收客户端或则其他dn的独写数据请求
initDataXceiver(conf);
//初始化http服务
startInfoServer(conf);
//初始化ipc服务
initIpcServer(conf);
//创建块池管理器
blockPoolManager = new BlockPoolManager(this);
//根据配置创建多个块池,并注册到对应的namenode
blockPoolManager.refreshNamenodes(conf);
}
org.apache.hadoop.hdfs.server.datanode.BlockPoolManager
refreshNameNode->doRefreshNamenodes
private void doRefreshNamenodes(
Map<String, Map<String, InetSocketAddress>> addrMap) throws IOException {
assert Thread.holdsLock(refreshNamenodesLock);
//addrMap是通过Socket去连接成功的NameService节点名称映射表
Set<String> toRefresh = Sets.newLinkedHashSet();
Set<String> toAdd = Sets.newLinkedHashSet();
Set<String> toRemove;
synchronized (this) {
//第一步,先把需要增加或则修改的nameserviceId放进内存
for (String nameserviceId : addrMap.keySet()) {
if (bpByNameserviceId.containsKey(nameserviceId)) {
toRefresh.add(nameserviceId);
} else {
toAdd.add(nameserviceId);
}
}
//第二部,删除当前拥有但不存在的nameserviceId
toRemove = Sets.newHashSet(Sets.difference(
bpByNameserviceId.keySet(), addrMap.keySet()));
//第三部,开启新的nameservices
if (!toAdd.isEmpty()) {
//循环每个新的nameserviceId,并创建对应的对象为之服务
//一个BPOfferService对应着一个nameservices,管理两个BPServiceActor
//每个BPServiceActore对应着一个NameNode
for (String nsToAdd : toAdd) {
ArrayList<InetSocketAddress> addrs =
Lists.newArrayList(addrMap.get(nsToAdd).values());
BPOfferService bpos = createBPOS(addrs);
bpByNameserviceId.put(nsToAdd, bpos);
offerServices.add(bpos);
}
}
//开启上面创建好的线程
startAll();
}
//第四步,关闭旧的服务nameservice的线程
if (!toRemove.isEmpty()) {
for (String nsToRemove : toRemove) {
BPOfferService bpos = bpByNameserviceId.get(nsToRemove);
bpos.stop();
bpos.join();
// they will call remove on their own
}
}
//第五步,刷新
if (!toRefresh.isEmpty()) {
LOG.info("Refreshing list of NNs for nameservices: " +
Joiner.on(",").useForNull("<default>").join(toRefresh));
for (String nsToRefresh : toRefresh) {
BPOfferService bpos = bpByNameserviceId.get(nsToRefresh);
ArrayList<InetSocketAddress> addrs =
Lists.newArrayList(addrMap.get(nsToRefresh).values());
bpos.refreshNNList(addrs);
}
}
}
接下来就是开启后台线程注册到NameNode
synchronized void startAll() throws IOException {
UserGroupInformation.getLoginUser().doAs(
new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
//调用start方法(非线程的start)
for (BPOfferService bpos : offerServices) {
bpos.start();
}
return null;
}
});
}
org.apache.hadoop.hdfs.server.datanode.BPOfferService
void start() {
for (BPServiceActor actor : bpServices) {
actor.start();
}
}
最终执行的就是BPServiceActor的run方法,他是一个线程,每一个BPServiceActor负责对接一个NameNode
org.apache.hadoop.hdfs.server.datanode.BPServiceActor
public void run() {
LOG.info(this + " starting to offer service");
try {
while (true) {
connectToNNAndHandshake();//连接到NameNode并且注册上去
break;
}
while (shouldRun()) {
//开始循环的执行作业:发送心跳和汇报数据块
offerService();
}
private void connectToNNAndHandshake() throws IOException {
//先拿到namenodeIPC的代理对象
bpNamenode = dn.connectToNN(nnAddr);
//通过调用代理对象的versionRequest方法,去NameNodeRpcServer服务里获取到NameSpaceInfo
NamespaceInfo nsInfo = retrieveNamespaceInfo();
//当第一个BPServiceActor获取到NameSpaceInfo信息后,会把这个对象设置到上层的BPOfferService属性里
//当第二个BPServiceActor获取到NameSpaceInfo信息后, 会进行校验,因为一个BPOfferService管理的两个BPServiceActor对象必须是相同的命名空间
//同时会校验块池ID和集群ID
bpos.verifyAndSetNamespaceInfo(nsInfo);
//然后把自身DataNode信息注册到NameNode上进行管理
register();
}
//这个方法是DataNode和NameNode交互的核心
private void offerService() throws Exception {
//heartBeatInterval 是心跳发送间隔 30秒
//deleteReportInterval 是增量汇报的间隔 100*heartBeatInterval = 5分钟
//blockReportInterval 是全量汇报的间隔 6小时
while (shouldRun()) {
try {
//获取当前那时间
final long startTime = now();
//如果距离上次发送心跳时间已经超过30秒,则发送心跳
if (startTime - lastHeartbeat >= dnConf.heartBeatInterval) {
//重置最后心跳时间
lastHeartbeat = startTime;
if (!dn.areHeartbeatsDisabledForTests()) {
//发送心跳,获取到一堆需要执行的命令
HeartbeatResponse resp = sendHeartBeat();
//如果nn状态发生变化,更新下bpos里管理的namenode和actor的信息
bpos.updateActorStatesFromHeartbeat(
this, resp.getNameNodeHaState());
state = resp.getNameNodeHaState().getState();
//如果最新的心跳返回信息里有滚动升级信息,则进行升级
if (state == HAServiceState.ACTIVE) {
handleRollingUpgradeStatus(resp);
}
//对namenode返回的指令进行执行
long startProcessCommands = now();
if (!processCommand(resp.getCommands()))
continue;
}
}
//如果设置了增量汇报标志或则距离上次增量汇报时间已经超过了五分钟,就开始汇报增量数据块
if (sendImmediateIBR ||
(startTime - lastDeletedReport > dnConf.deleteReportInterval)) {
reportReceivedDeletedBlocks();
lastDeletedReport = startTime;
}
//执行全量汇报
List<DatanodeCommand> cmds = blockReport();
} catch (IOException e) {}
} // while (shouldRun())
} // offerService
至此,DataNode的启动流程大概步骤就解析完毕,总结如下:
1.创建一些后台线程服务如:DataXceiverServer,BlockPoolManager等
2.获取到最新HA所有的NameSpaceInfo,更新到BlockManager管理的BPOfferServer里
3.创建多组BPServiceActor去连接对应的那个NameNode,定时发送心跳和汇报数据,并执行返回指令
4.启动后台服务线程,为其他角色进行服务,如:DataXceiverServer为客户端或则其他DN提供Socket方式读写Block