本文尝试分析某大型大数据解决方案公司企业级hadoop源代码,班门弄斧。
本篇文章的重点为hdfs block管理逻辑。
修改点3
@@ -1086,7 +1087,8 @@
if (!namesystem.isPopulatingReplQueues()) {
return;
}
- invalidateBlocks.add(block, datanode, true);
+ // invalidateBlocks.add(block, datanode, true);
+ invalidateBlocks.add(block, datanode, false);
}
首先定位到修改的函数addToInvalidates,该函数将datanode指定的块儿添加到失效块儿数组中。下面为对应代码。
/**
* Adds block to list of blocks which will be invalidated on specified
* datanode and log the operation
*/
void addToInvalidates(final Block block, final DatanodeInfo datanode) {
if (!namesystem.isPopulatingReplQueues()) {
return;
}
invalidateBlocks.add(block, datanode, true);
}
invalidateBlocks用于保存等待删除的数据块副本集合,InvalidateBlocks中的副本来自于corruptReplicas和excessReplicateMap这两个集合。
此处修改主要是改动第三个参数,第三个参数主要控制是否输出log。
这里没增加一个失效快就减少了一次log输出。
log输出属于IO,IO的速度还是比较慢的,而且很容易形成瓶颈。
这里的修改效果需要根据失效块儿的数量级和其占总体IO的比例来看。
修改点4
/**
@@ -1285,15 +1287,23 @@
nodesToProcess = Math.min(nodes.size(), nodesToProcess);
int blockCnt = 0;
- for (DatanodeInfo dnInfo : nodes) {
- int blocks = invalidateWorkForOneNode(dnInfo);
- if (blocks > 0) {
- blockCnt += blocks;
- if (--nodesToProcess == 0) {
- break;
- }
- }
+
+ List<DatanodeInfo> toProcess=new ArrayList();
+ LOG.warn("begain to process invalidate block,number of node:"+nodesToProcess+" nodes:"+nodes+"");
+ for(int nodeCnt = 0; nodeCnt < nodesToProcess; nodeCnt++ ) {
+ toProcess.add(nodes.get(nodeCnt));
}
+ blockCnt += invalidateWorkForOneNode(toProcess);
+
+// for (DatanodeInfo dnInfo : nodes) {
+// int blocks = invalidateWorkForOneNode(dnInfo);
+// if (blocks > 0) {
+// blockCnt += blocks;
+// if (--nodesToProcess == 0) {
+// break;
+// }
+// }
+// }
return blockCnt;
}
@@ -3560,6 +3636,46 @@
return toInvalidate.size();
}
+
+private int invalidateWorkForOneNode(List<DatanodeInfo> dn) {
+ final List<Block> toInvalidate = new ArrayList();
+ namesystem.writeLock();
+ try {
+ // blocks should not be replicated or removed if safe mode is on
+ if (namesystem.isInSafeMode()) {
+ LOG.debug("In safemode, not computing replication work");
+ return 0;
+ }
+ for (DatanodeInfo dni : dn) {
+ try {
+ List ret = invalidateBlocks.invalidateWork(datanodeManager
+ .getDatanode(dni));
+ if (NameNode.stateChangeLog.isInfoEnabled()) {
+ NameNode.stateChangeLog.info("BLOCK* "
+ + getClass().getSimpleName() + ": ask " + dn
+ + " to delete " + ret);
+ }
+ if (ret != null) {
+ toInvalidate.addAll(ret);
+ }
+
+ if (toInvalidate == null) {
+ return 0;
+ }
+ } catch(UnregisteredNodeException une) {
+ return 0;
+ }
+ }
+ } finally {
+ namesystem.writeUnlock();
+ }
+ if (blockLog.isInfoEnabled()) {
+ blockLog.info("BLOCK* " + getClass().getSimpleName()
+ + ": ask " + dn + " to delete " + toInvalidate);
+ }
+ return toInvalidate.size();
+ }
+
boolean isPlacementPolicySatisfied(Block b) {
List<DatanodeDescriptor> liveNodes = new ArrayList<DatanodeDescriptor>();
Collection<DatanodeDescriptor> corruptNodes = corruptReplicas
这个修改比较长,整体来看修改的是computeInvalidateWork方法,该方法主要处理失效块的删除。
它首先随机出一组datanode,然后遍历调用invalidateWorkForOneNode进行失效块的删除,最终返回删除块的数量。下面为computeInvalidateWork源码
/**
* Schedule blocks for deletion at datanodes
* @param nodesToProcess number of datanodes to schedule deletion work
* @return total number of block for deletion
*/
int computeInvalidateWork(int nodesToProcess) {
final List<DatanodeInfo> nodes = invalidateBlocks.getDatanodes();
Collections.shuffle(nodes);
nodesToProcess = Math.min(nodes.size(), nodesToProcess);
int blockCnt = 0;
for(int nodeCnt = 0; nodeCnt < nodesToProcess; nodeCnt++ ) {
blockCnt += invalidateWorkForOneNode(nodes.get(nodeCnt));
}
return blockCnt;
}
graph LR
computeDatanodeWork-->computeInvalidateWork
ReplicationMonitor-->computeDatanodeWork
BlockManager的ReplicationMonitor线程会定期(配置文件)执行删除操作,
每次删除时ReplicationMonitor线程都会从InvalidateBlocks中选出nodeToProcess个Datanode执行删除操作,
然后再从每个Datanode上选出limit个副本删除。
这里所做的修改主要针对invalidateWorkForOneNode方法,该方法原来只能进行一个节点的删除工作,
我们对该方法进行了重载,重载的方法可以为一次处理多个datanode节点。
那现在的区别在哪呢,我们先分析原版的invalidateWorkForOneNode方法。
该方法首先对namesystem加锁,然后判断namesystem是否处在安全模式,
如果没有处在安全模式,则调用invalidateBlocks.invalidateWork对dn进行实际的块删除工作。
删除完毕后,需要将namesystem解锁。
重载前后的主要区别在于加锁的次数,修改前每个dn处理单独加锁,修改后是集体加锁。
锁是有代价的,过于频繁的加锁会造成cpu的浪费。但是如果锁内的操作过长,也会影响其他线程的响应速度。
这里的修改可以通过调整一次处理的dn数目(配置文件),来达到加解锁的最优。
这里的点就是dn的数目如何来确定,(配置文件)
下面为方法源码。
/**
* Get blocks to invalidate for <i>nodeId</i>
* in {@link #invalidateBlocks}.
*
* @return number of blocks scheduled for removal during this iteration.
*/
private int invalidateWorkForOneNode(DatanodeInfo dn) {
final List<Block> toInvalidate;
namesystem.writeLock();
try {
// blocks should not be replicated or removed if safe mode is on
if (namesystem.isInSafeMode()) {
LOG.debug("In safemode, not computing replication work");
return 0;
}
try {
toInvalidate = invalidateBlocks.invalidateWork(datanodeManager.getDatanode(dn));
if (toInvalidate == null) {
return 0;
}
} catch(UnregisteredNodeException une) {
return 0;
}
} finally {
namesystem.writeUnlock();
}
if (NameNode.stateChangeLog.isInfoEnabled()) {
NameNode.stateChangeLog.info("BLOCK* " + getClass().getSimpleName()
+ ": ask " + dn + " to delete " + toInvalidate);
}
return toInvalidate.size();
}
我们分析下重载后的invalidateWorkForOneNode方法,该方法首先对namesystem加锁,然后判断namesystem是否处在安全模式,如果没有处在安全模式,则遍历dn数组,调用invalidateBlocks.invalidateWork对dn进行实际的块删除工作。删除完毕后,需要将namesystem解锁。和
下面为重载后的源代码。
/**
* Get blocks to invalidate for <i>nodeId</i> in {@link #invalidateBlocks}.
*
* @return number of blocks scheduled for removal during this iteration.
*/
private int invalidateWorkForOneNode(List<DatanodeInfo> dn) {
final List<Block> toInvalidate = new ArrayList();
namesystem.writeLock();
try {
for (DatanodeInfo dni : dn) {
// blocks should not be replicated or removed if safe mode is on
if (namesystem.isInSafeMode()) {
LOG.debug("In safemode, not computing replication work");
return 0;
}
try {
List ret = invalidateBlocks.invalidateWork(datanodeManager
.getDatanode(dni));
if (NameNode.stateChangeLog.isInfoEnabled()) {
NameNode.stateChangeLog.info("BLOCK* "
+ getClass().getSimpleName() + ": ask " + dn
+ " to delete " + ret);
}
if (ret != null) {
toInvalidate.addAll(ret);
}
if (toInvalidate == null) {
return 0;
}
} catch (UnregisteredNodeException une) {
return 0;
}
}
} finally {
namesystem.writeUnlock();
}
return toInvalidate.size();
}
修改点5
@@ -2312,6 +2322,72 @@
}
return storedBlock;
}
+
+ Random rand=new Random();
+ /*
+ enum ProcessType {TO_ADD,TO_INVA,TO_CORR,TO_UC,NULL};
+ private BlockInfo processReportedBlock(final DatanodeDescriptor dn,
+ final String storageID, final Block block,
+ final ReplicaState reportedState, DatanodeDescriptor delHintNode)
+ throws IOException {
+ if (rand.nextInt() % 1000 == 0)
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Reported block " + block + " on " + dn + " size "
+ + block.getNumBytes() + " replicaState = "
+ + reportedState);
+ }
+ if (shouldPostponeBlocksFromFuture
+ && namesystem.isGenStampInFuture(block)) {
+ queueReportedBlock(storageInfo, block, reportedState,
+ QUEUE_REASON_FUTURE_GENSTAMP);
+ return null;
+ }
+
+ // find block by blockId
+ BlockInfo storedBlock = blocksMap.getStoredBlock(block);
+ if (storedBlock == null) {
+ Block nb = new Block(block);
+ if (invalidateBlocks.blockSet.contains(nb) || rand.nextBoolean()) {
+ return null;
+ }
+
+
+ addToInvalidates(nb, dn);
+ return null;
+ }
+ BlockUCState ucState = storedBlock.getBlockUCState();
+
+
+ if (invalidateBlocks.blockSet.contains(block)) {
+ return storedBlock;
+ }
+
+ BlockToMarkCorrupt c = checkReplicaCorrupt(block, reportedState,
+ storedBlock, ucState, dn);
+ if (c != null) {
+ if (shouldPostponeBlocksFromFuture) {
+ queueReportedBlock(dn, storageID, storedBlock, reportedState,
+ QUEUE_REASON_CORRUPT_STATE);
+ } else {
+ markBlockAsCorrupt(c, dn, storageID);
+ }
+ return storedBlock;
+ }
+ if (isBlockUnderConstruction(storedBlock, ucState, reportedState)) {
+ addStoredBlockUnderConstruction(new StatefulBlockInfo(
+ (BlockInfoUnderConstruction) storedBlock, block,
+ reportedState), dn, storageID);
+ return storedBlock;
+ }
+
+ if (reportedState == ReplicaState.FINALIZED
+ && (storedBlock.findDatanode(dn) < 0 || corruptReplicas
+ .isReplicaCorrupt(storedBlock, dn))) {
+ addStoredBlock(storedBlock, dn, storageID, delHintNode, false);
+ }
+ return null;
+ }
+*/
/**
* Queue the given reported block for later processing in the
还有一个重写的方法为processReportedBlock,方法主要处理dn汇报的数据副本,
如果block不在blocksMap中,即nn不知道,则该块将标记为失效。
如果副本是合法的,即nn中可查到,则nn要记录副本位置。
如果副本不合法,则该副本被标记为corrupt,还会触发现有副本的复制工作。corrupt副本将在系统副本full后被清除。
如果副本是一个under construction的block,则副本会被添加到对应的建设队列。
。。。看错了,竟然是注释掉了,所以此处无修改。
最后
分析完毕,发现修改并不多。这不太像某企业的实例,或者说不太像某人的实例,找到原始cdh5.1.3的修改,发现修改确实比这多了不少,后续有时间再分析。
最后说下这些修改的号称效果。
对这个结果一定程度上,我是表示怀疑的,因为如果按照参数已经最优的同等条件来测,上面的那些修改可能无法带来这么大的提升。但是在某些特定环境下取得上述结果也还是可能的。