zoukankan      html  css  js  c++  java
  • HDFS全量块汇报(FBR)的限流机制

    前言


    众所周知在HDFS中,DataNode会进行定期的全量块汇报操作到NameNode中,来向NameNode表明其上所存储的所有块数据。这个动作在HDFS中称之为Full Block Report,简称FBR。FBR其实是一个十分expensive的操作,尤其当DataNode存储有大量block块的时候。又因为HDFS FSN全局单一锁的设计,当NameNode同时需要处理来自其下多个DataNode的FBR的时候,就可能陷入overload的情况。进而表现在用户应用层面,就是用户请求响应延时等等。本文笔者来聊聊HDFS的全量块汇报问题,以及目前社区对此的优化方案。

    HDFS全量块汇报(FBR)的性能问题


    如上文所提到的,由于HDFS内部的全局单一锁的设计,当NameNode对系统元数据做修改操作时,其它所有别的操作都将被block住。因此假设每个DN的FBR都包含有大量块信息的时候,NameNode将会花费很大的开销来处理这些汇报的块。

    Hadoop社区在很早的时候就发现了这个问题,它提出的一个改进思路是:既然一整个大的FBR行为会造成很大的性能问题,那么我们是否可以将它拆分成多个小部分的块,并且分多次RPC进行发送呢?基于这个思路,社区在HDFS-5153: Datanode should send block reports for each storage in a separate message.实现了基于每个Storage的块汇报实现。在此优化下,当DataNode发现自身全量块汇报的总数大于阈值块汇报数(默认为100w)时,会将块按照每个Storage存储目录进行汇报,这样一个大的FBR RPC就变为了多次小的RPC行为。这样的话,远端NameNode处理DataNode的FBR压力会小许多。,相关逻辑代码如下:

          // 当全量块汇报数小于阈值块时,进行一次性汇报行为
          if (totalBlockCount < dnConf.blockReportSplitThreshold) {
            // Below split threshold, send all reports in a single message.
            DatanodeCommand cmd = bpNamenode.blockReport(
                bpRegistration, bpos.getBlockPoolId(), reports,
                  new BlockReportContext(1, 0, reportId, fullBrLeaseId, true));
            blockReportSizes.add(
                calculateBlockReportPBSize(useBlocksBuffer, reports));
            numRPCs = 1;
            numReportsSent = reports.length;
            if (cmd != null) {
              cmds.add(cmd);
            }
          } else {
            // 否则按照Storage,进行多次RPC汇报
            // Send one block report per message.
            for (int r = 0; r < reports.length; r++) {
              StorageBlockReport singleReport[] = { reports[r] };
              DatanodeCommand cmd = bpNamenode.blockReport(
                  bpRegistration, bpos.getBlockPoolId(), singleReport,
                  new BlockReportContext(reports.length, r, reportId,
                      fullBrLeaseId, true));
    

    NameNode的FBR限流机制


    上面提到的HDFS-5153改进虽然在一定程度上的确是优化了HDFS的FBR处理,但是随着当今存储技术的进步,单块磁盘能够存储的数据量也在不断提升。进一步地来说,DataNode将FBR拆分出的小的Storage Report也可能存在包含有大量块信息的情况。面对Storage存储密度不断上升的情况,HDFS-5153并没有解决掉本质问题。

    那么这里我们有什么别的优化方向呢?我们是否能在NameNode端做特殊处理,能够使得它避免长时间忙碌于FBR的处理之中呢?为此,社区在block report中引入了租约的概念来控制DataNode的全量块汇报行为,简称BR Lease。在BR Lease机制下,只有那些获得了NameNode所授予的BR Lease的DataNode节点,才能进行FBR行为。有了这层控制,NameNode就能够减缓底层大量DataNode所带来的FBR操作压力了。

    用一个轻松、简单的对话来模拟BR Lease下的FBR行为:

    DN: Hi NN,我现在能进行全量块汇报行为吗?
    NN:我现在太忙了,不能让你汇报。
    过了一会儿,DN又来问了:
    DN: Hi NN,我现在可以进行全量块汇报行为吗?
    NN:现在不忙了,可以汇报了,我来分配给你一个租约。
    DN拿到了NN所授予的租约,NN验证了DN租约的有效性后,然后进行了FBR处理。

    其实从中我们可以看到,BR Lease在这里变相起到了Rate Limit的作用,至于这个Rate到底控制在什么值,可以根据具体场景进行具体设置。

    BR Lease的管理控制


    社区在HDFS-7923: The DataNodes should rate-limit their full block reports by asking the NN on heartbeat messages中实现了基于BR Lease的FBR的限流控制。在此实现内,新增了一个专门管理BR Lease的管理类BlockReportLeaseManager。BlockReportLeaseManager负责有以下2类功能:

    • 分配DataNode租约Id
    • 处理块汇报前验证DataNode提供的租约Id是否有效

    BlockReportLeaseManager对DataNode的BR Lease做了额外两项的限制:

    • 当前最多允许的Lease分配数,进而限制DataNode的FBR上报数,DataNode只有拿到Lease Id才能进行下一步的FBR。
    • 每个Lease有其过期时间,过期时间设置是为了限制Lease的有效使用时间范围,借此避免DataNode长时间占用Lease。

    基于BR Lease的FBR限流逻辑


    下面我们通过具体代码来展示基于BR Lease的FBR限流逻辑。

    首先是DataNode端,相关操作方法BPServiceActor#offerServic

              // 1. 判断是否需要发送请求BR Lease
              boolean requestBlockReportLease = (fullBlockReportLeaseId == 0) &&
                      scheduler.isBlockReportDue(startTime);
              if (!dn.areHeartbeatsDisabledForTests()) {
            	// 2. 发送心跳给NN,获取BR Lease信息
                resp = sendHeartBeat(requestBlockReportLease);
                assert resp != null;
                if (resp.getFullBlockReportLeaseId() != 0) {
                  if (fullBlockReportLeaseId != 0) {
                  ...
                  fullBlockReportLeaseId = resp.getFullBlockReportLeaseId();
                }
                ..
            // 3. 如果获取到的Lease Id不为0,则进行FBR操作(顺带带上LeaseId)
            if ((fullBlockReportLeaseId != 0) || forceFullBr) {
              cmds = blockReport(fullBlockReportLeaseId);
              // 重置FBR的Lease Id, 为了让DN下次重新申请Lease Id
              fullBlockReportLeaseId = 0;
            }
    

    然后我们紧接着来看服务端的逻辑方法,首先是FSNamesystem.handleHeartbeat对于request FBR Lease的处理,相关方法FSNamesystemhandleHeartbeat

      HeartbeatResponse handleHeartbeat(DatanodeRegistration nodeReg,
          StorageReport[] reports, long cacheCapacity, long cacheUsed,
          int xceiverCount, int xmitsInProgress, int failedVolumes,
          VolumeFailureSummary volumeFailureSummary,
          boolean requestFullBlockReportLease,
          @Nonnull SlowPeerReports slowPeers,
          @Nonnull SlowDiskReports slowDisks)
              throws IOException {
        readLock();
        try {
          //get datanode commands
          ...
          long blockReportLeaseId = 0;
          // 从BlockManage中获取租约
          if (requestFullBlockReportLease) {
            blockReportLeaseId =  blockManager.requestBlockReportLeaseId(nodeReg);
          }
    
          //...
          return new HeartbeatResponse(cmds, haState, rollingUpgradeInfo,
              blockReportLeaseId);
        } finally {
          readUnlock("handleHeartbeat");
        }
      }
    

    我们进入BlockManager的requestBlockReportLeaseId方法,在里面分配BR LeaseId时就有着限流逻辑,通过当前可允许分配的最大Lease数来做,相关方法BlockReportLeaseManager#requestLease

      public synchronized long requestLease(DatanodeDescriptor dn) {
        NodeData node = nodes.get(dn.getDatanodeUuid());
        ...
        // 1. 在分配新的Lease之前先移除过期的Lease
        pruneExpiredPending(monotonicNowMs);
        // 2. 如果当前的有效Lease超过最大可允许值,返回0代表请求Lease Id失败
        if (numPending >= maxPending) {
          if (LOG.isDebugEnabled()) {
            StringBuilder allLeases = new StringBuilder();
            String prefix = "";
            for (NodeData cur = pendingHead.next; cur != pendingHead;
                 cur = cur.next) {
              allLeases.append(prefix).append(cur.datanodeUuid);
              prefix = ", ";
            }
            LOG.debug("Can't create a new BR lease for DN {}, because " +
                  "numPending equals maxPending at {}.  Current leases: {}",
                  dn.getDatanodeUuid(), numPending, allLeases.toString());
          }
          return 0;
        }
        ...
      }
    

    接着是BlockManager对于DataNode上报的FBR的处理,相关方法NameNodeRpcServer#blockReport

      public DatanodeCommand blockReport(final DatanodeRegistration nodeReg,
            String poolId, final StorageBlockReport[] reports,
            final BlockReportContext context) throws IOException {
        checkNNStartup();
        ...
        try {
          // 1. 检查FBR租约的有效性,主要包含两方面的检查
          //    1)租约Id是否为当前已分配给此DN的Lease Id值
          //    2)租约Id是否已过期
          if (bm.checkBlockReportLease(context, nodeReg)) {
            for (int r = 0; r < reports.length; r++) {
              final BlockListAsLongs blocks = reports[r].getBlocks();
              ...
              final int index = r;
              // FBR租约验证通过,BlockManager进行FBR的处理
              noStaleStorages = bm.runBlockOp(() ->
                bm.processReport(nodeReg, reports[index].getStorage(),
                    blocks, context));
            }
            ...
    

    附上上述逻辑的流程图:

    在这里插入图片描述

    以上就是基于Lease的FBR限流控制原理,但是回过头来再细看这个方案,它依然还不是最完美的。因为DataNode的FBR在本质上依然没有被彻底改造,可能在未来更好的做法将FBR行为进行分段拆分,然后NN再要求DN汇报这些分段的block report,然后进行处理。这里笔者想表达一个核心point是FBR的行为应该由NN这边来控制,它来要求DN怎么去发,而不是目前单方面的由DN一股脑地将自身的全量块汇报给NN。

    引用

    [1]. https://issues.apache.org/jira/browse/HDFS-5153
    [2]. https://issues.apache.org/jira/browse/HDFS-7923

  • 相关阅读:
    原子操作--sync/atomic的用法
    基础的排序算法以及查找算法
    (三)MySQL终极篇
    (二)MySQL中级篇
    数据库表添加索引对性能的影响
    事务的四大特性以及事务的隔离级别
    int 和Integer
    数据库三范式
    Java反射
    获取Class实例的三种方式
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183527.html
Copyright © 2011-2022 走看看