HDFS的副本存放策略
阿新 • • 發佈:2019-02-07
上面的流程圖詳細的描述了Hadoop-0.2.0版本中副本的存放位置的選擇策略,當然,這當中還有一些細節問題,如:如何選擇一個本地資料節點,如何選擇一個本地機架資料節點等,所以下面我還將繼續展開討論。
1.選擇一個本地節點
這裡所說的本地節點是相對於客戶端來說的,也就是說某一個使用者正在用一個客戶端來向HDFS中寫資料,如果該客戶端上有資料節點,那麼就應該最優先考慮把正在寫入的資料的一個副本儲存在這個客戶端的資料節點上,它即被看做是本地節點,但是如果這個客戶端上的資料節點空間不足或者是當前負載過重,則應該從該資料節點所在的機架中選擇一個合適的資料節點作為此時這個資料塊的本地節點。另外,如果客戶端上沒有一個數據節點的話,則從整個叢集中隨機選擇一個合適的資料節點作為
- private boolean isGoodTarget(DatanodeDescriptor node, long blockSize, int maxTargetPerLoc, boolean considerLoad, List<DatanodeDescriptor> results) {
- Log logr = FSNamesystem.LOG;
- // 節點不可用了
- if (node.isDecommissionInProgress() || node.isDecommissioned()) {
- logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the node is (being) decommissioned");
- return false;
- }
- long remaining = node.getRemaining() - (node.getBlocksScheduled() * blockSize);
- // 節點剩餘的容量夠不夠
- if (blockSize* FSConstants.MIN_BLOCKS_FOR_WRITE>remaining) {
- logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the node does not have enough space");
- return false;
- }
- // 節點當前的負載情況
- if (considerLoad) {
- double avgLoad = 0;
- int size = clusterMap.getNumOfLeaves();
- if (size != 0) {
- avgLoad = (double)fs.getTotalLoad()/size;
- }
- if (node.getXceiverCount() > (2.0 * avgLoad)) {
- logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the node is too busy");
- return false;
- }
- }
- // 該節點坐在的機架被選擇存放當前資料塊副本的資料節點過多
- String rackname = node.getNetworkLocation();
- int counter=1;
- for(Iterator<DatanodeDescriptor> iter = results.iterator(); iter.hasNext();) {
- Node result = iter.next();
- if (rackname.equals(result.getNetworkLocation())) {
- counter++;
- }
- }
- if (counter>maxTargetPerLoc) {
- logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the rack has too many chosen nodes");
- return false;
- }
- return true;
- }
實際上,選擇本地節假節點和遠端機架節點都需要以一個節點為參考,這樣才是有意義,所以在上面的流程圖中,我用紅色字型標出了參考點。那麼,ReplicationTargetChooser是如何根據一個節點選擇它的一個本地機架節點呢?
這個過程很簡單,如果參考點為空,則從整個叢集中隨機選擇一個合適的資料節點作為此時的本地機架節點;否則就從參考節點所在的機架中隨機選擇一個合適的資料節點作為此時的本地機架節點,若這個叢集中沒有合適的資料節點的話,則從已選擇的資料節點中找出一個作為新的參考點,如果找到了一個新的參考點,則從這個新的參考點在的機架中隨機選擇一個合適的資料節點作為此時的本地機架節點;否則從整個叢集中隨機選擇一個合適的資料節點作為此時的本地機架節點。如果新的參考點所在的機架中仍然沒有合適的資料節點,則只能從整個叢集中隨機選擇一個合適的資料節點作為此時的本地機架節點了。
- <font xmlns="http://www.w3.org/1999/xhtml">private DatanodeDescriptor chooseLocalRack(DatanodeDescriptor localMachine, List<Node> excludedNodes, long blocksize, int maxNodesPerRack, List<DatanodeDescriptor> results)throws NotEnoughReplicasException {
- // 如果參考點為空,則從整個叢集中隨機選擇一個合適的資料節點作為此時的本地機架節點
- if (localMachine == null) {
- return chooseRandom(NodeBase.ROOT, excludedNodes, blocksize, maxNodesPerRack, results);
- }
- //從參考節點所在的機架中隨機選擇一個合適的資料節點作為此時的本地機架節點
- try {
- return chooseRandom(localMachine.getNetworkLocation(), excludedNodes, blocksize, maxNodesPerRack, results);
- } catch (NotEnoughReplicasException e1) {
- //若這個叢集中沒有合適的資料節點的話,則從已選擇的資料節點中找出一個作為新的參考點
- DatanodeDescriptor newLocal=null;
- for(Iterator<DatanodeDescriptor> iter=results.iterator(); iter.hasNext();) {
- DatanodeDescriptor nextNode = iter.next();
- if (nextNode != localMachine) {
- newLocal = nextNode;
- break;
- }
- }
- if (newLocal != null) {//找到了一個新的參考點
- try {
- //從這個新的參考點在的機架中隨機選擇一個合適的資料節點作為此時的本地機架節點
- return chooseRandom(newLocal.getNetworkLocation(), excludedNodes, blocksize, maxNodesPerRack, results);
- } catch(NotEnoughReplicasException e2) {
- //新的參考點所在的機架中仍然沒有合適的資料節點,從整個叢集中隨機選擇一個合適的資料節點作為此時的本地機架節點
- return chooseRandom(NodeBase.ROOT, excludedNodes, blocksize, maxNodesPerRack, results);
- }
- } else {
- //從整個叢集中隨機選擇一個合適的資料節點作為此時的本地機架節點
- return chooseRandom(NodeBase.ROOT, excludedNodes, blocksize, maxNodesPerRack, results);
- }
- }
- }</font>
選擇一個遠端機架節點就是隨機的選擇一個合適的不在參考點坐在的機架中的資料節點,如果沒有找到這個合適的資料節點的話,就只能從參考點所在的機架中選擇一個合適的資料節點作為此時的遠端機架節點了。
4.隨機選擇若干資料節點
這裡的隨機隨機選擇若干個資料節點實際上指的是從某一個範圍內隨機的選擇若干個節點,它的實現需要利用前面提到過的NetworkTopology資料結構。隨機選擇所使用的範圍本質上指的是一個路徑,這個路徑表示的是NetworkTopology所表示的樹狀網路拓撲圖中的一個非葉子節點,隨機選擇針對的就是這個節點的所有葉子子節點,因為所有的資料節點都被表示成了這個樹狀網路拓撲圖中的葉子節點。
5.優化資料傳輸的路徑
以前說過,HDFS對於Block的副本copy採用的是流水線作業的方式:client把資料Block只傳給一個DataNode,這個DataNode收到Block之後,傳給下一個DataNode,依次類推,...,最後一個DataNode就不需要下傳資料Block了。所以,在為一個數據塊確定了所有的副本存放的位置之後,就需要確定這種資料節點之間流水複製的順序,這種順序應該使得資料傳輸時花費的網路延時最小。ReplicationTargetChooser用了非常簡單的方法來考量的,大家一看便知:
- private DatanodeDescriptor[] getPipeline( DatanodeDescriptor writer, DatanodeDescriptor[] nodes) {
- if (nodes.length==0) return nodes;
- synchronized(clusterMap) {
- int index=0;
- if (writer == null || !clusterMap.contains(writer)) {
- writer = nodes[0];
- }
- for(;index<nodes.length; index++) {
- DatanodeDescriptor shortestNode = nodes[index];
- int shortestDistance = clusterMap.getDistance(writer, shortestNode);
- int shortestIndex = index;
- for(int i=index+1; i<nodes.length; i++) {
- DatanodeDescriptor currentNode = nodes[i];
- int currentDistance = clusterMap.getDistance(writer, currentNode);
- if (shortestDistance>currentDistance) {
- shortestDistance = currentDistance;
- shortestNode = currentNode;
- shortestIndex = i;
- }
- }
- //switch position index & shortestIndex
- if (index != shortestIndex) {
- nodes[shortestIndex] = nodes[index];
- nodes[index] = shortestNode;
- }
- writer = shortestNode;
- }
- }
- return nodes;
- }