HDFS Federation是爲解決HDFS單點故障而提出的NameNode水平擴展方案,該方案允許HDFS創建多個Namespace以提高集羣的擴展性和隔離性。在Federation中新增了block-pool的概念,block-pool就是屬於單個Namespace的一組block,每個DataNode爲所有的block-pool存儲block,可以理解block-pool是一個重新將block劃分的邏輯概念,同一個DataNode中可以存儲屬於多個block-pool的多個block。所以在NameNode和DataNode通信相關的代碼方面,也做了很大的改動以支持上述特性。
在cdh3x中,DataNode與NameNode交互主要集中在DataNode這個類中,類結構比較簡單,隨着Federation概念的引入,新增了一些比較重要的類來管理邏輯層面劃分的block-pool和block-pool下的block分佈,並以block-pool爲單位來與NameNode進行相關的通信。類圖如下
BPServiceActor類實現Runnable接口,以線程的方式運行,一個BPServiceActor實例可以和一個active或standby模式的NameNode實例進行交互,它是真正的任務執行者。主要有四大職能
1.預先與NameNode進行握手
2.向NameNode註冊
3.週期性的向NameNode發送心跳
4.處理NameNode發送回的命令
一個BPOfferService實例代表在某個DataNode上的某個block-pool(一個block-pool對應一個獨立的Namespace),對block-pool對應的active和standby狀態的NameNode進行交互的操作。BPOfferService管理和每個NameNode進行實際通信的BPServiceActor實例,並作爲代理與處於active狀態和standby狀態的兩個NameNode進行交互,同時標識與active狀態NameNode通信的BPServiceActor實例。相關代碼如下
class BPOfferService { static final Log LOG = DataNode.LOG; //本block-pool服務代表的Namespace信息,和NameNode握手的第一階段分配所得 NamespaceInfo bpNSInfo; //block-pool所在DataNode相關的註冊信息,和NameNode握手的第二階段分配所得 volatile DatanodeRegistration bpRegistration; //所屬datanode實例 private final DataNode dn; //代表和當前active狀態的NameNode關聯的BPServiceActor實例 //如果所有NameNode處於standby狀態,此屬性可以爲空 //如果此屬性非空,則必指向bpServices集合中的某個實例 private BPServiceActor bpServiceToActive = null; //在本nameservice服務下指向所有NameNode的BPServiceActor實例 //不論代表的NameNode是active狀態還是standby狀態 private List<BPServiceActor> bpServices = new CopyOnWriteArrayList<BPServiceActor>(); //構造方法中根據NameNode的地址來初始化BPServiceActor,並加入到bpServices集合中 BPOfferService(List<InetSocketAddress> nnAddrs, DataNode dn) { Preconditions.checkArgument(!nnAddrs.isEmpty(), "Must pass at least one NN."); this.dn = dn; for (InetSocketAddress addr : nnAddrs) { this.bpServices.add(new BPServiceActor(addr, this)); } } }
BlockPoolManager類主要用於管理DataNode上的BPOfferService,對BPOfferService對象的創建,刪除,啓動,停止,關閉的操作都需要通過BlockPoolManager提供的方法來控制。代碼如下
class BlockPoolManager { private static final Log LOG = DataNode.LOG; //nameserviceId和BPOfferService的映射集合 private final Map<String, BPOfferService> bpByNameserviceId = Maps.newHashMap(); //blockPoolId和BPOfferService的映射集合 private final Map<String, BPOfferService> bpByBlockPoolId = Maps.newHashMap(); //BPOfferService集合 private final List<BPOfferService> offerServices = Lists.newArrayList(); //當前所屬的datanode實例 private final DataNode dn; //更新NameNode列表時的lock private final Object refreshNamenodesLock = new Object(); BlockPoolManager(DataNode dn) { this.dn = dn; } }
BlockPoolSliceScanner類用於掃描block-pool下的block文件並校驗文件是否損壞,它對block和最後的校驗時間進行跟蹤,目前不提供修改block元數據的操作。一個DataNode對應一個DataBlockScanner,DataBlockScanner對不同block-pool的BlockPoolSliceScanner進行管理。
BlockPoolSliceStorage用於管理DataNode上對應同一個block pool的BlockPoolSlices集合,由於一個DataNode上可能會掛載多個存儲設備,即邏輯上對應多個volume,一個BlockPoolSlice對應一個volume,所以對同一個DataNode上的同一個block pool,可以管理多個BlockPoolSlice。BlockPoolSliceStorage的主要職能如下
1.對新生成的block-pool對應的存儲進行格式化
2.恢復存儲狀態以保持一致性
3.在升級的時候對block-pool進行快照處理
4.回滾block-pool到上一個快照
5.刪除快照並提交block
在cdh3x中,DataNode啓動過程中與NameNode交互的操作,都是在DataNode類中進行的,包括握手,註冊,數據塊上報和發送心跳等。代碼調用關係如下圖所示
握手
註冊
數據塊上報
發送心跳
在cdh5.1中,這些操作最終都交給了BPServiceActor來處理,下面來詳細分析下具體的代碼和相互間的調用關係。
BlockPoolManager在startDataNode方法中被實例化,startDataNode調用關係如下
DataNode.startDataNode(Configuration conf, List<StorageLocation> dataDirs, SecureResources resources ) throws IOException { blockPoolManager = new BlockPoolManager(this); //刷新加載NameNodes blockPoolManager.refreshNamenodes(conf); } BlockPoolManager.refreshNamenodes(Configuration conf) throws IOException { LOG.info("Refresh request received for nameservices: " + conf.get(DFSConfigKeys.DFS_NAMESERVICES)); //地址映射列表,Map<nameserviceId,<namenodeId,nnAddress>> Map<String, Map<String, InetSocketAddress>> newAddressMap = DFSUtil.getNNServiceRpcAddresses(conf); synchronized (refreshNamenodesLock) { doRefreshNamenodes(newAddressMap); } } BlockPoolManager.doRefreshNamenodes(Map<String, Map<String, InetSocketAddress>> addrMap){ Set<String> toRefresh = Sets.newLinkedHashSet(); Set<String> toAdd = Sets.newLinkedHashSet(); Set<String> toRemove; synchronized (this) { for (String nameserviceId : addrMap.keySet()) { if (bpByNameserviceId.containsKey(nameserviceId)) { //已經存在,可能有更新的nameserviceId toRefresh.add(nameserviceId); } else { //加入新的nameserviceId toAdd.add(nameserviceId); } } //找出bpByNameserviceId存在的,但不存在於addrMap的nameserviceId //等待刪除 toRemove = Sets.newHashSet(Sets.difference( bpByNameserviceId.keySet(), addrMap.keySet())); //啓動新的nameservice if (!toAdd.isEmpty()) { for (String nsToAdd : toAdd) { ArrayList<InetSocketAddress> addrs = Lists.newArrayList(addrMap.get(nsToAdd).values()); //根據NameNode地址集合創建新的BPOfferService實例 BPOfferService bpos = createBPOS(addrs); //建立nameserviceId到BPOfferService的映射 bpByNameserviceId.put(nsToAdd, bpos); //加入到offerServices集合 offerServices.add(bpos); } } //啓動BPOfferService服務 startAll(); } //刪除toRemove中的nameserviceId的映射關係,並停止相關服務 if (!toRemove.isEmpty()) { for (String nsToRemove : toRemove) { BPOfferService bpos = bpByNameserviceId.get(nsToRemove); bpos.stop(); bpos.join(); } } //刷新變化的nameserviceId if (!toRefresh.isEmpty()) { for (String nsToRefresh : toRefresh) { BPOfferService bpos = bpByNameserviceId.get(nsToRefresh); ArrayList<InetSocketAddress> addrs = Lists.newArrayList(addrMap.get(nsToRefresh).values()); bpos.refreshNNList(addrs); } } } BlockPoolManager.startAll() throws IOException { try { UserGroupInformation.getLoginUser().doAs( new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { for (BPOfferService bpos : offerServices) { //啓動BPOfferService服務 bpos.start(); } return null; } }); } catch (InterruptedException ex) { IOException ioe = new IOException(); ioe.initCause(ex.getCause()); throw ioe; } } BPOfferService.start() { for (BPServiceActor actor : bpServices) { //啓動BPServiceActor服務 actor.start(); } }
經過層層調用之後,真正和NameNode進行通信的BPServiceActor服務被啓動,啓動後的BPServiceActor開始和它對應狀態的NameNode進行握手註冊等一系列操作,BPServiceActor服務對應的NameNode可能是active或standby狀態。詳細代碼如下
BPServiceActor.run() { LOG.info(this + " starting to offer service"); try { while (true) { try { //連接到NameNode並進行握手 connectToNNAndHandshake(); break; } catch (IOException ioe) { runningState = RunningState.INIT_FAILED; if (shouldRetryInit()) { LOG.error("Initialization failed for " + this + " " + ioe.getLocalizedMessage()); sleepAndLogInterrupts(5000, "initializing"); } else { runningState = RunningState.FAILED; LOG.fatal("Initialization failed for " + this + ". Exiting. ", ioe); return; } } } runningState = RunningState.RUNNING; while (shouldRun()) { try { //循環調用offerService() //在本方法中,週期性的向NameNode發送心跳並執行NameNode返回的相關命令 offerService(); } catch (Exception ex) { LOG.error("Exception in BPOfferService for " + this, ex); sleepAndLogInterrupts(5000, "offering service"); } } runningState = RunningState.EXITED; } catch (Throwable ex) { LOG.warn("Unexpected exception in block pool " + this, ex); runningState = RunningState.FAILED; } finally { LOG.warn("Ending block pool service for: " + this); cleanUp(); } } BPServiceActor.connectToNNAndHandshake() throws IOException { //連接到NameNode並獲得NameNode代理對象 bpNamenode = dn.connectToNN(nnAddr); //第一階段獲取NamespaceInfo NamespaceInfo nsInfo = retrieveNamespaceInfo(); //校驗namespaceInfo是否和HA中的其他NameNode信息一致 //並建立blockPoolManager和BPOfferService的對應關係 bpos.verifyAndSetNamespaceInfo(nsInfo); //第二階段向NameNode註冊 register(); }
上述主要分析了加入Federation特性和HA特性後,DataNode和NameNode在代碼層面交互方式的改變,相比之前的代碼,邏輯更加清晰並且類之間的耦合度更低。