/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hdfs.server.datanode; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.server.datanode.FSDataset.ActiveFile; import org.apache.hadoop.hdfs.server.datanode.FSDataset.FSDatasetDeltaInterface; import org.apache.hadoop.hdfs.server.datanode.FSDataset.FSVolume; /** * A class to maintain a namespace's block->blockInfo map and block->ongoing * create map */ public class NamespaceMap { int NUM_BUCKETS = 64; /** * A bucket of blocks. */ public class BlockBucket { int bucketId; Map<Block, DatanodeBlockInfo> blockInfoMap; BlockBucket(int bucketId) { blockInfoMap = new HashMap<Block, DatanodeBlockInfo>(); this.bucketId = bucketId; } synchronized int removeUnhealthyVolumes(Collection<FSVolume> failed_vols, FSDatasetDeltaInterface datasetDelta) { int removed_blocks = 0; Iterator<Entry<Block, DatanodeBlockInfo>> dbi = blockInfoMap.entrySet() .iterator(); while (dbi.hasNext()) { Entry<Block, DatanodeBlockInfo> entry = dbi.next(); for (FSVolume v : failed_vols) { if (entry.getValue().getBlockDataFile().getVolume() == v) { DataNode.LOG.warn("removing block " + entry.getKey().getBlockId() + " from vol " + v.toString() + ", form namespace: " + namespaceId); dbi.remove(); if (datasetDelta != null) { datasetDelta.removeBlock(namespaceId, entry.getKey()); } removed_blocks++; break; } } } return removed_blocks; } synchronized DatanodeBlockInfo getBlockInfo(Block block) { return blockInfoMap.get(block); } synchronized void getBlockReport(List<Block> ret) { for (Entry<Block, DatanodeBlockInfo> e : blockInfoMap.entrySet()) { if (e.getValue().isFinalized()) { ret.add(e.getKey()); } } } synchronized List<DatanodeBlockInfo> getBlockInfosForTesting() { List<DatanodeBlockInfo> blockInfos = new ArrayList<DatanodeBlockInfo>(); for (Entry<Block, DatanodeBlockInfo> e : blockInfoMap.entrySet()) { blockInfos.add(e.getValue()); } return blockInfos; } synchronized DatanodeBlockInfo addBlockInfo(Block block, DatanodeBlockInfo replicaInfo) { DatanodeBlockInfo oldInfo = blockInfoMap.put(block, replicaInfo); if (oldInfo != null) { oldInfo.getBlockDataFile().closeFileChannel(); } return oldInfo; } synchronized DatanodeBlockInfo removeBlockInfo(Block block) { DatanodeBlockInfo removedInfo = blockInfoMap.remove(block); if (removedInfo != null) { removedInfo.getBlockDataFile().closeFileChannel(); } return removedInfo; } /** * Get the size of the map for given namespace * * @return the number of replicas in the map */ synchronized int size() { return blockInfoMap.size(); } public synchronized String toString() { return blockInfoMap.toString(); } synchronized void getBlockCrcPerVolume( Map<FSVolume, List<Map<Block, DatanodeBlockInfo>>> fsVolumeMap) { for (Map.Entry<Block, DatanodeBlockInfo> entry: blockInfoMap.entrySet()) { Block block = entry.getKey(); DatanodeBlockInfo binfo = entry.getValue(); if (fsVolumeMap.containsKey(binfo.getBlockDataFile().getVolume()) && binfo.hasBlockCrcInfo()) { fsVolumeMap.get(binfo.getBlockDataFile().getVolume()).get(bucketId) .put(block, binfo); } } } /** * @param blockCrcInfos * @return number of blocks whose CRCs were updated. * @throws IOException */ synchronized int updateBlockCrc(List<BlockCrcInfoWritable> blockCrcInfos) throws IOException { int updatedCount = 0; Block tmpBlock = new Block(); for (BlockCrcInfoWritable blockCrcInfo : blockCrcInfos) { tmpBlock.set(blockCrcInfo.blockId, 0, blockCrcInfo.blockGenStamp); DatanodeBlockInfo info = getBlockInfo(tmpBlock); if (info != null && !info.hasBlockCrcInfo()) { updatedCount++; info.setBlockCrc(blockCrcInfo.blockCrc); } } return updatedCount; } /** * * @param reader * @return number of blocks whose CRCs were updated. * @throws IOException */ int updateBlockCrc(BlockCrcFileReader reader) throws IOException { int updatedCount = 0; int batchCount = 0; List<BlockCrcInfoWritable> listCrcInfo = new ArrayList<BlockCrcInfoWritable>(); while (reader.moveToNextRecordAndGetItsBucketId() == this.bucketId) { BlockCrcInfoWritable blockCrcInfo = reader.getNextRecord(); if (blockCrcInfo == null) { DataNode.LOG.warn("Connot get next block crc record from file."); return updatedCount; } else { listCrcInfo.add(blockCrcInfo); batchCount++; if (batchCount >= 5000) { // We don't want to hold the lock for too long. updateBlockCrc(listCrcInfo); listCrcInfo.clear(); batchCount = 0; try { Thread.sleep(1); } catch (InterruptedException e) { DataNode.LOG.warn("thread interrupted"); } } } } if (listCrcInfo.size() > 0) { updatedCount += updateBlockCrc(listCrcInfo); } return updatedCount; } } // Map of block Id to DatanodeBlockInfo final private int numBucket; final private int namespaceId; final private Map<Block, ActiveFile> ongoingCreates; final private BlockBucket[] blockBuckets; NamespaceMap(int namespaceId) { numBucket = NUM_BUCKETS; this.namespaceId = namespaceId; ongoingCreates = new ConcurrentHashMap<Block, ActiveFile>(); blockBuckets = new BlockBucket[numBucket]; for (int i = 0; i < numBucket; i++) { blockBuckets[i] = new BlockBucket(i); } } public int getNumBucket() { return numBucket; } public BlockBucket getBucket(int i) { if (i < 0 || i >= blockBuckets.length) { return null; } return blockBuckets[i]; } int getBucketId(Block block) { int bucketId = (int) (block.getBlockId() % numBucket); if (bucketId < 0) { bucketId += numBucket; } return bucketId; } public BlockBucket getBlockBucket(Block block) { return blockBuckets[getBucketId(block)]; } int removeUnhealthyVolumes(Collection<FSVolume> failed_vols, FSDatasetDeltaInterface datasetDelta) { int removed_blocks = 0; for (BlockBucket blockBucket : blockBuckets) { removed_blocks += blockBucket.removeUnhealthyVolumes(failed_vols, datasetDelta); } return removed_blocks; } /** * Get the meta information of the replica that matches both block id and * generation stamp * * @param block * block with its id as the key * @return the replica's meta information * @throws IllegalArgumentException * if the input block or block pool is null */ DatanodeBlockInfo getBlockInfo(Block block) { return getBlockBucket(block).getBlockInfo(block); } /** * Add a replica's meta information into the map * * @param replicaInfo * a replica's meta information * @return previous meta information of the replica * @throws IllegalArgumentException * if the input parameter is null */ DatanodeBlockInfo addBlockInfo(Block block, DatanodeBlockInfo replicaInfo) { return getBlockBucket(block).addBlockInfo(block, replicaInfo); } DatanodeBlockInfo updateBlockInfo(Block oldB, Block newB) { int bidOld = this.getBucketId(oldB); int bidNew = this.getBucketId(newB); BlockBucket bOld = blockBuckets[bidOld]; BlockBucket bNew = blockBuckets[bidNew]; BlockBucket lock1 = bOld; BlockBucket lock2 = bNew; // make sure we acquire the locks in order if (bidOld > bidNew) { BlockBucket t = lock1; lock1 = lock2; lock2 = t; } synchronized (lock1) { synchronized (lock2) { DatanodeBlockInfo bi = bOld.removeBlockInfo(oldB); if (bi != null) { bNew.addBlockInfo(newB, bi); bi.setBlock(newB); } return bi; } } } /** * Remove the replica's meta information from the map that matches the input * block's id and generation stamp * * @param namespaceId * @param block * block with its id as the key * @return the removed replica's meta information * @throws IllegalArgumentException * if the input block is null */ DatanodeBlockInfo removeBlockInfo(Block block) { return getBlockBucket(block).removeBlockInfo(block); } /** * Get the size of the map for given namespace * * @return the number of replicas in the map */ int size() { int ret = 0; for (BlockBucket bb : blockBuckets) { ret += bb.size(); } return ret; } // for ongoing creates ActiveFile getOngoingCreates(Block block) { return ongoingCreates.get(block); } ActiveFile removeOngoingCreates(Block block) { return ongoingCreates.remove(block); } ActiveFile addOngoingCreates(Block block, ActiveFile af) { return ongoingCreates.put(block, af); } /** * If there is an ActiveFile object for the block, create a copy of the * old one and replace the old one. This is to make sure that the VisibleLength * applied to the old object will have no impact to the local map. In * that way, BlockReceiver can directly update visible length without * holding the lock. * * @param block * @throws CloneNotSupportedException */ void copyOngoingCreates(Block block) throws CloneNotSupportedException { ActiveFile af = ongoingCreates.get(block); if (af == null) { return; } ongoingCreates.put(block, af.getClone()); } /** * get a list of block info with CRC information per FS volume. * * @param volumes * Volumes are interested in get the list * @return a map from FSVolume to buckets -> (Block -> DatanodeBlockInfo) in * the volume and has CRC information. The first level value is a * list, each one on the list is for a bucket. The order on the list * is the bucket ID. The third level is a map from block to datablock * info. */ Map<FSVolume, List<Map<Block, DatanodeBlockInfo>>> getBlockCrcPerVolume( List<FSVolume> volumes) { Map<FSVolume, List<Map<Block, DatanodeBlockInfo>>> retMap = new HashMap<FSVolume, List<Map<Block, DatanodeBlockInfo>>>(); for (FSVolume volume : volumes) { List<Map<Block, DatanodeBlockInfo>> newSubMap = new ArrayList<Map<Block, DatanodeBlockInfo>>( numBucket); for (int i = 0; i < numBucket; i++) { newSubMap.add(new HashMap<Block, DatanodeBlockInfo>()); } retMap.put(volume, newSubMap); } for (BlockBucket bb : blockBuckets) { bb.getBlockCrcPerVolume(retMap); } return retMap; } /** * @param reader * @return number of blocks whose CRCs were updated. * @throws IOException */ int updateBlockCrc(BlockCrcFileReader reader) throws IOException { int retValue = 0; for (BlockBucket bucket : blockBuckets) { retValue += bucket.updateBlockCrc(reader); } return retValue; } public String toString() { StringBuilder sb = new StringBuilder(); for (BlockBucket bb : blockBuckets) { sb.append(bb.toString() + "\n"); } return sb.toString() + "\n---\n" + ongoingCreates.toString(); } }